Compare commits

...

173 Commits
v1.0 ... main

Author SHA1 Message Date
Joshua Bell
1ed40ff2dc Actions: Rev a dependency 2024-01-31 20:29:50 -08:00
Joshua Bell
1d6b923783 Actions: Rev a couple dependencies 2024-01-31 20:26:41 -08:00
Joshua Bell
7aeb2e55ad Docs: fix readme link 2024-01-31 20:22:03 -08:00
Joshua Bell
7bbc96924b Add SETUP.SYSTEM and build .SETUP files
This is an alternate approach proposed by Sean Nolan in 1987 which
allows placing the driver files in a subdirectory of the root volume
to avoid clutter and file ordering issues. Only a SETUP.SYSTEM file is
needed at the top level, and the drivers go into a SETUPS/ directory.

All drivers here (except QUIT.SYSTEM and SETUP.SYSTEM itself) have
alternate forms built into the /DRIVERS/SETUPS/ directory as XYZ.SETUP
instead of XYZ.SYSTEM. If you choose to use SETUP.SYSTEM, place these
.SETUP files in your SETUPS/ directory. The naming doesn't matter -
any SYS or BIN file can be used - but this convention makes
distribution easier. These .SETUP files do **NOT** chain to the next
file - that's handled by SETUP.SYSTEM itself.

Resolves #16
2024-01-30 21:16:32 -08:00
Joshua Bell
537d79810b Jumbo: Patch ThunderClock driver for 2023-2028
If ProDOS finds a ThunderClock it installs a built-in driver for it.
The ThunderClock card gives day-of-week but not year. So the driver
uses the day/month/day-of-week to infer the year, using a 7-entry
table. This means the driver gives the wrong year unless it is
reasonably updated.

ProDOS 2.4.2 includes an update to handle 2018-2023 but that's about
to run out. https://github.com/ProDOS-8/ProDOS8-Testing/issues/72

Make the CLOCK.SYSTEM jumbo driver do the extra work of updating the
year table for the ThunderClock driver. This handles old versions of
ProDOS from 1.1 through 2.4.2, and the update handles 2023 through
2028. (In 2027 we can update again to handle through 2032.)
2023-12-04 20:28:25 -08:00
Joshua Bell
477c1f7764 Build: Use common out/ dir 2023-11-12 16:42:11 -08:00
Joshua Bell
b8ef97b42d Build: Fix header include path in clock Makefiles 2023-11-12 13:56:52 -08:00
Joshua Bell
764b4bd8fd ZIPCHIP.SYSTEM - log cache size 2023-11-12 12:46:37 -08:00
Joshua Bell
93e48784b9 Add ZIPCHIP.SYSTEM
This detects a ZIP CHIP and, if present, configures it to slow down
for speaker access, leaving the other settings alone.

In the future maybe there will be a configuration utility to control
the slots, as was included with the ZIP CHIP back in the day.
2023-11-11 14:24:28 -08:00
Joshua Bell
01175cf573 Prevent hang probing for The Cricket! in MAME on IIc/IIc+
In MAME, for the SSC in Slot 2, the STATUS flag never comes back with
ready-to-send, so the probe would hang. Introduce a timer, same as on
the receiving side, and timeout instead of hanging.

Similar to 14e72b7384
2023-10-22 17:08:34 -04:00
Joshua Bell
18a73004c4 The Cricket!: Fix SSC reset for Laser 128EX
The change in b182a53d made resetting/detecting The Cricket! fail on
my Laser 128EX, likely due to startup config of the SSC not being
the same as on an Apple //c. This should be a superset of the change,
but compat testing will be needed.
2023-10-14 19:40:09 -07:00
Joshua Bell
ed2fa6e7d2 No-Slot Clock: Hit PTRIG to slow ZIP/IIc+ accelerator before reading
This appears to improve reliability when reading the NSC on the
IIc+. More data is probably needed before declaring this a really
good solution, but "it works on my machine!"
2023-10-06 22:11:36 -07:00
Joshua Bell
a519d0c619 Cricket: Make SET.TIME/SET.DATETIME time inputs 24-hour by default
The Cricket! can accept both 12-hour (with AM/PM suffix) and 24-hour
times. Unify with the No-Slot Clock UI and prompt for 24-hour.
2023-07-17 08:11:50 -07:00
frankmilliron
b182a53dfa
The Cricket!: fix hang on Total Replay (#19)
* The Cricket!: fix hang on Total Replay

The Cricket reset command sends Total Replay into an endless loop when launched, presumably because of an interrupt situation (TR just does RTI on an interrupt). Toggling the COMMAND and CONTROL settings seems to allow TR to function properly, while also allowing the Cricket initialization code to silence any playing notes.

* The Cricket!: Reset Cricket before probing

Finds the Cricket every single time this way. Occasionally the driver would miss finding it due to it being in a wait state. Sending a reset first fixes that.

* The Cricket!: make sure registers are toggled for TR
2023-07-17 08:11:30 -07:00
frankmilliron
9401723f0d
The Cricket!: further refinements to 'SSC init' (#18)
//c technical reference includes a 'hardware reset' for three of the ACIA registers. Here we reset all three.
2023-07-16 13:30:04 -07:00
Joshua Bell
ec4a0eeb1e The Cricket!: Issue "Reset Cricket" sequence when detecting
This stops any active music playback, which is handy if you did a warm
boot in the middle of playback, and your The Cricket! is stuck playing
the same note constantly but you don't want to power-cycle the device
which will unset the RTC.

c/o @frankmilliron
2023-06-25 21:08:08 -07:00
Joshua Bell
caeb237321 The Cricket!: Reset SSC ACIA before probing/setting
... and don't bother saving/restoring registers during install,
as this will only follow a boot or OS restart.

Suggested by @frankmilliron

Also inhibit interrupts in the SET.XXX utilities for good measure.
2023-06-25 12:06:40 -07:00
Joshua Bell
c01d26042a The Cricket!: Add unified SET.DATETIME util 2023-06-25 11:46:35 -07:00
Joshua Bell
8ca57b2fac NSClock: Incorporate @bobbimanners Ultrawarp bug workaround
For context, see:
https://github.com/bobbimanners/ProDOS-Utils/blob/master/No_Slot_Clock/README.md
2023-06-25 11:37:59 -07:00
Joshua Bell
e706385fe9 No Slot Clock: Add SET.DATETIME utility 2023-06-24 20:10:24 -07:00
frankmilliron
774889ec2e
Improve Cricket! detection on ROM4 //c (#17)
* Improve Cricket! detection on ROM4 //c

* Add PHP/PLP
2023-05-15 18:21:31 -07:00
Joshua Bell
5211dc6296 Add build options to suppress success and/or failure logging 2023-05-01 19:47:10 -07:00
Joshua Bell
ee5fbeda2d Workflows: bump ca65 action to v2 2023-03-02 20:02:25 -08:00
Joshua Bell
d37691dead Jumbo clock driver: Include success logging 2023-01-31 20:01:05 -08:00
Joshua Bell
1ddae4fab7 No Slot Clock: Shave bytes and better comments
* Replace BCD->Binary with smaller routine
* Replace most absolute use of $200-7 with stack
* Remove unneeded sentinel byte at end of unlock sequence

Leaves 9 bytes free in driver.
2023-01-27 18:30:42 -08:00
Joshua Bell
d9e72d3c1e NSC: Probe for Z80 card before probing for NSC to avoid hang
The NSC driver installer starts off by checking each slot, bit-banging
into the card's memory space to try and detect an NSC installed. If a
Z80 card is encountered before the NSC is hit, the Z80 card is
activated, causing the 6502 to halt.

Borrow the Z80 detection routine from A2D's This Apple and verify that
there isn't a Z80 in the slot before we start bit-banging.

Fixes #15
2023-01-01 17:12:21 -08:00
Joshua Bell
01fed0d524 Workflows: Bump ncipollo/release-action version 2022-11-29 18:39:05 -08:00
Joshua Bell
dc9c77170e FujiNet Clock Driver: Fix search continuation during install
When a SmartPort device was found that wasn't the FujiNet, the search
continuation used the wrong offset to get the high byte of the slot
address.

Also, fix the "jumbo" clock driver's Makefile to ensure it is rebuilt
when dependencies change.
2022-11-28 20:20:45 -08:00
Joshua Bell
f64011338d Packaging and docs for previous commit 2022-11-26 20:35:05 -08:00
Joshua Bell
f2ea11fcde Add "jumbo" clock driver, an amalgamation of the others
This one driver pulls in the installers for each other clock driver,
and invokes each in turn:

* No-Slot Clock
* ROMX
* FujiNet
* DClock
* Cricket!

This requires adding `.ifndef JUMBO` guards in the other drivers for
when they pull in include files (symbols, macros, etc). The other
drivers are adjusted to return with carry clear on successful install,
failure otherwise.
2022-11-26 20:29:38 -08:00
Joshua Bell
6f142c08d6 linkify docs 2022-11-18 18:46:33 -08:00
Joshua Bell
3a085d0d60 Add docs 2022-11-18 18:38:50 -08:00
Joshua Bell
8f658437ab Move clock driver sources into clocks/ subdir to tidy top level 2022-11-18 18:23:03 -08:00
Joshua Bell
c4c14419fe Tidy following previous 2022-11-15 20:36:19 -08:00
Joshua Bell
ac4b655661
Merge pull request #13 from ivanizag/main
Driver for the real time clock device in Fujinet
2022-11-15 20:24:15 -08:00
Ivan Izaguirre
26bde8abae Undo change not needed 2022-11-15 22:42:45 +01:00
Ivan Izaguirre
f0abdcc738 Driver for the Fujinet Clock 2022-11-14 19:05:30 +01:00
Joshua Bell
9590bc4b9e BuhBye: Cycle through volumes after the boot volume more intuitively
Previously, the boot volume was shown first, but then tab would cycle
from the highest priority volume to the lowest. This would be awkward
if the boot volume was not the highest priority volume. For example,
with these devices (listed lowest to highest priority, like DEVLST),
/HD1 is the boot volume but the RAM disks are highest priority:

(low) /FLOPPY2 /FLOPPY1 /HD4 /HD3 /HD2 /HD1 /RAMWORKS /RAM (high)

Prior to this fix, it would cycle:

/HD1 /RAM /RAMWORKS /HD1 /HD2 /HD3 /HD4 /FLOPPY1 /FLOPPY2 /RAM ...

After this fix, it will cycle:

/HD1 /HD2 /HD3 /HD4 /FLOPPY1 /FLOPPY2 /RAM /RAMWORKS /HD1 ...

To fit, a little bit of code golfing was necessary.
2022-09-01 20:38:07 -07:00
Joshua Bell
145aba1f66 Buhbye.system: Clear keyboard strobe on launch
This is particularly useful on MAME where you're likely to have just
pressed Return to dismiss a warning.
2022-07-03 15:29:04 -07:00
Joshua Bell
3b8fe3c461 ROMX Clock Driver: Ensure bank is restored if detection fails.
c/o @apple2geek: If your ROMX is in recovery mode, [not doing so]
hangs the system and requires a power cycle to get it back.
2022-06-24 12:42:07 -07:00
Joshua Bell
188f1b4ce8 ROMX: Align with 0.95 c/o Jeff Mazur 2022-06-04 13:37:22 -07:00
Joshua Bell
536ed14b60 RAMDRV: Place new unit number last in DEVLST
ProDOS prefers disks sorted with the fastest (e.g. ramdisks) at the
end and slowest (e.g. 5.25 floppies) at the start. This facilitates
pathname resolution starting at the end of DEVLST, so misses are
cheap.
2022-06-02 22:15:45 -07:00
Joshua Bell
c323b205bc ROMX: Update driver c/o Jeff Mazur
This ensures all of page 2 is preserved for full compatibility.
2022-06-02 22:14:48 -07:00
Joshua Bell
b1c3d52e0a Add ME.FIRST.SYSTEM 2022-02-03 20:14:10 -08:00
Joshua Bell
a6d4fa6308 Text color themes: Don't force 40 col/text or clear the text screen 2022-01-06 17:16:42 -08:00
Joshua Bell
3e0d41b809 Text color themes: Make the SYSTEM files chain 2022-01-06 17:12:30 -08:00
Joshua Bell
f740575697 Code formatting, constants, and comments 2022-01-06 16:58:03 -08:00
Joshua Bell
392ab0b7ef Add textcolors to build/package 2022-01-06 16:39:19 -08:00
Joshua Bell
0cd347b737
Merge pull request #11 from chrisparana/main
Add text mode color themes
2022-01-06 16:33:18 -08:00
Chris Parana
5b0ef800ce Add text mode color themes 2022-01-06 03:08:54 -05:00
Joshua Bell
ea853d7af1 Close open file on more error paths 2021-12-06 20:47:15 -08:00
Joshua Bell
94fae50608
Merge pull request #9 from rharke/close-before-quit
Close directory if no .SYSTEM file found to chain to
2021-12-06 20:16:21 -08:00
Renee Harke
39c7f83e04 Close directory if no .SYSTEM file found to chain to 2021-12-06 22:34:28 -05:00
Joshua Bell
8dbdd58d38 BUHBYE.SYSTEM: Work around MouseText bug on Franklin ACE 2X00/500 systems 2021-11-03 19:55:52 -07:00
Joshua Bell
a7e32d62d4 BUHBYE: Use 80-col save HTAB, for better compat 2021-10-31 15:59:47 -07:00
Joshua Bell
b621ac6a4d Add ROMX RTC driver, c/o Jeff Mazur.
This is a modified version of the ROMX Real-Time Clock driver. The changes include:

* Converting the source to ca65.

* Integrating with the driver installer framework.

* Adapting the driver to not modify page 2 beyond $220. The ROMX RTC
  firmware writes bytes to $2B0, and the the original driver placed
  temp code at $250. This can conflict with ProDOS applications that
  use page 2, so the driver was reworked to save/restore anything at
  at $2B0.

Other changes:

* Add a util/ source dir, and cricket/date, quit.system and
  pause.system there.

* Pull the "print current date" logic out of clock drivers into driver
  preamble.
2021-10-05 19:58:31 -07:00
Joshua Bell
7c8c608b65 Fix off-by-one in dclock installer 2021-09-10 08:16:49 -07:00
Joshua Bell
974a7d49f0 Installer: If next .SYSTEM file is not found, just QUIT.
Previously, an error was shown. This means using QUIT.SYSTEM was
required if you wanted to launch Bitsy Bye (or whatever selector you
installed). This meant extra code, and wasn't user friendly. You may
still want QUIT.SYSTEM if you have e.g. BASIC.SYSTEM later in your
directory.
2021-09-06 11:31:18 -07:00
Joshua Bell
2358dd7bab Docs for PAUSE.SYSTEM 2021-08-25 20:53:21 -07:00
Joshua Bell
46aef091df Have clock/ram drivers print at the bottom of the screen.
Trying this out. Might not stick with it.

Also added a PAUSE.SYSTEM that just waits a bit, if you want a delay
in your startup sequence to watch the log messages.
2021-08-25 20:49:41 -07:00
Joshua Bell
1a3aa1e8c6 Workflow: Try using action for ca65 2021-07-23 09:25:50 -07:00
Joshua Bell
b74244bbe1 Workflow: Try using action for cadius 2021-07-23 09:13:39 -07:00
Joshua Bell
304befcab2 Workflow: deploy on tag 2021-07-23 08:56:14 -07:00
Joshua Bell
ae13e28777 Migrate from Travis-CI to GitHub Actions 2021-06-19 17:23:00 -07:00
Joshua Bell
6e9c18c3aa travis-ci.org -> .com 2021-06-09 20:20:43 -07:00
Joshua Bell
5c71155dc3 Only run xattr if present try 3 2021-03-07 16:35:52 -08:00
Joshua Bell
73ac9cbfc0 Only run xattr if present 2021-03-07 16:05:31 -08:00
Joshua Bell
3aedc2ad21 Only run xattr if present 2021-03-07 15:58:30 -08:00
Joshua Bell
9b898fca75 Switch Travis-CI to use linux 2021-03-07 15:31:47 -08:00
Joshua Bell
ace258d862 Rejiggered the selectors, added actual 40-column Bird's Better Bye.
I discovered that Bird's Better Bye and the ProDOS 1.9 selector are different.
Documentation updated with the history to the best of my knowledge.
2020-12-23 19:23:23 -08:00
Joshua Bell
1312b3f386 Rename BBB installer to BBB.SYSTEM 2020-12-21 21:37:48 -08:00
Joshua Bell
5cf3426967 Add AE DClock driver, based on work by M.G. Fixes #3 2020-12-21 20:47:07 -08:00
Joshua Bell
cc49835c9f RAM.DRV.SYSTEM: Show block count. Resolves #2 2020-12-21 13:27:54 -08:00
Joshua Bell
1260999b6c Add SELECTOR.SYSTEM capturing the behavior of ProDOS 1.x. Fixes #5
Mostly just for historical interest.
2020-12-21 10:50:54 -08:00
Joshua Bell
a42dec7f3f
Update README.md
Correct which version added Bird's Better Bye (not sure if 1.8 or 1.9 tho)
2020-12-20 19:48:39 -08:00
Joshua Bell
4d24855217
Update README.md 2020-12-20 19:42:47 -08:00
Joshua Bell
4975081bb6 Streamline package script 2020-03-12 20:45:52 -07:00
Joshua Bell
3b7b2c0b9c Add package script 2020-03-12 20:15:18 -07:00
Joshua Bell
ac4fccd420 BBB: Restore accidentally removed line. Fixes #6 2019-12-31 00:16:47 -08:00
Joshua Bell
d9e90c0fe6 Releases shouldn't go in the tree 2019-10-04 18:31:17 -07:00
Joshua Bell
095f87813e BYE.SYSTEM: Chain rather than QUITing immediately. Fixes #4 2019-10-04 18:29:35 -07:00
Joshua Bell
cf891e061f Makefile tidying, unify headers 2019-10-04 18:10:01 -07:00
Joshua Bell
d10429b3a0 Split out quit.system 2019-10-03 21:55:36 -07:00
Joshua Bell
0f41276584 Combine preamble files, .gitignore, and split out ns.clock 2019-10-03 21:49:53 -07:00
Joshua Bell
cd2074c864 Add Travis-CI config, add README 2019-10-03 21:36:01 -07:00
Joshua Bell
0626869e27 Unify inc/ directories 2019-10-03 21:23:00 -07:00
Joshua Bell
78e1d2d2e6 Add master Makefile 2019-10-03 21:19:52 -07:00
Joshua Bell
8a242abc17 Remove dummy file 2019-10-03 21:17:43 -07:00
Joshua Bell
e34e7a9728 Merge a2stuff/ram.drv.system, move to subdir 2019-10-03 21:17:31 -07:00
Joshua Bell
eea2f6c343 Merge remote-tracking branch 'ram.drv.system/master' 2019-10-03 21:16:40 -07:00
Joshua Bell
f31f6775e9 Merge a2stuff/bbb, move to subdir 2019-10-03 21:15:56 -07:00
Joshua Bell
ec94293ec9 Merge remote-tracking branch 'bbb/master' 2019-10-03 21:15:22 -07:00
Joshua Bell
ffa1495a11 Merge a2stuff/cricket, move to subdir 2019-10-03 21:14:53 -07:00
Joshua Bell
2fab1e19bc Merge remote-tracking branch 'cricket/master' 2019-10-03 21:13:20 -07:00
Joshua Bell
771376f312 dummy commit 2019-10-03 21:13:11 -07:00
Joshua Bell
d049ae6dd8 Don't pollute stack 2019-10-01 22:27:18 -07:00
Joshua Bell
0aaf557aab Don't pollute stack; swap ROM in before COUTing 2019-10-01 22:27:02 -07:00
Joshua Bell
db977835ac Use common driver chain logic 2019-10-01 22:08:21 -07:00
Joshua Bell
2769d1d7a6 Add Travis-CI build status indicator 2019-10-01 21:17:42 -07:00
Joshua Bell
eb3ca7e7e2 Update README link 2019-10-01 21:15:49 -07:00
Joshua Bell
cfb8cab3b2 Add QUIT.SYSTEM 2019-10-01 21:14:34 -07:00
Joshua Bell
c7df8cb73b
Add Travis build status 2019-09-30 21:02:54 -07:00
Joshua Bell
850b10059d Add missing files 2019-09-30 20:57:45 -07:00
Joshua Bell
783c2af304 Clean up headers 2019-09-30 20:34:26 -07:00
Joshua Bell
7cc70cd729 Use common driver chain logic 2019-09-30 20:34:17 -07:00
Joshua Bell
ba0c5e53d3 Fix relocation 2019-09-30 20:20:59 -07:00
Joshua Bell
64c8ff73e3 Extract common logic for setup/chaining 2019-09-29 18:27:00 -07:00
Joshua Bell
fbddc217a6 Pull out system reset logic 2019-09-29 12:52:51 -07:00
Joshua Bell
e65fdfe1b5 Version stamps - don't zero pad 2019-09-29 12:15:37 -07:00
Joshua Bell
9dc6a7a149 Make NS.CLOCK.SYSTEM chain using I/O. Fix Cricket 2019-09-29 11:23:34 -07:00
Joshua Bell
7af95f7162 WIP: Chain using file I/O 2019-09-28 20:54:23 -07:00
Joshua Bell
291d0f12f2 Switch to semi-common include files 2019-09-28 20:14:53 -07:00
Joshua Bell
06cbcd4fa9 Restore SSC flags after detection. Fixes #8 2019-09-28 09:41:16 -07:00
Joshua Bell
16599fc4b9 Build: Use cc65's make avail 2019-09-22 21:34:47 -07:00
Joshua Bell
754d267c1e Build: Use cc65's make avail 2019-09-22 21:33:54 -07:00
Joshua Bell
3c9c491bae Build: Use cc65's make avail 2019-09-22 21:32:41 -07:00
Joshua Bell
b5b5b96d2a
Update Travis build path 2019-09-22 21:30:24 -07:00
Joshua Bell
2dfb4410a9 Update README with branch details 2019-06-21 16:48:07 -07:00
Joshua Bell
f380680cdd Use file I/O rather than block I/O to read dir 2019-06-21 15:25:11 -07:00
Joshua Bell
d4466231f5 Save name earlier 2019-06-19 20:39:54 -07:00
Joshua Bell
9b98a09067 rearrange config/data 2019-06-19 20:21:10 -07:00
Joshua Bell
88472a8e11 Rearrange so driver is a subroutine 2019-06-19 20:12:18 -07:00
Joshua Bell
903cd4ff1e Fix chaining 2019-06-18 22:10:49 -07:00
Joshua Bell
d115882280 Chain to next SYSTEM file. Resolves #1 2019-06-17 20:46:32 -07:00
Joshua Bell
6edbfa48ac Rename to match original 2019-06-17 20:45:00 -07:00
Joshua Bell
9d424a4e01 Add pushorg/poporg macros 2019-06-14 21:23:24 -07:00
Joshua Bell
4d4a164e30 Signature bytes 2019-06-14 21:00:53 -07:00
Joshua Bell
a2bc49a38b Add README 2019-02-24 20:05:42 -08:00
Joshua Bell
fbd79badee initial snapshot 2019-02-24 20:03:51 -08:00
Joshua Bell
feaac6d4fc DATE: Mask off hour/minute in case of extra bits 2018-08-05 11:09:49 -07:00
Joshua Bell
f6e359cfb7
Update Makefile 2018-04-29 21:45:53 -07:00
Joshua Bell
e974c76ff7 Build into out/ subdir 2018-04-29 20:04:13 -07:00
Joshua Bell
23af1ff081
Add travis build status 2018-04-10 20:58:20 -07:00
Joshua Bell
5aae9d79bc travis debugging 2018-04-10 20:53:42 -07:00
Joshua Bell
e878aec51a travis debugging 2018-04-10 20:51:44 -07:00
Joshua Bell
3ee2f9ef3f add experimental travis-ci config 2018-04-10 20:49:01 -07:00
Joshua Bell
3a29c86c4b set auxtype 2018-04-10 20:38:45 -07:00
Joshua Bell
320edcb634 adjust credits 2017-12-13 08:49:43 -08:00
Joshua Bell
b064242a7a Consolidate installer 2017-12-12 20:56:51 -08:00
Joshua Bell
c11c49a03b Fix typo in title 2017-12-12 20:41:06 -08:00
Joshua Bell
37597de535 Add readme 2017-12-12 20:40:31 -08:00
Joshua Bell
c0c9cc01dd Shave off a few bytes by making alpha default 2017-12-12 15:55:32 -08:00
Joshua Bell
fa0bfcd936 comment 2017-12-12 14:55:23 -08:00
Joshua Bell
4cc70803fd Tweak installer 2017-12-12 14:54:27 -08:00
Joshua Bell
f0ceec0aea Reorder ZP uses 2017-12-12 10:58:22 -08:00
Joshua Bell
1acc9e5946 Clean up comments 2017-12-12 10:52:01 -08:00
Joshua Bell
72b8859222 works 2017-12-12 05:17:39 -08:00
Joshua Bell
5e28639a4e alpha works, but esc crashes 2017-12-12 05:00:29 -08:00
Joshua Bell
b199b21f77 works 2017-12-12 04:45:28 -08:00
Joshua Bell
d74dec19b2 WIP variant 2017-12-11 20:15:51 -08:00
Joshua Bell
6eb9613a28 More tidying, size assert 2017-12-10 20:22:34 -08:00
Joshua Bell
5e3d46373e Stamp SYS files with AuxType 2017-12-09 16:50:35 -08:00
Joshua Bell
6df7941617 Rename to bbb 2017-12-09 16:46:45 -08:00
Joshua Bell
b7074e04f7 Move prodos defs to inc 2017-12-09 16:29:06 -08:00
Joshua Bell
6a0a6c5ad9 Finish labels 2017-12-09 16:17:29 -08:00
Joshua Bell
ece57e671c read routines 2017-12-09 16:06:31 -08:00
Joshua Bell
589bc09605 more labels 2017-12-09 14:49:16 -08:00
Joshua Bell
714c796605 open and prefix 2017-12-09 14:28:54 -08:00
Joshua Bell
dd6a4b319e prodos global page, devices 2017-12-09 14:11:29 -08:00
Joshua Bell
1d974dcf34 Make padding calc dynamic 2017-12-09 13:11:36 -08:00
Joshua Bell
00a2275346 more ids 2017-12-09 13:00:27 -08:00
Joshua Bell
ba96487609 more identifications 2017-12-09 09:29:35 -08:00
Joshua Bell
7042ad9901 Identify states 2017-12-09 09:03:44 -08:00
Joshua Bell
e6a02b099c Comments for utilities 2017-12-08 20:51:54 -08:00
Joshua Bell
b6d7b3068a
Update README.md 2017-12-07 23:47:45 -08:00
Joshua Bell
106b31b899 Standardize macro usage 2017-12-07 21:48:43 -08:00
Joshua Bell
89edcb917b
Update README.md 2017-12-07 21:47:50 -08:00
Joshua Bell
b204ea7f2f Rename GET.TIME to DATE, single line 2017-12-07 20:49:53 -08:00
Joshua Bell
82a8ade241 Add set.date/set.time helpers for Cricket 2017-12-07 19:54:55 -08:00
Joshua Bell
987092c318 disk image 2017-12-03 17:29:11 -08:00
Joshua Bell
e13d9f0e60 Remove dead comment 2017-11-23 08:31:12 -08:00
Joshua Bell
2db6f978e4 Identifying data blocks 2017-11-13 07:57:57 -08:00
Joshua Bell
8817199aa7 Initial snapshot - mid-disassembly 2017-11-11 20:44:15 -08:00
75 changed files with 9324 additions and 1684 deletions

31
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,31 @@
name: build
on:
push:
branches: [ main ]
tags: ['*']
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: a2stuff/build-install-ca65-action@v2
- uses: a2stuff/build-install-cadius-action@v1
- name: build
env:
TERM: xterm-256color
run: >
make && make package
- name: deploy new version
if: startsWith(github.ref, 'refs/tags/')
uses: ncipollo/release-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
artifacts: "prodos-drivers.po"

17
.gitignore vendored
View File

@ -1,7 +1,16 @@
# Generated files
*.BIN
*.SYS
*.list
# Reference copy of original binary
orig
# Output files
*.SYS
*.BIN
*.list
# Build directory
out
# Directory mounted in Virtual ][ as a ProDOS volume
mount
# OS-specific files
.DS_Store

View File

@ -1,27 +1,25 @@
targets := clocks selectors accelerators ram.drv setup util textcolors
CC65 = ~/dev/cc65/bin
CAFLAGS = --target apple2enh --list-bytes 0
CCFLAGS = --config apple2-asm.cfg
.PHONY: all $(targets) package
TARGETS = prodos.mod.BIN ns.clock.system.SYS cricket.system.SYS test.BIN get.time.BIN
all: $(targets)
# For timestamps
MM = $(shell date "+%m")
DD = $(shell date "+%d")
YY = $(shell date "+%y")
DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY)
export LOG_SUCCESS=1
export LOG_FAILURE=1
.PHONY: clean all
all: $(TARGETS)
HEADERS = $(wildcard *.inc)
# Build all targets
$(targets):
@tput setaf 3 && echo "Building: $@" && tput sgr0
@$(MAKE) -C $@ \
&& (tput setaf 2 && echo "make $@ good" && tput sgr0) \
|| (tput blink && tput setaf 1 && echo "MAKE $@ BAD" && tput sgr0 && false)
# Clean all temporary/target files
clean:
rm -f *.o
rm -f $(TARGETS)
@for dir in $(targets); do \
tput setaf 2 && echo "cleaning $$dir" && tput sgr0; \
$(MAKE) -C $$dir clean; \
done
%.o: %.s $(HEADERS)
$(CC65)/ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $<
%.BIN %.SYS: %.o
$(CC65)/ld65 $(CCFLAGS) -o $@ $<
package:
./package.sh

124
README.md
View File

@ -1,48 +1,112 @@
# The Cricket! - ProDOS Clock Driver
# ProDOS Drivers
I acquired a Cricket sound/clock peripheral on eBay. Therefore it is now critical that we have a conforming ProDOS clock driver for it.
[![build](https://github.com/a2stuff/prodos-drivers/actions/workflows/main.yml/badge.svg)](https://github.com/a2stuff/prodos-drivers/actions/workflows/main.yml)
> STATUS: Works on my machine!
Build with [ca65](https://cc65.github.io/doc/ca65.html)
## Background
# What are ProDOS "drivers"?
"The Cricket!" by Street Electronics Corporation, released in 1984, is a hardware peripheral for the Apple //c computer. It plugs into the serial port and offers a multi-voice sound synthesizer, a speech synthesizer, and a real-time clock.
The ProDOS operating system for the Apple II executes the first `.SYSTEM` file found in the boot directory on startup. A common pattern is to have the boot directory contain several "driver" files that customize ProDOS by installing drivers for hardware or modify specific parts of the operating system. These include:
The disks supplied with the device include:
* `/CRICKET/PRODOS.MOD` which can be BRUN to patch ProDOS in memory with a clock driver.
* A modified version of ProDOS
* A utility to patch ProDOS on disk
* Real-time Clock drivers (e.g. No-Slot Clock, Cricket!, AE DClock, etc)
* In ProDOS 1.x, 2.0 and 2.4 the Thunderclock driver is built-in.
* RAM Disk drivers (e.g. RamWorks)
* In ProDOS 1.x, 2.0 and 2.4 only a 64K driver for /RAM is built-in.
* Quit dispatcher/selector (`BYE` routines)
* In ProDOS 1.0 and later, a 40-column friendly [selector](selector) prompts for a prefix then a path `ENTER PREFIX (PRESS "RETURN" TO ACCEPT)`
* In ProDOS 1.9 and 2.0.x, on 80-column systems, a menu-driven selector is installed instead.
* In ProDOS 2.4.x [Bitsy Bye](https://prodos8.com/bitsy-bye/) is built-in.
## Goals
Early versions of these drivers would often invoke a specific file on completion, sometimes user-configurable. The best versions of these drivers simply execute the following `.SYSTEM` file, although this is non-trivial code and often did not work with network drives.
Like the `NS.CLOCK.SYSTEM` (by "CAP") ideally we would have:
This repository collects several drivers and uses common code to chain to the next `.SYSTEM` file, suporting network drives.
* [x] A ProDOS `.SYSTEM` file
* [x] Detects the presence of a Cricket
* [x] Installs a driver in memory following the ProDOS clock driver protocol
* [x] Chains to the next `.SYSTEM` file (e.g. `BASIC.SYSTEM`)
## What is present here?
Successfully tested on real hardware. (Laser 128EX, an Apple //c clone &mdash; including at 3x speed!)
This repo includes the following drivers/modifications:
## Build
* Real-time Clock drivers
* No-Slot Clock
* Cricket!
* Applied Engineering DClock
* ROMX Real-Time Clock
* FujiNet Clock
* A "jumbo" driver that includes all of the above (just called `CLOCK.SYSTEM`)
* Accelerators
* ZIP CHIP configuration (sets the chip to slow on speaker access)
* RAM Disk drivers
* RAMWorks Driver by Glen E. Bredon
* Quit dispatcher/selector (`BYE` routines)
* 40-column Selector (from ProDOS)
* 80-column menu-driven Selector (from ProDOS 1.9 and 2.x)
* Bird's Better Bye (a 40-column menu-driven selector)
* Buh-Bye (an enhanced version of the ProDOS 80-column, menu-driven selector)
* Text color themes
* These set the IIgs (or VidHD) text/background/border colors
Requires [cc65](https://github.com/cc65/cc65). The included `Makefile` is very specific to my machine - sorry about that.
In addition, `QUIT.SYSTEM` is present which isn't a driver but which immediately invokes the QUIT handler (a.k.a. program selector). This will happen automatically if the last driver can't find another `.SYSTEM` file, but `QUIT.SYSTEM` can be used to stop the chain.
[CRICKET.SYSTEM](cricket.system.s) is the result of the build.
There's also `PAUSE.SYSTEM` which just waits for a fraction of a second before invoking the next driver file. (Why? In case the log messages from the other installers goes by too fast!)
## Notes
Non-drivers that are included:
* The `DATE` binary file can be `BRUN` (or just `-DATE`) to show the current ProDOS Date/Time, to verify that the clock driver is working.
* Some utilities for The Cricket! clock are also included.
I ended up disassembling both [NS.CLOCK.SYSTEM](ns.clock.system.s) (to understand the SYSTEM chaining - what a pain!) and The Cricket!'s [PRODOS.MOD](prodos.mod.s) and melding them together, adding in the detection routine following the protocol in the manual.
## How do you use these?
Other files:
* [GET.TIME](get.time.s) just prints the current ProDOS date/time, to verify the time is set and updating.
* [TEST](test.s) attempts to identify an SSC in Slot 2 and the Cricket via the ID sequence, to test routines.
The intent is that you use a tool like Copy II Plus or [Apple II DeskTop](https://github.com/a2stuff/a2d) to copy and arrange the SYSTEM files on your boot disk as you see fit. A boot disk image catalog that is used on multiple different hardware configurations might include:
## Resources
* `PRODOS` - the operating system, e.g. [ProDOS 2.4](https://prodos8.com/)
* `NS.CLOCK.SYSTEM` - install No-Slot clock driver, if present
* `ROMXRTC.SYSTEM` - install ROMX clock driver, if present
* `FN.CLOCK.SYSTEM` - install FujiNet clock driver, if present
* `DCLOCK.SYSTEM` - install DClock clock driver, if present
* `CRICKET.SYSTEM` - install Cricket! clock driver, if present
* `ZIPCHIP.SYSTEM` - slow the ZIP CHIP on speaker access, if present
* `RAM.DRV.SYSTEM` - install RamWorks RAM disk driver, if present
* `BUHBYE.SYSTEM` - install a customized Quit handler to replace the built-in one
* `PAUSE.SYSTEM` - pause for a moment, so that you can inspect the output of the above
* `QUIT.SYSTEM` - invoke the Quit handler immediately, as a program selector
* `BASIC.SYSTEM` - which will not be automatically invoked, but is available to manually invoke
Cricket disks on Asimov:
* ftp://ftp.apple.asimov.net/pub/apple_II/images/hardware/sound/cricket_disk1.po
* ftp://ftp.apple.asimov.net/pub/apple_II/images/hardware/sound/cricket_disk2.po
Alternately, you might want to install some drivers then immediately launch into BASIC. In that case, put `BASIC.SYSTEM` after the drivers in place of `QUIT.SYSTEM`.
Cricket Manual on Asimov:
* ftp://ftp.apple.asimov.net/pub/apple_II/documentation/hardware/sound/Street%20Electronics%20The%20Cricket.pdf
## Alternate Approach
If you want to keep your volume directory tidier, consider using [SETUP.SYSTEM](./setup/README.md) instead.
# Building
Fetch, build, and install [cc65](http://cc65.github.io/cc65/):
```
git clone https://github.com/cc65/cc65
make -C cc65 && make -C cc65 avail
```
Fetch and build this repo:
```
git clone https://github.com/a2stuff/prodos-drivers
cd prodos-drivers
make
```
To make a disk image, fetch, build and install [Cadius](https://github.com/mach-kernel/cadius):
```
git clone https://github.com/mach-kernel/cadius
make -C cadius && make -C cadius install
```
Then you can:
```
cd prodos-drivers
make && make package
```
This will produce `prodos-drivers.po`, a disk image for use with emulators or tools like [ADTPro](http://adtpro.com/).
Notes:
* Specify `LOG_SUCCESS=0` and/or `LOG_FAILURE=0` (e.g. `make LOG_SUCCESS=0`) to build with driver success and/or error logging suppressed.

22
accelerators/Makefile Normal file
View File

@ -0,0 +1,22 @@
targets := zipchip
.PHONY: all $(targets)
all: $(targets)
export LOG_SUCCESS
export LOG_FAILURE
# Build all targets
$(targets):
@tput setaf 3 && echo "Building: $@" && tput sgr0
@$(MAKE) -C $@ \
&& (tput setaf 2 && echo "make $@ good" && tput sgr0) \
|| (tput blink && tput setaf 1 && echo "MAKE $@ BAD" && tput sgr0 && false)
# Clean all temporary/target files
clean:
@for dir in $(targets); do \
tput setaf 2 && echo "cleaning $$dir" && tput sgr0; \
$(MAKE) -C $$dir clean; \
done

View File

@ -0,0 +1,46 @@
CAFLAGS = --target apple2enh --list-bytes 0
LDFLAGS = --config apple2-asm.cfg
OUTDIR = ../../out
HEADERS = $(wildcard *.inc) $(wildcard ../inc/*.inc)
TARGETS = \
$(OUTDIR)/zipchip.system.SYS \
$(OUTDIR)/zipchip.setup.SYS
LOG_SUCCESS=1
LOG_FAILURE=1
# For timestamps
MM = $(shell date "+%-m")
DD = $(shell date "+%-d")
YY = $(shell date "+%-y")
DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY) \
-D LOG_SUCCESS=$(LOG_SUCCESS) -D LOG_FAILURE=$(LOG_FAILURE)
XATTR := $(shell command -v xattr 2> /dev/null)
.PHONY: clean all
all: $(OUTDIR) $(TARGETS)
$(OUTDIR):
mkdir -p $(OUTDIR)
clean:
rm -f $(OUTDIR)/*.o
rm -f $(OUTDIR)/*.list
rm -f $(TARGETS)
$(OUTDIR)/%.o: %.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.setup.o: %.system.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) -D BUILD_SETUP_FILE --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.BIN $(OUTDIR)/%.SYS: $(OUTDIR)/%.o
ld65 $(LDFLAGS) -o $@ $<
ifdef XATTR
xattr -wx prodos.AuxType '00 20' $@
endif

View File

@ -0,0 +1,179 @@
;;; ZIPCHIP.SYSTEM
;;; Configures the speaker to be temporarily slow
;;; TODO: Add a configuration utility
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "opcodes.inc"
.include "../../inc/apple2.inc"
.include "../../inc/macros.inc"
.include "../../inc/prodos.inc"
.include "../../inc/ascii.inc"
;;; ************************************************************
.include "../../inc/driver_preamble.inc"
;;; ************************************************************
;; From ZIP Chip Manual v1 1987
ZC_REG_LOCK := $C05A
;; Write:
;; $A5 Locks the ZIP CHIP.
;; 4 consecutive $5A writes unlock ZIP CHIP.
;; While unlocked, any write other than $A5 or
;; $5A will initiate an indefinate syncronous [sic]
;; sequence.
kZCLock = $A5
kZCUnlock = $5A
ZC_REG_ENABLE := $C05B
;; Write - Any hex byte written will enable ZIP CHIP
ZC_REG_STATUS := $C05B
;; Read - Read the current status of the following:
;; bit 0 & 1 - Ramsize where
;; RAMSIZE1 RAMSIZE0 SIZE
;; 0 0 8K
;; 0 1 16K
;; 1 0 32K
;; 1 1 64K
;; bit 2 - unused
;; bit 3 - Delay (for memory)
;; 0 = Fast Mode - Delay not in effect
;; 1 = Sync Mode - Delay in effect
;; bit 4 - Disabled/enabled
;; 0 = Chip Enabled
;; 1 = Chip Disabled
;; bit 5 - Paddle fast/normal
;; 0 = Fast Mode
;; 1 = Synchronous Mode (Normal)
;; bit 6 = Cache Updated by data read
;; 0 = No update
;; 1 = Yes cache updated
;; bit 7 = Clock Pulse - 1.0035 milliseconds
;; Edges occur at .50175 milliseconds
ZC_REG_SLOTSPKR := $C05C
;; Read/Write - Slow/Speaker set and read
;; 0 = Set slot/speaker Fast
;; 1 = Set slot/speaker Normal
;; bit 0 - Speaker bit 4 - Slot 4
;; bit 1 - Slot 1 bit 5 - Slot 5
;; bit 2 - Slot 2 bit 6 - Slot 6
;; bit 3 - Slot 3 bit 7 - Slot 7
ZC_REG_SYSSPEED := $C05D
;; Write - Set System Speed
;; bit 0 - unused bit 4 - Clk4/5
;; bit 1 - unused bit 5 - Clk5/6
;; bit 2 - Clk2/3 bit 6 - Clk/2
;; bit 3 - Clk3/4 bit 7 - Clk/4
;; NOTE: bit 6 and bit 7 yield Clk/3
ZC_REG_SYNCOP := $C05E
;; Write - Enable/Disable Synchronous Operation
;; for I/O Devices
;; bit 0 through bit 6 - Not Used
;; bit 7 - Enable/Disable Delay
;; 0 = Enable Delay
;; 1 = Disable and Reset Delay
ZC_REG_SOFTSW := $C05E
;; Read - Read Apple softswitches
;; 0 = False
;; 1 = True
;; bit 0 - ROMRD bit 4 - 80STORE
;; bit 1 - RAMBNK bit 5 - MWR
;; bit 2 - PAGE2 bit 6 - MRD
;; bit 3 - HIRES bit 7 - ALTZP
ZC_REG_PDLBSLC := $C05F
;; Write - Paddle Speed, Bank Switch Language Card
;; bit 0 through bit 5 - Not Used
;; bit 6 - Paddle Set
;; 0 = Disable Paddle Delay
;; 1 = Enable Paddle Delay
;; bit 7 - Language Card Enable/Disable
;; 0 = Enable Cache of Language Card Memory
;; 1 = Disable Cache of Language Card Memory
.undef PRODUCT
.define PRODUCT "ZIP CHIP"
.proc maybe_install_driver
php ; timing sensitive
sei
;; Unlock
lda #kZCUnlock
sta ZC_REG_LOCK
sta ZC_REG_LOCK
sta ZC_REG_LOCK
sta ZC_REG_LOCK
;; ZIP CHIP present?
lda ZC_REG_SLOTSPKR
eor #$FF
sta ZC_REG_SLOTSPKR
cmp ZC_REG_SLOTSPKR
bne no_zip
eor #$FF
sta ZC_REG_SLOTSPKR
cmp ZC_REG_SLOTSPKR
bne no_zip
;; Slow on speaker access
lda ZC_REG_SLOTSPKR
ora #%00000001 ; bit 0 = Speaker
sta ZC_REG_SLOTSPKR
;; Get size
lda ZC_REG_STATUS
and #%00000011
asl
tax
lda size_table,x
sta size
lda size_table+1,x
sta size+1
;; Lock
lda #kZCLock
sta ZC_REG_LOCK
jsr log_message
scrcode PRODUCT, " "
size: .res 2 ; patched with cache size
scrcode "K - Configured."
.byte 0
plp
rts
no_zip:
jsr log_message
scrcode PRODUCT, " - Not Found."
.byte 0
plp
rts
size_table:
scrcode " 8"
scrcode "16"
scrcode "32"
scrcode "64"
.endproc
;;; ************************************************************
.include "../../inc/driver_postamble.inc"
;;; ************************************************************

22
clocks/Makefile Normal file
View File

@ -0,0 +1,22 @@
targets := ns.clock cricket dclock romx fujinet jumbo
.PHONY: all $(targets)
all: $(targets)
export LOG_SUCCESS
export LOG_FAILURE
# Build all targets
$(targets):
@tput setaf 3 && echo "Building: $@" && tput sgr0
@$(MAKE) -C $@ \
&& (tput setaf 2 && echo "make $@ good" && tput sgr0) \
|| (tput blink && tput setaf 1 && echo "MAKE $@ BAD" && tput sgr0 && false)
# Clean all temporary/target files
clean:
@for dir in $(targets); do \
tput setaf 2 && echo "cleaning $$dir" && tput sgr0; \
$(MAKE) -C $$dir clean; \
done

34
clocks/README.md Normal file
View File

@ -0,0 +1,34 @@
# ProDOS Clock Drivers
Other than the Apple IIgs model, the Apple II did not ship with a real-time system clock. Many third party [Apple II system clocks](https://en.wikipedia.org/wiki/Apple_II_system_clocks) were produced in the 1980s, and many new system clocks are available, either as entirely new designs or recreations.
The ProDOS operating system for the Apple II personal computer line natively supports the Thunderclock real-time clock card, but there is a protocol for custom clock drivers to be installed:
* Check `MACHID` bit 0 to see if a clock is already active; abort if so.
* Optional: Probe hardware to determine if the clock is present; abort if not.
* Relocate the clock driver to LC bank 1, at the address at `DATETIME`+1
* Update `DATETIME` to be a `JMP` instruction.
* Optional: Chain to the next `.SYSTEM` file.
In addition:
* The clock driver must fit into 125 bytes.
* The driver may dirty $200-$207 but other memory must be restored if modified.
* When invoked, the clock driver should read the clock hardware and encode the date and time into `DATELO`/`DATEHI` and `TIMELO`/`TIMEHI`.
* ProDOS calls the clock driver when `GET_TIME` is called, and on every call (`CREATE`, `RENAME`, etc) that might need the date and time.
See https://prodos8.com/docs/techref/adding-routines-to-prodos/ for more information.
## Included drivers
This directory includes drivers for the following real-time clocks:
* [No-Slot Clock](https://www.reactivemicro.com/product/no-slot-clock/)
* [ROMX Real-Time Clock](https://theromexchange.com/)
* [FujiNet Clock](https://fujinet.online/)
* DClock
* Cricket!
All follow the above protocol: install only if there is not already a clock, probe for the clock before installing, and chain to the next driver.
In addition, the "jumbo" directory combines the other driver installers to create a single unified clock driver installer. See its [README](jumbo/README.md) for more details.

57
clocks/cricket/Makefile Normal file
View File

@ -0,0 +1,57 @@
CAFLAGS = --target apple2enh --list-bytes 0
LDFLAGS = --config apple2-asm.cfg
OUTDIR = ../../out
UTILDIR = $(OUTDIR)/cricket.util
HEADERS = $(wildcard *.inc) $(wildcard ../../inc/*.inc)
TARGETS = \
$(UTILDIR)/prodos.mod.BIN \
$(OUTDIR)/cricket.system.SYS \
$(OUTDIR)/cricket.setup.SYS \
$(UTILDIR)/test.BIN \
$(UTILDIR)/set.datetime.BIN \
$(UTILDIR)/set.time.BIN \
$(UTILDIR)/set.date.BIN
LOG_SUCCESS=1
LOG_FAILURE=1
# For timestamps
MM = $(shell date "+%-m")
DD = $(shell date "+%-d")
YY = $(shell date "+%-y")
DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY) \
-D LOG_SUCCESS=$(LOG_SUCCESS) -D LOG_FAILURE=$(LOG_FAILURE)
XATTR := $(shell command -v xattr 2> /dev/null)
.PHONY: clean all
all: $(OUTDIR) $(UTILDIR) $(TARGETS)
$(OUTDIR) $(UTILDIR):
mkdir -p $@
clean:
rm -f $(OUTDIR)/*.o
rm -f $(OUTDIR)/*.list
rm -f $(UTILDIR)/*.o
rm -f $(UTILDIR)/*.list
rm -f $(TARGETS)
$(OUTDIR)/%.o $(UTILDIR)/%.o: %.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.setup.o: %.system.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) -D BUILD_SETUP_FILE --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.SYS: $(OUTDIR)/%.o
ld65 $(LDFLAGS) -o $@ $<
$(UTILDIR)/%.BIN: $(UTILDIR)/%.o
ld65 $(LDFLAGS) -o $@ $<
ifdef XATTR
xattr -wx prodos.AuxType '00 20' $@
endif

58
clocks/cricket/README.md Normal file
View File

@ -0,0 +1,58 @@
# _The Cricket!_ &mdash; ProDOS Clock Driver
I acquired a Cricket sound/clock peripheral on eBay. Therefore it is now critical that we have a conforming ProDOS clock driver for it.
> STATUS: Works on my machine!
## Background
_"The Cricket!"_ by Street Electronics Corporation, released in 1984, is a hardware peripheral for the Apple //c computer. It plugs into the serial port and offers a multi-voice sound synthesizer, a speech synthesizer, and a real-time clock.
The disks supplied with the device include:
* `/CRICKET/PRODOS.MOD` which can be BRUN to patch ProDOS in memory with a clock driver.
* A modified version of ProDOS
* A utility to patch ProDOS on disk
## `CRICKET.SYSTEM`
Like the `NS.CLOCK.SYSTEM` (by "CAP"), `CRICKET.SYSTEM` has these features:
* [x] A ProDOS `.SYSTEM` file
* [x] Detects the presence of a Cricket
* [x] Installs a driver in memory following the ProDOS clock driver protocol
* [x] Chains to the next `.SYSTEM` file (e.g. `BASIC.SYSTEM`)
Successfully tested on real hardware. (Laser 128EX, an Apple //c clone &mdash; including at 3x speed!)
## Build
Requires [cc65](https://github.com/cc65/cc65). The included `Makefile` is very specific to my machine - sorry about that.
[CRICKET.SYSTEM](cricket.system.s) is the result of the build.
## Notes
I ended up disassembling both [NS.CLOCK.SYSTEM](../ns.clock/ns.clock.system.s) (to understand the SYSTEM chaining - what a pain!) and The Cricket!'s [PRODOS.MOD](prodos.mod.s) and melding them together, adding in the detection routine following the protocol in the manual.
## Other Utilities
These `BRUN`able files are also built:
* [TEST](test.s) attempts to identify an SSC in Slot 2 and the Cricket via the ID sequence, to test routines.
* [SET.DATETIME](set.datetime.s) sets the Cricket's current date and time.
* [SET.DATE](set.date.s) sets the Cricket's current date.
* [SET.TIME](set.time.s) sets the Cricket's current time.
`CRICKET.SETUP` is for use with [SETUP.SYSTEM](../../setup/README.md)
## Resources
Cricket disks on Asimov:
* ftp://ftp.apple.asimov.net/pub/apple_II/images/hardware/sound/cricket_disk1.po
* ftp://ftp.apple.asimov.net/pub/apple_II/images/hardware/sound/cricket_disk2.po
Cricket Manual on Asimov:
* ftp://ftp.apple.asimov.net/pub/apple_II/documentation/hardware/sound/Street%20Electronics%20The%20Cricket.pdf
## FYI...
In the Cricket manual there is a short sample BASIC program to set the clock without the use of any assembly routines, using `PR#2` to talk to the serial card followed by `PRINT` statements. This does not appear to work from ProDOS with the clock driver in place - the time ends up temporarily scrambled until the clock sorts itself out again. Assembly language routines do work, however. I believe ProDOS attempts to read the clock during the execution of the basic program, which interferes the device.

View File

@ -0,0 +1,375 @@
;;; The Cricket Clock - ProDOS System
;;; Adapted from /CRICKET/PRODOS.MOD
;;; Original: Street Electronics Corporation (C) 1984
;;; Adapted from: /NO.SLOT.CLOCK/NS.CLOCK.SYSTEM
;;; Original by "CAP" 04/21/91
;;; http://www.apple2.org.za/gswv/a2zine/GS.WorldView/v1999/Oct/MISC/NSC.Disk.TXT
.ifndef JUMBO_CLOCK_DRIVER
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "opcodes.inc"
.include "../../inc/apple2.inc"
.include "../../inc/macros.inc"
.include "../../inc/prodos.inc"
.include "../../inc/ascii.inc"
.endif ; JUMBO_CLOCK_DRIVER
;;; ************************************************************
.ifndef JUMBO_CLOCK_DRIVER
.include "../../inc/driver_preamble.inc"
.endif ; JUMBO_CLOCK_DRIVER
;;; ************************************************************
;;; ============================================================
;;;
;;; Driver Installer
;;;
;;; ============================================================
read_delay_hi = $3 * 3 ; ($300 iterations is normal * 3.6MHz)
.undef PRODUCT
.define PRODUCT "Cricket Clock"
;;; ============================================================
;;; Ensure there is not a previous clock driver installed.
.proc maybe_install_driver
lda MACHID
and #$01 ; existing clock card?
beq detect_cricket ; nope, check for Cricket
rts ; yes, done!
.endproc
;;; ------------------------------------------------------------
;;; Detect Cricket. Detect SSC and if present probe device.
.proc detect_cricket
;; Check Slot 2 for SSC. ID bytes per:
;; Apple II Technical Note #8: Pascal 1.1 Firmware Protocol ID Bytes
lda $C205
cmp #$38
bne ssc_not_found
lda $C207
cmp #$18
bne ssc_not_found
lda $C20B
cmp #$01
bne ssc_not_found
lda $C20C
cmp #$31
bne ssc_not_found
beq init_ssc
ssc_not_found:
jmp not_found
;; Init SSC and try the "Read Cricket ID code" sequence.
init_ssc:
php
sei
lda COMMAND ; save status of SSC registers
sta saved_command
lda CONTROL
sta saved_control
;; Reset the Cricket (stops any playing notes & ensures Cricket is listening)
jsr reset_cricket
jsr reset_cricket ; does it twice in original Cricket driver
;; Read Cricket ID code: 00 ($00)
lda #0
jsr sendbyte
bcs cricket_not_found ; timeout
;; "The Cricket will return a "C" (195, $C3) followed by a
;; version number (in ASCII) and a carriage return (141, $8D)."
jsr readbyte
bcs cricket_not_found ; timeout
cmp #HI('C') ; = 'C' ?
bne cricket_not_found
jsr readbyte
bcs cricket_not_found ; timeout
bcc digit
: jsr readbyte
bcs cricket_not_found ; timeout
cmp #HI(ASCII_CR)
beq cricket_found
digit: cmp #HI('0') ; < '0' ?
bcc cricket_not_found
cmp #HI('9' + 1) ; > '9' ?
bcs cricket_not_found
bcc :-
cricket_found:
jsr restore_cmd_ctl
plp
jmp install_driver
cricket_not_found:
jsr restore_cmd_ctl
plp
;; fall through...
not_found:
.ifndef JUMBO_CLOCK_DRIVER
.if ::LOG_FAILURE
;; Show failure message
jsr log_message
scrcode PRODUCT, " - Not Found."
.byte 0
.endif ; ::LOG_FAILURE
.endif ; JUMBO_CLOCK_DRIVER
sec ; failure
rts
reset_cricket:
;; Reset SSC
lda #0
sta COMMAND ; hardware reset all Port 2 ACIA registers
sta CONTROL
;; Configure SSC
lda #%00001011 ; no parity/echo/interrupts, RTS low, DTR low
sta COMMAND
lda #%10011110 ; 9600 baud, 8 data bits, 2 stop bits
sta CONTROL
jsr readbyte ; done on original disk
lda #HI(ASCII_CR) ; two carriage returns
jsr sendbyte
lda #HI(ASCII_CR)
jsr sendbyte
lda #HI('!') ; Reset Cricket (everything but time/date)
jsr sendbyte
rts
restore_cmd_ctl:
lda saved_control
sta CONTROL
lda saved_command
sta COMMAND
rts
saved_command: .byte 0
saved_control: .byte 0
.endproc
;; Write byte in A
.proc sendbyte
tries := $100 * read_delay_hi
counter := $A5
pha
lda #<tries
sta counter
lda #>tries
sta counter+1
check: lda STATUS
and #(1 << 4) ; transmit register empty? (bit 4)
bne ready ; yes, ready to write
dec counter
bne check
dec counter+1
bne check
pla
sec ; failed
rts
ready:
pla
sta TDREG
clc
rts
.endproc
;; Read byte into A, or carry set if timed out
.proc readbyte
tries := $100 * read_delay_hi
counter := $A5
lda #<tries
sta counter
lda #>tries
sta counter+1
check: lda STATUS ; did we get it?
and #(1 << 3) ; receive register full? (bit 3)
bne ready ; yes, we read the value
dec counter
bne check
dec counter+1
bne check
sec ; failed
rts
ready: lda RDREG ; actually read the register
clc
rts
.endproc
;;; ------------------------------------------------------------
;;; Install Cricket Driver. Copy into address at DATETIME vector,
;;; update the vector and update MACHID bits to signal a clock
;;; is present.
.proc install_driver
ptr := $A5
lda DATETIME+1
sta ptr
lda DATETIME+2
sta ptr+1
lda RWRAM1
lda RWRAM1
ldy #sizeof_driver-1
loop: lda driver,y
sta (ptr),y
dey
bpl loop
;; Set the "Recognizable Clock Card" bit
lda MACHID
ora #$01
sta MACHID
lda #OPC_JMP_abs
sta DATETIME
;; Invoke the driver to init the time
jsr DATETIME
lda ROMIN2
.if ::LOG_SUCCESS
;; Display success message
jsr log_message
scrcode PRODUCT, " - "
.byte 0
;; Display the current date
jsr cout_date
.endif ; ::LOG_SUCCESS
clc ; success
rts ; done!
.endproc
;;; ============================================================
;;; Cricket Clock Driver - copied into ProDOS
;;; ============================================================
.proc driver
scratch := $3A ; ZP scratch location
;; Initialize
php
sei
lda COMMAND ; save status of command register
pha
;; Configure SSC
lda #%00001011 ; no parity/echo/interrupts, RTS low, DTR low
sta COMMAND
lda #%10011110 ; 9600 baud, 8 data bits, 2 stop bits
sta CONTROL
;; Send command
: lda STATUS
and #(1 << 4) ; transmit register empty? (bit 4)
beq :- ; nope, keep waiting
lda #HI('@') ; '@' command
sta TDREG
read_len := 7 ; read 7 bytes (w/m/d/y/H/M/S)
;; Read response, pushing to stack
ldy #(read_len-1)
rloop: ldx #0 ; x = retry loop counter low byte
lda #read_delay_hi ; scratch = retry loop counter high byte
sta scratch
check: lda STATUS ; did we get it?
and #(1 << 3) ; receive register full? (bit 3)
bne ready ; yes, we read the value
inx ; not yet, so keep trying
bne check ; until counter runs out
dec scratch
bne check
;; Read failed - restore stack and exit
reset: cpy #(read_len-1) ; anything left to restore?
beq done ; nope, exit
pla ; yep, clear it off the stack
iny
bne reset
;; Read succeeded - stuff it on the stack and continue
ready: lda RDREG
pha
dey
bpl rloop
;; Convert pushed response to ProDOS time field
pla ; day of week (unused)
pla ; minute
sta TIMELO ; -- stored as-is (TIMELO 5-0)
pla ; hour
sta TIMELO+1 ; -- stored as-is (TIMELO 12-8)
pla ; year
sta DATELO+1 ; -- will be shifted up by 1 (DATELO 15-9)
pla ; day
and #%00011111 ; -- masked, stored as is (DATELO 4-0)
sta DATELO
pla ; month
asl a ; -- shifted up (DATELO 8-5)
asl a
asl a
asl a
asl a
ora DATELO ; -- merge low 5 bits
sta DATELO
rol DATELO+1
pla ; seconds (unused)
;; Restore prior state
done: pla ; restore saved command state
sta COMMAND
plp
rts
.endproc
sizeof_driver := .sizeof(driver)
.assert sizeof_driver <= 125, error, "Clock code must be <= 125 bytes"
;;; ************************************************************
.ifndef JUMBO_CLOCK_DRIVER
.include "../../inc/driver_postamble.inc"
.endif ; JUMBO_CLOCK_DRIVER
;;; ************************************************************

View File

@ -6,7 +6,8 @@
.include "apple2.inc"
.include "opcodes.inc"
.include "./common.inc"
.include "../../inc/apple2.inc"
.include "../../inc/prodos.inc"
.org $300

100
clocks/cricket/set.date.s Normal file
View File

@ -0,0 +1,100 @@
;;; SET.DATE utility for The Cricket!
;;; Prompts for date and sends Cricket "Set Date" sequence.
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "../../inc/apple2.inc"
.include "../../inc/macros.inc"
.include "../../inc/ascii.inc"
.org $2000
.proc main
jsr zstrout
scrcode "\rDate: WWW MM/DD/YY\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08"
.byte 0
jsr GETLN2
php
sei
;; Reset SSC
sta KBDSTRB ; Port 2 DSR line connected to KBDSTRB
lda #0
sta COMMAND
sta CONTROL
;; Configure SSC
lda #%00001011 ; no parity/echo/interrupts, RTS low, DTR low
sta COMMAND
lda #%10011110 ; 9600 baud, 8 data bits, 2 stop bits
sta CONTROL
;; Clock Commands
;; Set Date "SD WWW MM/DD/YY"
lda #HI('S')
jsr sendbyte
lda #HI('D')
jsr sendbyte
lda #HI(' ')
jsr sendbyte
ldx #0
loop: lda INPUT_BUFFER,x
jsr sendbyte
inx
cmp #HI(ASCII_CR)
bne loop
plp
rts
.endproc
;; Write byte in A to SSC
.proc sendbyte
pha
: lda STATUS
and #(1 << 4) ; transmit register empty? (bit 4)
beq :- ; nope, keep waiting
pla
sta TDREG
rts
.endproc
;;; ------------------------------------------------------------
;;; Output a high-ascii, null-terminated string.
;;; String immediately follows the JSR.
.proc zstrout
ptr := $A5
pla ; read address from stack
sta ptr
pla
sta ptr+1
bne skip ; always (since data not on ZP)
next: jsr COUT
skip: inc ptr
bne :+
inc ptr+1
: ldy #0
lda (ptr),y
bne next
lda ptr+1 ; restore address to stack
pha
lda ptr
pha
rts
.endproc
;;; ------------------------------------------------------------

View File

@ -0,0 +1,144 @@
;;; SET.DATETIME utility for The Cricket!
;;; Prompts for date and time and updates Cricket.
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "../../inc/apple2.inc"
.include "../../inc/macros.inc"
.include "../../inc/ascii.inc"
.org $2000
.proc main
;; --------------------------------------------------
;; Date
jsr zstrout
scrcode "\rDate: WWW MM/DD/YY\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08"
.byte 0
jsr GETLN2
php
sei
;; Reset SSC
sta KBDSTRB ; Port 2 DSR line connected to KBDSTRB
lda #0
sta COMMAND
sta CONTROL
;; Configure SSC
lda #%00001011 ; no parity/echo/interrupts, RTS low, DTR low
sta COMMAND
lda #%10011110 ; 9600 baud, 8 data bits, 2 stop bits
sta CONTROL
;; Clock Commands
;; Set Date "SD WWW MM/DD/YY"
lda #HI('S')
jsr sendbyte
lda #HI('D')
jsr sendbyte
lda #HI(' ')
jsr sendbyte
ldx #0
: lda INPUT_BUFFER,x
jsr sendbyte
inx
cmp #HI(ASCII_CR)
bne :-
plp
;; --------------------------------------------------
;; Time
jsr zstrout
scrcode "\rTime: HH:MM:SS\x08\x08\x08\x08\x08\x08\x08\x08"
.byte 0
jsr GETLN2
php
sei
;; Reset SSC
sta KBDSTRB ; Port 2 DSR line connected to KBDSTRB
lda #0
sta COMMAND
sta CONTROL
;; Configure SSC
lda #%00001011 ; no parity/echo/interrupts, RTS low, DTR low
sta COMMAND
lda #%10011110 ; 9600 baud, 8 data bits, 2 stop bits
sta CONTROL
;; Clock Commands
;; Set Time "ST HH:MM:SS"
lda #HI('S')
jsr sendbyte
lda #HI('T')
jsr sendbyte
lda #HI(' ')
jsr sendbyte
ldx #0
: lda INPUT_BUFFER,x
jsr sendbyte
inx
cmp #HI(ASCII_CR)
bne :-
plp
rts
.endproc
;; Write byte in A to SSC
.proc sendbyte
pha
: lda STATUS
and #(1 << 4) ; transmit register empty? (bit 4)
beq :- ; nope, keep waiting
pla
sta TDREG
rts
.endproc
;;; ------------------------------------------------------------
;;; Output a high-ascii, null-terminated string.
;;; String immediately follows the JSR.
.proc zstrout
ptr := $A5
pla ; read address from stack
sta ptr
pla
sta ptr+1
bne skip ; always (since data not on ZP)
next: jsr COUT
skip: inc ptr
bne :+
inc ptr+1
: ldy #0
lda (ptr),y
bne next
lda ptr+1 ; restore address to stack
pha
lda ptr
pha
rts
.endproc

100
clocks/cricket/set.time.s Normal file
View File

@ -0,0 +1,100 @@
;;; SET.TIME utility for The Cricket!
;;; Prompts for time and sends Cricket "Set Time" sequence.
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "../../inc/apple2.inc"
.include "../../inc/macros.inc"
.include "../../inc/ascii.inc"
.org $2000
.proc main
jsr zstrout
scrcode "\rTime: HH:MM:SS\x08\x08\x08\x08\x08\x08\x08\x08"
.byte 0
jsr GETLN2
php
sei
;; Reset SSC
sta KBDSTRB ; Port 2 DSR line connected to KBDSTRB
lda #0
sta COMMAND
sta CONTROL
;; Configure SSC
lda #%00001011 ; no parity/echo/interrupts, RTS low, DTR low
sta COMMAND
lda #%10011110 ; 9600 baud, 8 data bits, 2 stop bits
sta CONTROL
;; Clock Commands
;; Set Time "ST HH:MM:SS"
lda #HI('S')
jsr sendbyte
lda #HI('T')
jsr sendbyte
lda #HI(' ')
jsr sendbyte
ldx #0
loop: lda INPUT_BUFFER,x
jsr sendbyte
inx
cmp #HI(ASCII_CR)
bne loop
plp
rts
.endproc
;; Write byte in A to SSC
.proc sendbyte
pha
: lda STATUS
and #(1 << 4) ; transmit register empty? (bit 4)
beq :- ; nope, keep waiting
pla
sta TDREG
rts
.endproc
;;; ------------------------------------------------------------
;;; Output a high-ascii, null-terminated string.
;;; String immediately follows the JSR.
.proc zstrout
ptr := $A5
pla ; read address from stack
sta ptr
pla
sta ptr+1
bne skip ; always (since data not on ZP)
next: jsr COUT
skip: inc ptr
bne :+
inc ptr+1
: ldy #0
lda (ptr),y
bne next
lda ptr+1 ; restore address to stack
pha
lda ptr
pha
rts
.endproc
;;; ------------------------------------------------------------

View File

@ -1,11 +1,19 @@
;;; Test program for The Cricket!
;;; * Probes Slot 2 for Super Serial Card (or compatible)
;;; * Initializes SSC
;;; * Sends Cricket ID sequence
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "opcodes.inc"
.include "./common.inc"
.include "../../inc/apple2.inc"
.include "../../inc/macros.inc"
.include "../../inc/ascii.inc"
.org $2000
@ -28,16 +36,20 @@
bne ssc_not_found
jsr zstrout
HIASCIIZ "SSC found.", CR
scrcode "SSC found.\r"
.byte 0
jmp init_ssc
ssc_not_found:
jsr zstrout
HIASCIIZ "SSC not found.", CR
scrcode "SSC not found.\r"
.byte 0
rts
;; TODO: Write NUL and check for 'C' ... version ... $8D (CR)
;; https://github.com/inexorabletash/cricket/issues/3
;; https://github.com/a2stuff/cricket/issues/3
init_ssc:
lda COMMAND ; save status of SSC registers
sta saved_command
@ -59,7 +71,8 @@ init_ssc:
;; by a version number (in ASCII) and a carriage return (141,
;; $8D)."
jsr zstrout
HIASCIIZ "Reading SSC: "
scrcode "Reading SSC: "
.byte 0
jsr readbyte
bcs cricket_not_found ; timeout
@ -72,7 +85,7 @@ init_ssc:
: jsr readbyte
bcs cricket_not_found ; timeout
cmp #HI(CR) ; = CR ?
cmp #HI(ASCII_CR)
beq cricket_found
digit: cmp #HI('0') ; < '0' ?
bcc cricket_not_found
@ -84,12 +97,14 @@ digit: cmp #HI('0') ; < '0' ?
cricket_found:
jsr zstrout
HIASCIIZ CR, "Cricket tentatively found.", CR
scrcode "\rCricket tentatively found.\r"
.byte 0
jmp exit
cricket_not_found:
jsr zstrout
HIASCIIZ CR, "Cricket not identified.", CR
scrcode "\rCricket not identified.\r"
.byte 0
jmp exit
exit:
@ -138,7 +153,8 @@ check: lda STATUS ; did we get it?
bne check
jsr zstrout
HIASCIIZ "... timeout!"
scrcode "... timeout!"
.byte 0
sec ; failed
rts

46
clocks/dclock/Makefile Normal file
View File

@ -0,0 +1,46 @@
CAFLAGS = --target apple2enh --list-bytes 0
LDFLAGS = --config apple2-asm.cfg
OUTDIR = ../../out
HEADERS = $(wildcard *.inc) $(wildcard ../../inc/*.inc)
TARGETS = \
$(OUTDIR)/dclock.system.SYS \
$(OUTDIR)/dclock.setup.SYS
LOG_SUCCESS=1
LOG_FAILURE=1
# For timestamps
MM = $(shell date "+%-m")
DD = $(shell date "+%-d")
YY = $(shell date "+%-y")
DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY) \
-D LOG_SUCCESS=$(LOG_SUCCESS) -D LOG_FAILURE=$(LOG_FAILURE)
XATTR := $(shell command -v xattr 2> /dev/null)
.PHONY: clean all
all: $(OUTDIR) $(TARGETS)
$(OUTDIR):
mkdir -p $(OUTDIR)
clean:
rm -f $(OUTDIR)/*.o
rm -f $(OUTDIR)/*.list
rm -f $(TARGETS)
$(OUTDIR)/%.o: %.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.setup.o: %.system.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) -D BUILD_SETUP_FILE --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.SYS: $(OUTDIR)/%.o
ld65 $(LDFLAGS) -o $@ $<
ifdef XATTR
xattr -wx prodos.AuxType '00 20' $@
endif

15
clocks/dclock/README.md Normal file
View File

@ -0,0 +1,15 @@
# Applied Engineering DClock &mdash; ProDOS Clock Driver
This is based on a disassembly of the Applied Engineering driver for the DClock real time clock add on for the Apple IIc.
> NOTE: Currently untested!
Like other drivers here, this one will:
* Conditionally install, only if a DClock is detected.
* Only attempts detection if there is not already a clock driver.
* Only attempts detection if the system is a an Apple IIc
* If detected, installs into ProDOS directly, following Technical Reference Manual requirements.
* Chains to the next `.SYSTEM` file in the directory.
`DCLOCK.SETUP` is for use with [SETUP.SYSTEM](../../setup/README.md).

View File

@ -0,0 +1,406 @@
; Fully disassembled and analyzed source to AE
; DCLOCK.SYSTEM by M.G. - 04/18/2017
; https://gist.github.com/mgcaret/7f0d7aeec169e90809c7cfaab9bf183b
; Further modified by @inexorabletash - 12/21/2020
; There are critical bugs in the original AE code:
; * When driver loader is initially probing it corrupts the
; Apple //c Memory Expansion Card:
; - it saves, but fails to restore, data at address $080000
; - it fails to reset slinky pointer, and *will* trash $080000-$080007
; * When the clock is read, it corrupts data at address $08xx01
; - John Brooks spotted this, [M.G.] totally missed this.
; This version of the code has fixes permanently applied.
.ifndef JUMBO_CLOCK_DRIVER
.setcpu "6502" ; changed below
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "opcodes.inc"
.include "../../inc/apple2.inc"
.include "../../inc/macros.inc"
.include "../../inc/prodos.inc"
.endif ; JUMBO_CLOCK_DRIVER
; zero page locations
SCRATCH := $0B ; scratch value for BCD range checks
SAVEBYTE := $0C ; slinky overwritten byte save location
BCDTMP := $3A ; location clock driver uses for BCD->Binary
; buffers & other spaces
INBUF := $0200 ; input buffer
CLOCKBUF := $0300 ; clock buffer
; I/O and hardware
C8OFF := $CFFF ; C8xx ROM off
SLOT4ROM := $C400 ; Slot 4 ROM space
SLOT4IO := $C0C0 ; Slot 4 I/O space
DPTRL := SLOT4IO+0 ; Slinky data ptr low
DPTRM := SLOT4IO+1 ; Slinky data ptr middle
DPTRH := SLOT4IO+2 ; Slinky data ptr high
DATA := SLOT4IO+3 ; Slinky data byte
;;; ************************************************************
.ifndef JUMBO_CLOCK_DRIVER
.include "../../inc/driver_preamble.inc"
.endif ; JUMBO_CLOCK_DRIVER
;;; ************************************************************
;;; ============================================================
;;;
;;; Driver Installer
;;;
;;; ============================================================
.undef PRODUCT
.define PRODUCT "DClock"
;;; ============================================================
;;; Ensure there is not a previous clock driver installed.
;;; And that this is a IIc. And that the clock is present.
;;; NOTE: Safe to run on a non-IIc (6502 opcodes, etc)
.proc maybe_install_driver
lda MACHID
and #$01 ; existing clock card?
bne done
lda VERSION ; IIc identification byte 1
cmp #$06
bne done
lda ZIDBYTE ; IIc identification byte 2
cmp #$00
bne done
;;; Since this is a IIc, okay to rely on 65C02 opcodes from here.
.pushcpu
.setcpu "65C02"
jsr ClockRead
jsr ValidTime
bcc InstallDriver
.ifndef JUMBO_CLOCK_DRIVER
.if ::LOG_FAILURE
;; Show failure message
jsr log_message
scrcode PRODUCT, " - Not Found."
.byte 0
.endif ; ::LOG_FAILURE
.endif ; JUMBO_CLOCK_DRIVER
done: sec ; failure
rts
.endproc
; ----------------------------------------------------------------------------
; Install clock driver
.proc InstallDriver
;; Copy into address at DATETIME vector, update the vector and
;; update MACHID bits to signal a clock is present.
ptr := $A5
;; Update absolute addresses within driver
lda DATETIME+1
sta ptr
clc
adc #(regulk - driver)
sta regulk_addr
lda DATETIME+2
sta ptr+1
adc #0
sta regulk_addr+1
;; Copy driver into appropriate bank
lda RWRAM1
lda RWRAM1
ldy #sizeof_driver-1
loop: lda driver,y
sta (ptr),y
dey
bpl loop
;; Set the "Recognizable Clock Card" bit
lda MACHID
ora #$01
sta MACHID
lda #OPC_JMP_abs
sta DATETIME
;; Invoke the driver to init the time
jsr DATETIME
lda ROMIN2
.if ::LOG_SUCCESS
;; Display success message
jsr log_message
scrcode PRODUCT, " - "
.byte 0
;; Display the current date
jsr cout_date
.endif ; ::LOG_SUCCESS
clc ; success
rts ; done!
.endproc
; ----------------------------------------------------------------------------
; enable slinky registers, set adddress and save byte we intend to trash
.proc SlinkyEnable
lda C8OFF ; not needed on //c, but release $C8xx firmware
lda SLOT4ROM ; enable slinky registers
lda #$08 ; set addr $080000
sta DPTRH
stz DPTRM
stz DPTRL
lda DATA ; read data byte
sta SAVEBYTE ; save it to restore later
rts
.endproc
; ----------------------------------------------------------------------------
; Routine to restore trashed byte in slinky RAM
.proc SlinkyRestore
lda #$08 ; set adddr $080000
sta DPTRH
stz DPTRM
stz DPTRL
lda SAVEBYTE ; get saved byte
sta DATA ; and put it back
lda C8OFF ; not needed on //c, but release $C8xx firmware
rts
.endproc
; ----------------------------------------------------------------------------
; Write 8 bits to clock
.proc ClockWrite8b
ldx #$08 ; set adddr $080000
stx DPTRH
stz DPTRM
: stz DPTRL ; restore low byte to 0
sta DATA ; write byte
lsr a ; next bit into 0 position
dex
bne :-
rts
.endproc
; ----------------------------------------------------------------------------
; unlock the clock by writing the magic bit sequence
.proc ClockUnlock
ldy #$08
: lda unlock,y
jsr ClockWrite8b ; write 8 bits
dey
bne :-
rts
unlock = * - 1
.byte $5c, $a3, $3a, $c5, $5c, $a3, $3a, $c5
.endproc
; ----------------------------------------------------------------------------
; Read 8 bits from the clock
.proc ClockRead8b
ldx #$08 ; set adddr $080000
stz DPTRL
stz DPTRM
stx DPTRH
: pha ; save accumulator
lda DATA ; get data byte
lsr a ; bit 0 into carry
pla ; restore accumulator
ror a ; put read bit into position
dex
bne :-
rts
.endproc
; ----------------------------------------------------------------------------
; read the clock data into memory at CLOCKBUF
; WARNING: unfixed code never restores byte we trashed
.proc ClockRead
jsr SlinkyEnable
jsr ClockUnlock
ldy #$00
: jsr ClockRead8b
sta CLOCKBUF,y
iny
cpy #$08 ; have we read 8 bytes?
bcc :- ; nope
jsr SlinkyRestore
rts
.endproc
; ----------------------------------------------------------------------------
; validate the DClock data makes sense
; return carry clear if it does, carry set if it does not
.proc ValidTime
; validate ms
ldx #$00
ldy #$99
lda CLOCKBUF
jsr CheckBCD
bcs :+
; validate seconds
ldx #$00
ldy #$59
lda CLOCKBUF+$01
jsr CheckBCD
bcs :+
; validate minutes
ldx #$00
ldy #$59
lda CLOCKBUF+$02
jsr CheckBCD
bcs :+
; validate hours
ldx #$00
ldy #$23
lda CLOCKBUF+$03
jsr CheckBCD
bcs :+
; validate day of week
ldx #$01
ldy #$07
lda CLOCKBUF+$04
jsr CheckBCD
bcs :+
; validate day of month
ldx #$01
ldy #$31
lda CLOCKBUF+$05
jsr CheckBCD
bcs :+
; validate month
ldx #$01
ldy #$12
lda CLOCKBUF+$06
jsr CheckBCD
bcs :+
; validate year
ldx #$00
ldy #$99
lda CLOCKBUF+$07
jsr CheckBCD
bcs :+
clc ; all good
rts
: sec ; problem
rts
.endproc
; ----------------------------------------------------------------------------
; Check BCD number in range of [x,y]
; return carry clear if it is, carry set if it is not
.proc CheckBCD
sed ; decimal mode
stx SCRATCH ; lower bound into scratch
cmp SCRATCH ; compare it
bcc :++ ; fail if out of range
sty SCRATCH ; upper bound into scratch
cmp SCRATCH ; compare it
beq :+ ; OK if equal
bcs :++ ; fail if out of range
: cld ; in range
clc
rts
: cld ; not in range
sec
rts
.endproc
; ----------------------------------------------------------------------------
; clock driver code inserted into ProDOS
driver:
lda #$08 ; useless instruction
php
sei
lda SLOT4ROM ; activate slinky registers
; ($08 from above overwritten)
stz DPTRL ; set slinky address to $08xx00
ldy #$08 ; also counter for unlock bytes
sty DPTRH
lda DATA ; get destroyed byte
; (slinky now at $08xx01)
pha ; save value on stack
; unlock dclock registers
regulk_addr := *+1
ubytlp: lda regulk,y ; self-modified
ldx #$08 ; bit counter
ubitlp: stz DPTRL ; reset pointer to $08xx00
sta DATA ; write to $08xx00
lsr a ; next bit into 0 position
dex
bne ubitlp
dey
bne ubytlp
; now read 64 bits (8 bytes) from dclock
ldx #$08 ; byte counter
rbytlp: ldy #$08 ; bit counter
rbitlp: pha
lda DATA ; data byte
lsr a ; bit 0 into carry
pla
ror a ; carry into bit 7
dey
bne rbitlp
; got 8 bits now, convert from BCD to binary
pha
and #$0F
sta BCDTMP
pla
and #$F0
lsr a
pha
adc BCDTMP
sta BCDTMP
pla
lsr a
lsr a
adc BCDTMP
; place in input buffer, which is OK because the ThunderClock driver does this
sta INBUF-1,x
dex
bne rbytlp
; done copying, now put necessary values into ProDOS time locations
; copy hours to ProDOS hours
lda INBUF+4
sta TIMEHI
; copy minutes to ProDOS minutes
lda INBUF+5
sta TIMELO
; copy month ...
lda INBUF+1
lsr a
ror a
ror a
ror a
; ... and day of month to ProDOS month/day
ora INBUF+2
sta DATELO
; copy year and final bit of month to ProDOS year/month
lda INBUF
rol a
sta DATEHI
stz DPTRL ; set slinky back to $08xx00
pla ; get saved byte
sta DATA ; put it back
plp
rts
; DS1215 unlock sequence (in reverse)
regulk = * - 1
.byte $5C, $A3, $3A, $C5, $5C, $A3, $3A, $C5
sizeof_driver := * - driver
.assert sizeof_driver <= 125, error, "Clock code must be <= 125 bytes"
.popcpu
;;; ************************************************************
.ifndef JUMBO_CLOCK_DRIVER
.include "../../inc/driver_postamble.inc"
.endif ; JUMBO_CLOCK_DRIVER
;;; ************************************************************

46
clocks/fujinet/Makefile Normal file
View File

@ -0,0 +1,46 @@
CAFLAGS = --target apple2enh --list-bytes 0
LDFLAGS = --config apple2-asm.cfg
OUTDIR = ../../out
HEADERS = $(wildcard *.inc) $(wildcard ../../inc/*.inc)
TARGETS = \
$(OUTDIR)/fn.clock.system.SYS \
$(OUTDIR)/fn.clock.setup.SYS
LOG_SUCCESS=1
LOG_FAILURE=1
# For timestamps
MM = $(shell date "+%-m")
DD = $(shell date "+%-d")
YY = $(shell date "+%-y")
DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY) \
-D LOG_SUCCESS=$(LOG_SUCCESS) -D LOG_FAILURE=$(LOG_FAILURE)
XATTR := $(shell command -v xattr 2> /dev/null)
.PHONY: clean all
all: $(OUTDIR) $(TARGETS)
$(OUTDIR):
mkdir -p $(OUTDIR)
clean:
rm -f $(OUTDIR)/*.o
rm -f $(OUTDIR)/*.list
rm -f $(TARGETS)
$(OUTDIR)/%.o: %.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.setup.o: %.system.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) -D BUILD_SETUP_FILE --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.BIN $(OUTDIR)/%.SYS: $(OUTDIR)/%.o
ld65 $(LDFLAGS) -o $@ $<
ifdef XATTR
xattr -wx prodos.AuxType '00 20' $@
endif

7
clocks/fujinet/README.md Normal file
View File

@ -0,0 +1,7 @@
# FujiNet ProDOS Clock Driver
[FujiNet](https://fujinet.online/) for Apple II provides a number of devices via SmartPort. Alongside four block and a network device it has a real time clock device.
This driver is an adaptation of the other drivers in https://github.com/a2stuff/prodos-drivers
`FN.CLOCK.SETUP` is for use with [SETUP.SYSTEM](../../setup/README.md).

View File

@ -0,0 +1,201 @@
;;; ProDOS driver for the Fujinet clock
;;; Adapted from: https://github.com/a2stuff/prodos-drivers/blob/main/cricket/cricket.system.s
.ifndef JUMBO_CLOCK_DRIVER
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "opcodes.inc"
.include "../../inc/apple2.inc"
.include "../../inc/macros.inc"
.include "../../inc/prodos.inc"
.include "../../inc/ascii.inc"
.endif ; JUMBO_CLOCK_DRIVER
;;; ************************************************************
.ifndef JUMBO_CLOCK_DRIVER
.include "../../inc/driver_preamble.inc"
.endif ; JUMBO_CLOCK_DRIVER
;;; ************************************************************
.include "./smartport.inc"
FN_CLOCK_DEVICE_TYPE := $13 ; As defined on the Fujinet firmware
;;; ============================================================
;;;
;;; Driver Installer
;;;
;;; ============================================================
.undef PRODUCT
.define PRODUCT "Fujinet Clock"
;;; ============================================================
;;; Ensure there is not a previous clock driver installed.
.proc maybe_install_driver
lda MACHID
and #$01 ; existing clock card?
beq detect_fujinet_clock ; nope, check for clock
rts ; yes, done!
.endproc
;;; ============================================================
;;; Fujinet Clock Driver - copied into ProDOS
;;; ============================================================
.proc driver
scratch := $3A ; ZP scratch location
;; Initialize
php
sei
;; Execute smartport command
jsr $c50d ; To be changed to the detected slot and address
drv_call_hi = *-1
drv_call_lo = *-2
.byte DRIVER_COMMAND_STATUS ; Command Status
params_address:
.word params - driver ; To be changed on relocation
;; Restore state and return
sta $CFFF ; release C8xx ROM space
plp
rts
params: .byte $03 ; Status param count
port: .byte $00 ; Smartport device
.word DATELO ; Write directly on the four bytes reserved by Prodos for date and time
.byte 'P' ; Get datetime in ProDDOS format
.endproc
.assert .sizeof(driver) <= 125, error, "Clock code must be <= 125 bytes"
;;; ------------------------------------------------------------
;;; Detect Fujinet Clock.
.proc detect_fujinet_clock
;; Search for smartport cards
ldx #$C7 ; Start the search from slot 7
search_slot:
jsr find_smartport
bcs not_found
;; Find a Fujinet Clock device on this slot
jsr setup_smartport
jsr device_count
cpx #$0
beq continue_slot_search; no devices in the slot
search_unit:
jsr unit_type
cmp #FN_CLOCK_DEVICE_TYPE
beq found
dex
bne search_unit
continue_slot_search:
ldx sp_call_hi ; restore card
dex
cpx #$C0
bne search_slot
jmp not_found
found:
; Modify the driver code with the detected data
stx driver::port
lda sp_call_lo
sta driver::drv_call_lo
lda sp_call_hi
sta driver::drv_call_hi
jmp install_driver
not_found:
.ifndef JUMBO_CLOCK_DRIVER
.if ::LOG_FAILURE
;; Show failure message
jsr log_message
scrcode PRODUCT, " - Not Found."
.byte 0
.endif ; ::LOG_FAILURE
.endif ; JUMBO_CLOCK_DRIVER
sec ; failure
rts
.endproc
;;; ------------------------------------------------------------
;;; Install Driver. Copy into address at DATETIME vector,
;;; update the vector and update MACHID bits to signal a clock
;;; is present.
.proc install_driver
ptr := $A5
;; Find driver destination
lda DATETIME+1
sta ptr
lda DATETIME+2
sta ptr+1
;; Fix pointers
clc
lda ptr
adc driver::params_address
sta driver::params_address
lda ptr+1
adc driver::params_address+1
sta driver::params_address+1
;; Copy code
lda RWRAM1
lda RWRAM1
ldy #.sizeof(driver)-1
loop: lda driver,y
sta (ptr),y
dey
bpl loop
;; Set the "Recognizable Clock Card" bit
lda MACHID
ora #$01
sta MACHID
lda #OPC_JMP_abs
sta DATETIME
;; Invoke the driver to init the time
jsr DATETIME
lda ROMIN2
.if ::LOG_SUCCESS
;; Display success message
jsr log_message
scrcode PRODUCT, " - "
.byte 0
;; Display the current date
jsr cout_date
.endif ; ::LOG_SUCCESS
clc ; success
rts ; done!
.endproc
;;; ************************************************************
.ifndef JUMBO_CLOCK_DRIVER
.include "../../inc/driver_postamble.inc"
.endif ; JUMBO_CLOCK_DRIVER
;;; ************************************************************

View File

@ -0,0 +1,111 @@
;;; ------------------------------------------------------------
;;; Smartport access functions
;;; Derived from: http://mirrors.apple2.org.za/ground.icaen.uiowa.edu/MiscInfo/Programming/smartport.statusexample
;;; This function scans the slots to locate a SmartPort.
;;; On entry, X=$Cx, where x is the first slot to be checked.
;;; On exit, X=$Cy, where y is the highest numbered slot less than or
;;; equal to x which contains SmartPort firmware. If no SmartPort
;;; is found, C=1 and A=$00.
ptr := $A5 ; Generic pointer
.proc find_smartport
LDA #$00
STA ptr ; Set up the pointer
try_slot:
STX ptr+1
LDY #$01
LDA (ptr),Y ; Check the first ID byte
CMP #$20
BNE not_here
LDY #$03
LDA (ptr),Y ; and the second one
CMP #$00
BNE not_here
LDY #$05
LDA (ptr),Y ; and the third one
CMP #$03
BNE not_here
LDY #$07
LDA (ptr),Y ; and the fourth one
CMP #$00
BNE not_here
LDX ptr+1 ; Match! Get the address back
CLC
RTS
not_here:
LDX ptr+1 ; Mismatch
DEX ; Go down one slot
CPX #$C1
BCS try_slot ; Stop once we have gone past slot 1
LDX #$00
SEC ; Error - no SmartPort found
RTS
.endproc
;;; This function sets up the SP_CALL function for calling the
;;; SmartPort driver. On entry, X=$Cx, where x is the slot number
;;; containing a SmartPort driver. This should be checked via
;;; FIND_SMARTPORT if necessary - don't assume there is a SmartPort
;;; device in slot 5, for example!
.proc setup_smartport
LDA #$00
STA ptr ; Set up the pointer
STX ptr+1
LDY #$FF
LDA (ptr),Y ; Get the ProDOS driver entry point
CLC
ADC #$03 ; Get the SmartPort driver entry point
STA sp_call_lo ; Store in the JSR
STX sp_call_hi ; also store the high byte
RTS
.endproc
;;; This function return in X the number of devices available
;;; on a SmartPort
.proc device_count
LDA #$00
STA st_unit
STA st_code
JSR sp_call
BCS device_count_error
LDX st_list+0
RTS
device_count_error:
LDX #$00
RTS
.endproc
;;; This function returns in A the device type for a unit in X
.proc unit_type
STX st_unit
LDA #$03
STA st_code
JSR sp_call
BCS unit_type_error
LDA st_list+21
LDX st_unit
RTS
unit_type_error:
LDA #$ff
LDX st_unit
RTS
.endproc
;;; Status command parameters
sp_call: JSR $0000
sp_call_hi = *-1
sp_call_lo = *-2
.byte DRIVER_COMMAND_STATUS ; Command Status
params_address:
.word st_params
RTS
st_params:
.byte $3 ; Parameter count
st_unit:.byte $0
.word st_list
st_code:.byte $0
st_list:.byte 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24

52
clocks/jumbo/Makefile Normal file
View File

@ -0,0 +1,52 @@
CAFLAGS = --target apple2enh --list-bytes 0
LDFLAGS = --config apple2-asm.cfg
OUTDIR = ../../out
HEADERS = $(wildcard *.inc) $(wildcard ../../inc/*.inc) \
../ns.clock/ns.clock.system.s \
../romx/romxrtc.system.s \
../dclock/dclock.system.s \
../fujinet/fn.clock.system.s \
../fujinet/smartport.inc \
../cricket/cricket.system.s
TARGETS = \
$(OUTDIR)/clock.system.SYS \
$(OUTDIR)/clock.setup.SYS
LOG_SUCCESS=1
LOG_FAILURE=1
# For timestamps
MM = $(shell date "+%-m")
DD = $(shell date "+%-d")
YY = $(shell date "+%-y")
DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY) \
-D LOG_SUCCESS=$(LOG_SUCCESS) -D LOG_FAILURE=$(LOG_FAILURE)
XATTR := $(shell command -v xattr 2> /dev/null)
.PHONY: clean all
all: $(OUTDIR) $(TARGETS)
$(OUTDIR):
mkdir -p $(OUTDIR)
clean:
rm -f $(OUTDIR)/*.o
rm -f $(OUTDIR)/*.list
rm -f $(TARGETS)
$(OUTDIR)/%.o: %.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.setup.o: %.system.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) -D BUILD_SETUP_FILE --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.SYS: $(OUTDIR)/%.o
ld65 $(LDFLAGS) -o $@ $<
ifdef XATTR
xattr -wx prodos.AuxType '00 20' $@
endif

17
clocks/jumbo/README.md Normal file
View File

@ -0,0 +1,17 @@
# "Jumbo" ProDOS Clock Driver
This is an amalgamation of the other clock driver installers. Each one is tried in turn, until one successfully installs a clock driver.
The drivers are (in order):
* No-Slot Clock
* ROMX
* FujiNet
* DClock
* Cricket!
By default, the installer logs on success so you can tell what clock was detected, but you can build with `LOG_SUCCESS=0` to prevent that.
If ProDOS _already_ has a clock driver installed, the driver is checked for common Thunderclock year tables. If found, the table is updated in memory to cover 2023-2028.
`CLOCK.SETUP` is for use with [SETUP.SYSTEM](../../setup/README.md).

161
clocks/jumbo/clock.system.s Normal file
View File

@ -0,0 +1,161 @@
;;; "Jumbo" Clock Driver
;;;
;;; Pulls in several clock drivers sources and tries each one in sequence.
;;;
JUMBO_CLOCK_DRIVER = 1
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "opcodes.inc"
.include "../../inc/apple2.inc"
.include "../../inc/macros.inc"
.include "../../inc/prodos.inc"
.include "../../inc/ascii.inc"
;;; ************************************************************
.include "../../inc/driver_preamble.inc"
;;; ************************************************************
.scope ns_clock
.include "../ns.clock/ns.clock.system.s"
.endscope
.scope romx
.include "../romx/romxrtc.system.s"
.endscope
.scope dclock
.include "../dclock/dclock.system.s"
.endscope
.scope fujinet
.include "../fujinet/fn.clock.system.s"
.endscope
.scope cricket
.include "../cricket/cricket.system.s"
.endscope
;;; ============================================================
;;;
;;; Driver Installer
;;;
;;; ============================================================
.undef PRODUCT
.define PRODUCT "Jumbo Clock Driver"
.proc maybe_install_driver
lda MACHID
and #$01 ; existing clock card?
bne check_thunderclock
jsr ns_clock::maybe_install_driver
bcc ret
jsr romx::maybe_install_driver
bcc ret
jsr dclock::maybe_install_driver
bcc ret
jsr fujinet::maybe_install_driver
bcc ret
jsr cricket::maybe_install_driver
bcc ret
ret: rts
.endproc
.proc check_thunderclock
;; Look for Thunderclock year table
bit RWRAM1
bit RWRAM1
lda #<table_1982
ldx #>table_1982
jsr check_sig
beq update_table
lda #<table_1986
ldx #>table_1986
jsr check_sig
beq update_table
lda #<table_1993
ldx #>table_1993
jsr check_sig
beq update_table
lda #<table_2018
ldx #>table_2018
jsr check_sig
beq update_table
;; Table not found - we have a clock but don't
;; know what it is, so don't log anything.
bit ROMIN2
rts
;; ----------------------------------------
;; Copy the latest table into place
update_table:
ldx #SIG_LEN-1
: lda table_2023,x
sta SIG_ADDR,x
dex
bpl :-
bit ROMIN2
.if ::LOG_SUCCESS
;; Display success message, to confirm table updates.
jsr log_message
scrcode "ThunderClock - "
.byte 0
;; Display the current date
jsr cout_date
.endif ; ::LOG_SUCCESS
rts
;; ----------------------------------------
check_sig:
ptr := $06
sta ptr
stx ptr+1
ldy #SIG_LEN-1
: lda (ptr),y
cmp SIG_ADDR,y
bne :+ ; Z=0 for no match
dey
bpl :-
iny ; Z=1 for match
: rts
SIG_LEN = 7
SIG_ADDR := $D7B8
table_1982: ; ProDOS 1.1.1
.byte $54, $54, $53, $52, $57, $56, $55
table_1986: ; ProDOS 1.3 - 1.9
.byte $5A, $59, $58, $58, $57, $56, $5B
table_1993: ; ProDOS 2.0.3
.byte $60, $5F, $5E, $5D, $62, $61, $60
table_2018: ; ProDOS 2.4.2
.byte $12, $17, $16, $15, $14, $14, $13
table_2023:
.byte $18, $17, $1C, $1B, $1A, $19, $18
.endproc
;;; ************************************************************
.include "../../inc/driver_postamble.inc"
;;; ************************************************************

53
clocks/ns.clock/Makefile Normal file
View File

@ -0,0 +1,53 @@
CAFLAGS = --target apple2enh --list-bytes 0
LDFLAGS = --config apple2-asm.cfg
OUTDIR = ../../out
UTILDIR = $(OUTDIR)/nsclock.util
HEADERS = $(wildcard *.inc) $(wildcard ../../inc/*.inc)
TARGETS = \
$(OUTDIR)/ns.clock.system.SYS \
$(OUTDIR)/ns.clock.setup.SYS \
$(UTILDIR)/set.datetime.BIN
LOG_SUCCESS=1
LOG_FAILURE=1
# For timestamps
MM = $(shell date "+%-m")
DD = $(shell date "+%-d")
YY = $(shell date "+%-y")
DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY) \
-D LOG_SUCCESS=$(LOG_SUCCESS) -D LOG_FAILURE=$(LOG_FAILURE)
XATTR := $(shell command -v xattr 2> /dev/null)
.PHONY: clean all
all: $(OUTDIR) $(UTILDIR) $(TARGETS)
$(OUTDIR) $(UTILDIR):
mkdir -p $@
clean:
rm -f $(OUTDIR)/*.o
rm -f $(OUTDIR)/*.list
rm -f $(UTILDIR)/*.o
rm -f $(UTILDIR)/*.list
rm -f $(TARGETS)
$(OUTDIR)/%.o $(UTILDIR)/%.o: %.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.setup.o: %.system.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) -D BUILD_SETUP_FILE --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.SYS: $(OUTDIR)/%.o
ld65 $(LDFLAGS) -o $@ $<
$(UTILDIR)/%.BIN: $(UTILDIR)/%.o
ld65 $(LDFLAGS) -o $@ $<
ifdef XATTR
xattr -wx prodos.AuxType '00 20' $@
endif

16
clocks/ns.clock/README.md Normal file
View File

@ -0,0 +1,16 @@
# No Slot Clock ProDOS Driver
Adapted from `NS.CLOCK.SYSTEM` (by "CAP"), with these changes:
* Fixes a typo
* Removes beeps
* Is less chatty so you can have multiple clock drivers, e.g. if you use the same hard disk image across different hardware configurations
* Uses file I/O rather than block I/O for chaining
* Does not hard-code driver file name
## Other Utilities
These `BRUN`able files are also built:
* [SET.DATETIME](set.datetime.s) sets the No Slot Clock date/time.
`NS.CLOCK.SETUP` is for use with [SETUP.SYSTEM](../../setup/README.md).

View File

@ -0,0 +1,387 @@
;;; NS.CLOCK.SYSTEM
;;; Original by "CAP" 04/21/91
;;; http://www.apple2.org.za/gswv/a2zine/GS.WorldView/v1999/Oct/MISC/NSC.Disk.TXT
;;; Modification history available at:
;;; https://github.com/a2stuff/prodos-drivers
.ifndef JUMBO_CLOCK_DRIVER
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "opcodes.inc"
.include "../../inc/apple2.inc"
.include "../../inc/macros.inc"
.include "../../inc/prodos.inc"
.endif ; JUMBO_CLOCK_DRIVER
;;; ************************************************************
.ifndef JUMBO_CLOCK_DRIVER
.include "../../inc/driver_preamble.inc"
.endif ; JUMBO_CLOCK_DRIVER
;;; ************************************************************
;;; ============================================================
;;;
;;; Driver Installer
;;;
;;; ============================================================
.undef PRODUCT
.define PRODUCT "No-Slot Clock"
;;; ============================================================
;;; Ensure there is not a previous clock driver installed.
.proc maybe_install_driver
lda MACHID
and #$01 ; existing clock card?
beq detect_nsc ; nope, check for NSC
rts ; yes, done!
.endproc
;;; ------------------------------------------------------------
;;; Detect NSC. Scan slot ROMs and main ROMs. Try reading
;;; each location several times, and validate results before
;;; installing driver.
.proc detect_nsc
;; Preserve date/time
ldy #3 ; copy 4 bytes
: lda DATELO,y
sta saved,y
dey
bpl :-
;; Check slot ROMs
lda #>$CFFF
ldy #<$CFFF
sta ld4+2
sty ld4+1
sta st4+2
sty st4+1
lda #0
sta slot
lda #3 ; treat slot 0 as slot 3
sloop: ora #$C0 ; A=$Cs
pha
jsr DetectZ80
pla ; A=$Cs
bcs next ; Z80 present, skip this slot
sta st1+2
rloop: sta ld1+2
sta ld2+2
sta st2+2
lda #3 ; 3 tries - need valid results each time
sta tries
try: jsr driver ; try reading date/time
lda DATELO+1 ; check result
ror a
lda DATELO
rol a
rol a
rol a
rol a
and #$0F
beq next
cmp #13 ; month
bcs next
lda DATELO
and #$1F
beq next
cmp #32 ; day
bcs next
lda TIMELO+1
cmp #24 ; hours
bcs next
lda TIMELO
cmp #60 ; minutes
bcs next
dec tries
bne try
beq install_driver ; all tries look valid
next: inc slot
lda slot
cmp #8
bcc sloop ; next slot
bne not_found
;; Not found in slot ROM, try main ROMs ???
lda #>$C015 ; $C015 = RDCXROM (internal or slot ROM?)
ldy #<$C015
sta ld4+2
sty ld4+1
ldy #$07 ; $C007 = INTCXROM (read internal ROM)
sta st1+2
sty st1+1
dey ; $C006 = SLOTCXROM (read slot ROM)
sta st4+2
sty st4+1
lda #>$C800
bne rloop
;; Restore date/time
not_found:
ldy #3
: lda saved,y
sta DATELO,y
dey
bpl :-
.ifndef JUMBO_CLOCK_DRIVER
.if ::LOG_FAILURE
;; Show failure message
jsr log_message
scrcode PRODUCT, " - Not Found."
.byte 0
.endif ; ::LOG_FAILURE
.endif ; JUMBO_CLOCK_DRIVER
sec ; failure
rts
saved: .byte 0, 0, 0, 0
tries: .byte 3
slot: .byte 0
.endproc
;;; ------------------------------------------------------------
;;; Install NSC Driver. Copy into address at DATETIME vector,
;;; update the vector and update MACHID bits to signal a clock
;;; is present.
.proc install_driver
ptr := $A5
;; Update absolute addresses within driver
lda DATETIME+1
sta ptr
clc
adc #(unlock - driver - 1)
sta unlock_addr
lda DATETIME+2
sta ptr+1
adc #0
sta unlock_addr+1
;; Copy driver into appropriate bank
lda RWRAM1
lda RWRAM1
ldy #sizeof_driver-1
loop: lda driver,y
sta (ptr),y
dey
bpl loop
;; Set the "Recognizable Clock Card" bit
lda MACHID
ora #$01
sta MACHID
lda #OPC_JMP_abs
sta DATETIME
;; Invoke the driver to init the time
jsr DATETIME
lda ROMIN2
.if ::LOG_SUCCESS
;; Display success message
jsr log_message
scrcode PRODUCT, " - "
.byte 0
;; Display the current date
jsr cout_date
.endif ; ::LOG_SUCCESS
clc ; success
rts ; done!
.endproc
;;; ------------------------------------------------------------
;;; Detect Z80
;;; ------------------------------------------------------------
;;; This routine gets swapped into $0FFD for execution
.proc Z80Routine
target := $0FFD
;; .org $FFFD
patch := *+2
.byte $32, $00, $e0 ; ld ($Es00),a ; s=slot being probed turn off Z80, next PC is $0000
.byte $3e, $01 ; ld a,$01
.byte $32, $08, $00 ; ld (flag),a
.byte $c3, $fd, $ff ; jp $FFFD
flag := *
.byte $00 ; flag: .db $00
.endproc
.assert Z80Routine > Z80Routine::target + .sizeof(Z80Routine), error, "Z80 collision"
;;; Input: A = $Cn where n = slot number
;;; Output: C=1 if Z80 found in slot
.proc DetectZ80
;; Location to poke to invoke Z80
sta store+1
;; Convert $Cn to $En, update Z80 code
ora #$E0
sta Z80Routine::patch
;; Clear detection flag
copy #0, Z80Routine::flag
;; Put routine in place
jsr SwapRoutine
;; Try to invoke Z80
php
sei
store := *+1
sta $C000 ; self-modified
plp
;; Restore memory
jsr SwapRoutine
;; Flag will be set to 1 by routine if Z80 was present.
lda Z80Routine::flag
ror ; move flag into carry
rts
.proc SwapRoutine
ldx #.sizeof(Z80Routine)-1
: ldy Z80Routine::target,x
lda Z80Routine,x
sta Z80Routine::target,x
tya
sta Z80Routine,x
dex
bpl :-
rts
.endproc
.endproc
;;; ============================================================
;;; NSC driver - modified as needed and copied into ProDOS
;;; ============================================================
driver:
php
sei
lda PTRIG ; Slow ZIP, IIc+ accelerator, etc
lda $C00B ; Ultrawarp bug workaround c/o @bobbimanners
ld4: lda $CFFF ; self-modified ($CFFF or RDCXROM)
pha
st1: sta $C300 ; self-modified ($Cn00 or INTCXROM)
ld1: lda $C304 ; self-modified ($Cn04)
ldx #8
;; --------------------------------------------------
;; Unlock the NSC by bit-banging.
uloop:
unlock_addr := *+1
lda unlock-1,x ; self-modified (during relocation)
sec
ror a ; a bit at a time
: pha
lda #0
rol a
tay
ld2: lda $C300,y ; self-modified ($Cn00)
pla
lsr a
bne :-
dex
bne uloop
;; --------------------------------------------------
;; Read 8 bytes * 8 bits of clock data, push onto stack
tmp := $200
ldx #8
bloop: ldy #8
st2:
: lda $C304 ; self-modified ($Cn04)
ror a
ror tmp
dey
bne :-
;; BCD to Binary - slow but tiny
lda tmp ; A = value
ldy #$FF ; result = -1
sec
sed
: iny ; result += 1
sbc #1 ; value -= 1
bcs :-
cld
tya ; A = result
;; Push to stack
pha
dex
bne bloop
;; --------------------------------------------------
;; Now stack has y/m/d/w/H/M/S/F
pla ; year
sta DATELO+1
pla ; month
asl
asl
asl
asl
asl
sta DATELO
rol DATELO+1
pla ; day
ora DATELO
sta DATELO
pla ; skip week
pla ; hour
sta TIMELO+1
pla ; minute
sta TIMELO
pla ; skip seconds
pla ; skip fraction
;; --------------------------------------------------
;; Finish up
pla
bmi done
st4: sta $CFFF ; self-modified ($CFFF or SLOTCXROM)
done: plp
rts
unlock:
;; NSC unlock sequence
.byte $5C, $A3, $3A, $C5
.byte $5C, $A3, $3A, $C5
sizeof_driver := * - driver
.assert sizeof_driver <= 125, error, "Clock code must be <= 125 bytes"
;;; ************************************************************
.ifndef JUMBO_CLOCK_DRIVER
.include "../../inc/driver_postamble.inc"
.endif ; JUMBO_CLOCK_DRIVER
;;; ************************************************************

View File

@ -0,0 +1,581 @@
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "opcodes.inc"
.include "../../inc/apple2.inc"
.include "../../inc/macros.inc"
.include "../../inc/prodos.inc"
;;; ------------------------------------------------------------
.define PRODUCT "No-Slot Clock"
;;; ------------------------------------------------------------
.org $2000
.proc main
jsr detect_nsc
bcc :+
rts
:
;; --------------------------------------------------
;; Prompt for Date
date:
jsr zstrout
scrcode "\rDate: WWW MM/DD/YY\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08"
.byte 0
jsr GETLN2
lda INPUT_BUFFER+4
jsr shift_into_tmp
lda INPUT_BUFFER+5
jsr shift_into_tmp
sta set_month
jsr bcd_to_binary
cmp #13
bcs date
lda INPUT_BUFFER+7
jsr shift_into_tmp
lda INPUT_BUFFER+8
jsr shift_into_tmp
sta set_date
jsr bcd_to_binary
cmp #32
bcs date
lda INPUT_BUFFER+10
jsr shift_into_tmp
lda INPUT_BUFFER+11
jsr shift_into_tmp
sta set_year
ldx #0
ldy #1
dow_loop:
CHAR_MASK = $5F ; clear high bit and force uppercase
lda INPUT_BUFFER+0
and #CHAR_MASK
cmp wkdays,x
bne next
lda INPUT_BUFFER+1
and #CHAR_MASK
cmp wkdays+1,x
bne next
lda INPUT_BUFFER+2
and #CHAR_MASK
cmp wkdays+2,x
bne next
sty set_dow
jmp time
next: inx
inx
inx
iny
cpy #8
bcc dow_loop
jmp date
;; --------------------------------------------------
;; Prompt for Time
time:
jsr zstrout
scrcode "\rTime: HH:MM:SS\x08\x08\x08\x08\x08\x08\x08\x08"
.byte 0
jsr GETLN2
lda INPUT_BUFFER+0
jsr shift_into_tmp
lda INPUT_BUFFER+1
jsr shift_into_tmp
sta set_hours
jsr bcd_to_binary
cmp #24
bcs time
lda INPUT_BUFFER+3
jsr shift_into_tmp
lda INPUT_BUFFER+4
jsr shift_into_tmp
sta set_minutes
jsr bcd_to_binary
cmp #60
bcs time
lda INPUT_BUFFER+6
jsr shift_into_tmp
lda INPUT_BUFFER+7
jsr shift_into_tmp
sta set_seconds
jsr bcd_to_binary
cmp #60
bcs time
;; --------------------------------------------------
;; Set NSC
jmp set_datetime
;;; --------------------------------------------------
bcd_to_binary:
ldy #$FF ; result = -1
sec
sed
: iny ; result += 1
sbc #1 ; value -= 1
bcs :-
cld
tya ; A = result
rts
;;; --------------------------------------------------
shift_into_tmp:
pha
lda tmp
asl
asl
asl
asl
sta tmp
pla
and #%00001111
ora tmp
sta tmp
rts
tmp: .byte 0
.endproc
;;; ------------------------------------------------------------
set_year: .byte 0
set_month: .byte 0
set_date: .byte 0
set_dow: .byte 0
set_hours: .byte 0
set_minutes: .byte 0
set_seconds: .byte 0
set_hundredths: .byte 0
wkdays: .byte "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"
;;; ------------------------------------------------------------
.proc detect_nsc
;; Preserve date/time
ldy #3 ; copy 4 bytes
: lda DATELO,y
sta saved,y
dey
bpl :-
;; Check slot ROMs
lda #>$CFFF
ldy #<$CFFF
sta ld4+2
sty ld4+1
sta st4+2
sty st4+1
lda #0
sta slot
lda #3 ; treat slot 0 as slot 3
sloop: ora #$C0 ; A=$Cs
pha
jsr DetectZ80
pla ; A=$Cs
bcs next ; Z80 present, skip this slot
sta st1+2
rloop: sta ld1+2
sta ld2+2
sta st2+2
lda #3 ; 3 tries - need valid results each time
sta tries
try: jsr driver ; try reading date/time
lda DATELO+1 ; check result
ror a
lda DATELO
rol a
rol a
rol a
rol a
and #$0F
beq next
cmp #13 ; month
bcs next
lda DATELO
and #$1F
beq next
cmp #32 ; day
bcs next
lda TIMELO+1
cmp #24 ; hours
bcs next
lda TIMELO
cmp #60 ; minutes
bcs next
dec tries
bne try
beq found
next: inc slot
lda slot
cmp #8
bcc sloop ; next slot
bne not_found
;; Not found in slot ROM, try main ROMs ???
lda #>$C015 ; $C015 = RDCXROM (internal or slot ROM?)
ldy #<$C015
sta ld4+2
sty ld4+1
ldy #$07 ; $C007 = INTCXROM (read internal ROM)
sta st1+2
sty st1+1
dey ; $C006 = SLOTCXROM (read slot ROM)
sta st4+2
sty st4+1
lda #>$C800
bne rloop
;; Restore date/time
not_found:
ldy #3
: lda saved,y
sta DATELO,y
dey
bpl :-
;; Show failure message
jsr zstrout
scrcode PRODUCT, " - Not Found."
.byte 0
sec ; failure
rts
found:
clc ; success
rts
saved: .byte 0, 0, 0, 0
tries: .byte 3
slot: .byte 0
.endproc
;;; ------------------------------------------------------------
;;; Detect Z80
;;; ------------------------------------------------------------
;;; This routine gets swapped into $0FFD for execution
.proc Z80Routine
target := $0FFD
;; .org $FFFD
patch := *+2
.byte $32, $00, $e0 ; ld ($Es00),a ; s=slot being probed turn off Z80, next PC is $0000
.byte $3e, $01 ; ld a,$01
.byte $32, $08, $00 ; ld (flag),a
.byte $c3, $fd, $ff ; jp $FFFD
flag := *
.byte $00 ; flag: .db $00
.endproc
.assert Z80Routine > Z80Routine::target + .sizeof(Z80Routine), error, "Z80 collision"
;;; Input: A = $Cn where n = slot number
;;; Output: C=1 if Z80 found in slot
.proc DetectZ80
;; Location to poke to invoke Z80
sta store+1
;; Convert $Cn to $En, update Z80 code
ora #$E0
sta Z80Routine::patch
;; Clear detection flag
copy #0, Z80Routine::flag
;; Put routine in place
jsr SwapRoutine
;; Try to invoke Z80
php
sei
store := *+1
sta $C000 ; self-modified
plp
;; Restore memory
jsr SwapRoutine
;; Flag will be set to 1 by routine if Z80 was present.
lda Z80Routine::flag
ror ; move flag into carry
rts
.proc SwapRoutine
ldx #.sizeof(Z80Routine)-1
: ldy Z80Routine::target,x
lda Z80Routine,x
sta Z80Routine::target,x
tya
sta Z80Routine,x
dex
bpl :-
rts
.endproc
.endproc
;;; ============================================================
;;; NSC driver - modified as needed and copied into ProDOS
;;; ============================================================
driver:
php
sei
lda $C00B ; Ultrawarp bug workaround c/o @bobbimanners
ld4: lda $CFFF ; self-modified ($CFFF or RDCXROM)
pha
st1: sta $C300 ; self-modified ($Cn00 or INTCXROM)
ld1: lda $C304 ; self-modified ($Cn04)
ldx #8
;; --------------------------------------------------
;; Unlock the NSC by bit-banging.
uloop:
unlock_addr := *+1
lda unlock-1,x ; self-modified (during relocation)
sec
ror a ; a bit at a time
: pha
lda #0
rol a
tay
ld2: lda $C300,y ; self-modified ($Cn00)
pla
lsr a
bne :-
dex
bne uloop
;; --------------------------------------------------
;; Read 8 bytes * 8 bits of clock data, push onto stack
tmp := $200
ldx #8
bloop: ldy #8
st2:
: lda $C304 ; self-modified ($Cn04)
ror a
ror tmp
dey
bne :-
;; BCD to Binary - slow but tiny
lda tmp ; A = value
ldy #$FF ; result = -1
sec
sed
: iny ; result += 1
sbc #1 ; value -= 1
bcs :-
cld
tya ; A = result
;; Push to stack
pha
dex
bne bloop
;; --------------------------------------------------
;; Now stack has y/m/d/w/H/M/S/F
pla ; year
sta DATELO+1
pla ; month
asl
asl
asl
asl
asl
sta DATELO
rol DATELO+1
pla ; day
ora DATELO
sta DATELO
pla ; skip week
pla ; hour
sta TIMELO+1
pla ; minute
sta TIMELO
pla ; skip seconds
pla ; skip fraction
;; --------------------------------------------------
;; Finish up
pla
bmi done
st4: sta $CFFF ; self-modified ($CFFF or SLOTCXROM)
done: plp
rts
unlock:
;; NSC unlock sequence
.byte $5C, $A3, $3A, $C5
.byte $5C, $A3, $3A, $C5
sizeof_driver := * - driver
.assert sizeof_driver <= 125, error, "Clock code must be <= 125 bytes"
;;; ============================================================
.proc set_datetime
jsr patch_from_driver
php
sei
lda $C00B ; Ultrawarp bug workaround c/o @bobbimanners
ld4: lda $CFFF ; self-modified ($CFFF or RDCXROM)
pha
st1: sta $C300 ; self-modified ($Cn00 or INTCXROM)
ld1: lda $C304 ; self-modified ($Cn04)
ldx #8
;; --------------------------------------------------
;; Unlock the NSC by bit-banging.
uloop:
unlock_addr := *+1
lda unlock-1,x ; self-modified (during relocation)
sec
ror a ; a bit at a time
: pha
lda #0
rol a
tay
ld2: lda $C300,y ; self-modified ($Cn00)
pla
lsr a
bne :-
dex
bne uloop
;; --------------------------------------------------
;; Push data onto stack
lda set_year
pha
lda set_month
pha
lda set_date
pha
lda set_dow
pha
lda set_hours
pha
lda set_minutes
pha
lda set_seconds
pha
lda #$00 ; Hundredths
pha
;; --------------------------------------------------
;; Loop over and write all 64 bits into register
tmp := $200
lda #8 ; bytes
sta count
bloop: ldx #8 ; bits
: pla ; current byte
ror a ; shift out bit to write
pha ; not done with this
lda #0
rol a ; shift into low bit
tay ; and into Y to use as index
st2:
lda $C300,y ; self-modified ($Cn00)
dex ; next bit
bne :-
pla ; next byte
dec count
bne bloop
;; --------------------------------------------------
;; Finish up
pla
bmi done
st4: sta $CFFF ; self-modified ($CFFF or SLOTCXROM)
done: plp
rts
count: .byte 0
.endproc
;;; --------------------------------------------------
.proc patch_from_driver
copy ld1+2, set_datetime::ld1+2
copy ld2+2, set_datetime::ld2+2
copy16 ld4+1, set_datetime::ld4+1
copy16 st1+1, set_datetime::st1+1
copy st2+2, set_datetime::st2+2
copy16 st4+1, set_datetime::st4+1
rts
.endproc
;;; ------------------------------------------------------------
;;; Output a high-ascii, null-terminated string.
;;; String immediately follows the JSR.
.proc zstrout
ptr := $A5
pla ; read address from stack
sta ptr
pla
sta ptr+1
bne skip ; always (since data not on ZP)
next: jsr COUT
skip: inc ptr
bne :+
inc ptr+1
: ldy #0
lda (ptr),y
bne next
lda ptr+1 ; restore address to stack
pha
lda ptr
pha
rts
.endproc

46
clocks/romx/Makefile Normal file
View File

@ -0,0 +1,46 @@
CAFLAGS = --target apple2enh --list-bytes 0
LDFLAGS = --config apple2-asm.cfg
OUTDIR = ../../out
HEADERS = $(wildcard *.inc) $(wildcard ../../inc/*.inc)
TARGETS = \
$(OUTDIR)/romxrtc.system.SYS \
$(OUTDIR)/romxrtc.setup.SYS
LOG_SUCCESS=1
LOG_FAILURE=1
# For timestamps
MM = $(shell date "+%-m")
DD = $(shell date "+%-d")
YY = $(shell date "+%-y")
DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY) \
-D LOG_SUCCESS=$(LOG_SUCCESS) -D LOG_FAILURE=$(LOG_FAILURE)
XATTR := $(shell command -v xattr 2> /dev/null)
.PHONY: clean all
all: $(OUTDIR) $(TARGETS)
$(OUTDIR):
mkdir -p $(OUTDIR)
clean:
rm -f $(OUTDIR)/*.o
rm -f $(OUTDIR)/*.list
rm -f $(TARGETS)
$(OUTDIR)/%.o: %.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.setup.o: %.system.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) -D BUILD_SETUP_FILE --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.SYS: $(OUTDIR)/%.o
ld65 $(LDFLAGS) -o $@ $<
ifdef XATTR
xattr -wx prodos.AuxType '00 20' $@
endif

5
clocks/romx/README.md Normal file
View File

@ -0,0 +1,5 @@
# ROMX ProDOS Clock Driver
Original source c/o Jeff Mazur.
`ROMXRTC.SETUP` is for use with [SETUP.SYSTEM](../../setup/README.md).

View File

@ -0,0 +1,332 @@
;;; ROMX ProDOS RTC Driver
;;; Ver 0.91
;;; Ver 0.92 Added ZIP slowdowns
;;; Ver 0.93 Moved StubLoc to $0110
;;; Ver 0.94 Moved StubLoc to RTC_BUF + 7. Save/restore in stack
;;; Ver 0.95 Moved MaskTable to after Thunderclock reloc area
;;;
;;; Modifications by Joshua Bell inexorabletash@gmail.com
;;; * Converted to ca65 syntax and adapted to driver wrapper.
.ifndef JUMBO_CLOCK_DRIVER
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "opcodes.inc"
.include "../../inc/apple2.inc"
.include "../../inc/macros.inc"
.include "../../inc/prodos.inc"
.endif ; JUMBO_CLOCK_DRIVER
;;; Uncomment the following to "fake" a clock with a fixed date.
;;; Used for testing without a real ROMX around.
;;; FAKE_CLOCK = 1
;;; ************************************************************
.ifndef JUMBO_CLOCK_DRIVER
.include "../../inc/driver_preamble.inc"
.endif ; JUMBO_CLOCK_DRIVER
;;; ************************************************************
ZipSlo := $C0E0 ; ZIP CHIP slowdown
;;; ROMX locations
FWReadClock := $D8F0 ; Firmware clock driver routine
SigCk := $DFFE ; ROMX sig bytes
SEL_MBANK := $F851 ; Select Main bank reg
;;; ============================================================
;;;
;;; Driver Installer
;;;
;;; ============================================================
.undef PRODUCT
.define PRODUCT "ROMX Clock"
;;; ============================================================
;;; Ensure there is not a previous clock driver installed.
.ifdef FAKE_CLOCK
maybe_install_driver := install_driver
.else
.proc maybe_install_driver
lda MACHID
and #$01 ; existing clock card?
beq detect_romx ; nope, check for ROMX
rts ; yes, done!
.endproc
;;; ------------------------------------------------------------
.proc detect_romx
;; Try to detect ROMX and RTC
bit ROMIN2 ; enable ROM
bit ZipSlo ; disable ZIP
bit $FACA ; enable ROMXe, temp bank 0
bit $FACA
bit $FAFE
lda SigCk ; Check for ROMX signature bytes
cmp #$4A
bne nope
lda SigCk+1
cmp #$CD
bne nope
lda FWReadClock ; is RTC code there?
cmp #$AD
bne nope
clc ; found clock!
bcc :+
nope: sec ; not found
: bit SEL_MBANK ; restore original bank (unconditionally)
bcc install_driver ; found clock!
.ifndef JUMBO_CLOCK_DRIVER
.if ::LOG_FAILURE
;; Show failure message
jsr log_message
scrcode PRODUCT, " - Not Found."
.byte 0
.endif ; ::LOG_FAILURE
.endif ; JUMBO_CLOCK_DRIVER
sec ; failure
rts
.endproc
.endif
;;; ------------------------------------------------------------
;;; Install ROMX RTC Driver. Copy into address at DATETIME vector,
;;; update the vector and update MACHID bits to signal a clock
;;; is present.
.proc install_driver
ptr := $A5
;; Update absolute addresses within driver
lda DATETIME+1
sta ptr
lda DATETIME+2
sta ptr+1
lda ptr
clc
adc RELOC1
sta RELOC1
lda ptr + 1
adc RELOC1 + 1
sta RELOC1 + 1
lda ptr
clc
adc RELOC2
sta RELOC2
lda ptr + 1
adc RELOC2 + 1
sta RELOC2 + 1
;; Copy driver into appropriate bank
lda RWRAM1
lda RWRAM1
ldy #ClockDrvSize-1
loop: lda ClockDrv,y
sta (ptr),y
dey
bpl loop
;; Set the "Recognizable Clock Card" bit
lda MACHID
ora #$01
sta MACHID
lda #OPC_JMP_abs
sta DATETIME
;; Invoke the driver to init the time
jsr DATETIME
lda ROMIN2
.if ::LOG_SUCCESS
;; Display success message
jsr log_message
scrcode PRODUCT, " - "
.byte 0
;; Display the current date
jsr cout_date
.endif ; ::LOG_SUCCESS
clc ; success
rts ; done!
.endproc
;;; ============================================================
;;; ROMX RTC driver - Relocated into ProDOS clock driver space
;;; ============================================================
;;; ROMX Firmware writes RTC data into this fixed location.
;;; It risks conflicting with some applications (e.g. A2DeskTop),
;;; so the data is saved/restored around clock reads.
RTC_BUF := $02B0 ; use keyboard buffer
StubLoc := RTC_BUF+7 ; RAM stub for ROMX
ClockDrv:
;; --------------------------------------------------
;; Enter driver
php
sei
;; --------------------------------------------------
;; Copy the stub to RAM, and preserve RTC_BUF
ldx #RamStubEnd-RamStub+7 ; preserve and copy stub to RAM
: lda RTC_BUF,x
pha
RELOC1 := *+1
lda RamStub-ClockDrv-7,x ; self-modified during relocation
sta RTC_BUF,x
dex
bpl :-
;; --------------------------------------------------
;; Read the clock into `RTC_BUF`
jsr StubLoc
;; --------------------------------------------------
;; Strip non-number bits, convert decimal to binary, push to stack
ldy #6
bufloop:
lda RTC_BUF,y
RELOC2 := *+1
and MaskTable-1-ClockDrv,y ; self-modified during relocation
;; BCD to Binary
;; On entry, A=BCD value &00-&99
;; On exit, A=binary value 0-99
ldx #$FF ; Start with result=-1
sec ; Prepare for subtraction
sed ; Switch to Decimal arithmetic
: inx ; Add 1 to result
sbc #1 ; Subtract 1 with BCD arithmetic
bcs :- ; Loop until BCD value < 0
cld ; Switch back to Binary arithmetic
txa ; return in A
;; Push to stack
pha
dey
bne bufloop ; 6..1
;; --------------------------------------------------
;; Pull and place values into ProDOS time locations
;; (`RTC_BUF`+0 is not pushed)
pla ; `RTC_BUF`+1 = minute
sta TIMELO
pla ; `RTC_BUF`+2 = hour
sta TIMEHI
pla ; `RTC_BUF`+3 = weekday (unused)
pla ; `RTC_BUF`+4 = day
sta DATELO
pla ; `RTC_BUF`+5 = month
asl a
asl a
asl a
asl a
asl a ; MSB will merge into DATEHI
ora DATELO ; merge with day
sta DATELO
pla ; `RTC_BUF`+6 = year
sta DATEHI
rol DATEHI ; merge with MSB from month
;; --------------------------------------------------
;; Restore what was originally at `RTC_BUF`
ldx #0
: pla
sta RTC_BUF,x
inx
cpx #RamStubEnd-RamStub+7+1
bne :-
;; --------------------------------------------------
;; Exit driver
plp
rts
RamStub:
.ifndef FAKE_CLOCK
;; Really read the ROMX RTC
bit ROMIN2 ; enable ROM
bit ZipSlo ; disable ZIP
bit $FACA ; enable ROMXe, temp bank 0
bit $FACA
bit $FAFE
jsr FWReadClock ; Call ROMX to read clock
bit SEL_MBANK ; restore original bank
bit LCBANK1 ; restore LC w/write
bit LCBANK1
.else
;; No ROMX RTC around? Provide fake data for testing.
;; October 5, 2021 12:34:56
lda #$56 ; sec
sta RTC_BUF+0
lda #$34 ; min
sta RTC_BUF+1
lda #$12 ; hr
sta RTC_BUF+2
lda #$05 ; date
sta RTC_BUF+4
lda #$10 ; month
sta RTC_BUF+5
lda #$21 ; year
sta RTC_BUF+6
.endif
rts
RamStubEnd := *
.assert RamStubEnd - RamStub < $20, error, "Stub too long"
MaskTable:
.byte $7f, $3f, $07, $3f, $1f, $ff
;; .... min hour wkdy date mnth year (`RTC_BUF` bytes 1..6)
ClockDrvEnd := *
ClockDrvSize = ClockDrvEnd - ClockDrv
.assert ClockDrvSize <= 125, error, \
.sprintf("Clock driver must be <= 125 bytes, was %d bytes", ClockDrvSize)
;;; ************************************************************
.ifndef JUMBO_CLOCK_DRIVER
.include "../../inc/driver_postamble.inc"
.endif ; JUMBO_CLOCK_DRIVER
;;; ************************************************************

View File

@ -1,120 +0,0 @@
;;; ------------------------------------------------------------
;;; ASCII
BELL := $07
CR := $0D
;;; ------------------------------------------------------------
;;; Constants
MAX_DW := $FFFF
;;; ------------------------------------------------------------
;;; Softswitches
CLR80VID := $C00C ; 40 Columns
ROMIN2 := $C082 ; Read ROM; no write
RWRAM1 := $C08B ; Read/write RAM bank 1
;;; ------------------------------------------------------------
;;; ProDOS
PRODOS := $BF00
DATETIME := $BF06
DEVNUM := $BF30
BITMAP := $BF58
BITMAP_SIZE := 24 ; 24 bytes in system bit map
DATELO := $BF90
TIMELO := $BF92
MACHID := $BF98
SYS_ADDR := $2000 ; Load address for SYSTEM files
PATHNAME := $0280 ; Pathname of loaded system file
;;; MLI commands
MLI_QUIT := $65
MLI_READ_BLOCK := $80
MLI_GET_TIME := $82
MLI_OPEN := $C8
MLI_READ := $CA
MLI_CLOSE := $CC
.macro PRODOS_CALL call, params
jsr PRODOS
.byte call
.addr params
.endmacro
;;; Volume Directory Block Header structure
.scope VolumeDirectoryBlockHeader
prev_block := $00
next_block := $02
entry_length := $23
entries_per_block := $24
header_length := $2B
.endscope
;; File Entry structure
.scope FileEntry
storage_type := $00
name_length := $00
file_name := $01
file_type := $10
.endscope
;;; ------------------------------------------------------------
;;; Monitor
INIT := $FB2F
MON_HOME := $FC58
CROUT := $FD8E
PRBYTE := $FDDA
COUT := $FDED
SETNORM := $FE84
SETKBD := $FE89
SETVID := $FE93
;;; ------------------------------------------------------------
;;; I/O Registers (for Slot 2)
TDREG := $C088 + $20 ; ACIA Transmit Register (write)
RDREG := $C088 + $20 ; ACIA Receive Register (read)
STATUS := $C089 + $20 ; ACIA Status/Reset Register
COMMAND := $C08A + $20 ; ACIA Command Register (read/write)
CONTROL := $C08B + $20 ; ACIA Control Register (read/write)
;;; ------------------------------------------------------------
;;; Length-prefixed string
.macro PASCAL_STRING arg
.byte .strlen(arg)
.byte arg
.endmacro
;;; ------------------------------------------------------------
;;; Define a string with high bits set
;;; e.g. HIASCII "Ding ding", $7, $7
.macro HIASCII arg, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9
.if .blank(arg)
.exitmacro
.endif
.if .match ({arg}, "") ; string?
.repeat .strlen(arg), i
.byte .strat(arg, i) | $80
.endrep
.else ; otherwise assume number/char/identifier
.byte (arg | $80)
.endif
HIASCII arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9
.endmacro
;;; Like HIASCII, but null-terminated
.macro HIASCIIZ arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9
HIASCII arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9
.byte 0
.endmacro
;;; Set the high bit on the passed byte
.define HI(c) ((c)|$80)

View File

@ -1,737 +0,0 @@
;;; The Cricket Clock - ProDOS System
;;; Adapted from /CRICKET/PRODOS.MOD
;;; Original: Street Electronics Corporation (C) 1984
;;; Adapted from: /NO.SLOT.CLOCK/NS.CLOCK.SYSTEM
;;; Original by "CAP" 04/21/91
;;; http://www.apple2.org.za/gswv/a2zine/GS.WorldView/v1999/Oct/MISC/NSC.Disk.TXT
.setcpu "6502"
.linecont +
.include "apple2.inc"
.include "opcodes.inc"
.include "./common.inc"
;;; ------------------------------------------------------------
data_buffer = $1800
read_delay_hi = $3 * 3 ; ($300 iterations is normal * 3.6MHz)
.define SYSTEM_SUFFIX ".SYSTEM"
.define PRODUCT "Cricket Clock"
;;; ------------------------------------------------------------
.org $1000
;; Loaded at $2000 but relocates to $1000
;;; ------------------------------------------------------------
sys_start:
sec
bcs relocate
.byte MM, DD, YY ; version date stamp
;;; ------------------------------------------------------------
;;; Relocate this code from $2000 (.SYSTEM start location) to $1000
;;; and start executing there. This is done so that the next .SYSTEM
;;; file can be loaded/run at $2000.
.proc relocate
src := SYS_ADDR
dst := $1000
ldx #(sys_end - sys_start + $FF) / $100 ; pages
ldy #0
load: lda src,y ; self-modified
load_hi := *-1
sta dst,y ; self-modified
store_hi := *-1
iny
bne load
inc load_hi
inc store_hi
dex
beq find_self_name ; done
jmp load
.endproc
;;; ------------------------------------------------------------
;;; Identify the name of this SYS file, which should be present at
;;; $280 with or without a path prefix. This is used when searching
;;; for the next .SYSTEM file to execute.
.proc find_self_name
;; Search pathname buffer backwards for '/', then
;; copy name into |self_name|; this is used later
;; to find/invoke the next .SYSTEM file.
;; Find '/' (which may not be present, prefix is optional)
lda #0
sta $A8
ldx PATHNAME
beq pre_install
floop: inc $A8
dex
beq copy
lda PATHNAME,x
eor #'/'
asl a
bne floop
;; Copy name into |self_name| buffer
copy: ldy #0
cloop: iny
inx
lda PATHNAME,x
sta self_name,y
cpy $A8
bcc cloop
sty self_name
.endproc
;; Fall through...
;;; ------------------------------------------------------------
;;; Before installing, get the system to a known state and
;;; ensure there is not a previous clock driver installed.
.proc pre_install
cld
bit ROMIN2
;; Update reset vector - re-invokes this code.
lda #<pre_install
sta $03F2
lda #>pre_install
sta $03F3
eor #$A5
sta $03F4
;; Quit 80-column firmware
lda #$95 ; Ctrl+U (quit 80 col firmware)
jsr COUT
;; Reset stack
ldx #$FF
txs
;; Reset I/O
sta CLR80VID
sta CLRALTCHAR
jsr SETVID
jsr SETKBD
jsr SETNORM
jsr INIT
;; Update System Bit Map
ldx #BITMAP_SIZE-1
lda #%00000001 ; protect page $BF
: sta BITMAP,x
lda #%00000000 ; nothing else protected until...
dex
bne :-
lda #%11001111 ; ZP ($00), stack ($01), text page 1 ($04-$07)
sta BITMAP
lda MACHID
and #$88 ; IIe or IIc (or IIgs) ?
bne :+
lda #$DF
sta lowercase_mask ; lower case to upper case
: lda MACHID
and #$01 ; existing clock card?
beq detect_cricket ; nope, check for Cricket
;; Chain with no message
jmp launch_next_sys_file
.endproc
;;; ------------------------------------------------------------
;;; Detect Cricket. Detect SSC and if present probe device.
.proc detect_cricket
;; Check Slot 2 for SSC. ID bytes per:
;; Apple II Technical Note #8: Pascal 1.1 Firmware Protocol ID Bytes
lda $C205
cmp #$38
bne ssc_not_found
lda $C207
cmp #$18
bne ssc_not_found
lda $C20B
cmp #$01
bne ssc_not_found
lda $C20C
cmp #$31
bne ssc_not_found
beq init_ssc
ssc_not_found:
jmp not_found
;; Init SSC and try the "Read Cricket ID code" sequence.
init_ssc:
lda COMMAND ; save status of SSC registers
sta saved_command
lda CONTROL
sta saved_control
;; Configure SSC
lda #%00001011 ; no parity/echo/interrupts, RTS low, DTR low
sta COMMAND
lda #%10011110 ; 9600 baud, 8 data bits, 2 stop bits
sta CONTROL
;; Read Cricket ID code: 00 ($00)
lda #0
jsr sendbyte
;; "The Cricket will return a "C" (195, $C3) followed by a
;; version number (in ASCII) and a carriage return (141, $8D)."
jsr readbyte
bcs cricket_not_found ; timeout
cmp #HI('C') ; = 'C' ?
bne cricket_not_found
jsr readbyte
bcs cricket_not_found ; timeout
bcc digit
: jsr readbyte
bcs cricket_not_found ; timeout
cmp #HI(CR) ; = CR ?
beq cricket_found
digit: cmp #HI('0') ; < '0' ?
bcc cricket_not_found
cmp #HI('9' + 1) ; > '9' ?
bcs cricket_not_found
bcc :-
cricket_found:
jmp install_driver
cricket_not_found:
lda saved_control
sta CONTROL
lda saved_command
sta COMMAND
;; fall through...
not_found:
;; Show failure message
jsr MON_HOME
jsr zstrout
HIASCIIZ CR, CR, CR, PRODUCT, " - Not Found."
jmp launch_next_sys_file
saved_command: .byte 0
saved_control: .byte 0
.endproc
;; Write byte in A
.proc sendbyte
pha
: lda STATUS
and #(1 << 4) ; transmit register empty? (bit 4)
beq :- ; nope, keep waiting
pla
sta TDREG
rts
.endproc
;; Read byte into A, or carry set if timed out
.proc readbyte
tries := $100 * read_delay_hi
counter := $A5
lda #<tries
sta counter
lda #>tries
sta counter+1
check: lda STATUS ; did we get it?
and #(1 << 3) ; receive register full? (bit 3)
bne ready ; yes, we read the value
dec counter
bne check
dec counter+1
bne check
sec ; failed
rts
ready: lda RDREG ; actually read the register
clc
rts
.endproc
;;; ------------------------------------------------------------
;;; Install Cricket Driver. Copy into address at DATETIME vector,
;;; update the vector and update MACHID bits to signal a clock
;;; is present.
.proc install_driver
ptr := $A5
lda DATETIME+1
sta ptr
lda DATETIME+2
sta ptr+1
lda RWRAM1
lda RWRAM1
ldy #sizeof_driver-1
loop: lda driver,y
sta (ptr),y
dey
bpl loop
;; Set the "Recognizable Clock Card" bit
lda MACHID
ora #$01
sta MACHID
lda #OPC_JMP_abs
sta DATETIME
;; Invoke the driver to init the time
jsr DATETIME
;; Display success message
bit ROMIN2
jsr MON_HOME
jsr zstrout
HIASCIIZ CR, CR, CR, PRODUCT, " - Installed "
;; Display the current date
lda DATELO+1 ; month
ror a
pha
lda DATELO
pha
rol a
rol a
rol a
rol a
and #%00001111
jsr cout_number
lda #(HI '/') ; /
jsr COUT
pla ; day
and #%00011111
jsr cout_number
lda #(HI '/') ; /
jsr COUT
pla ; year
jsr cout_number
jsr CROUT
.endproc
;;; ------------------------------------------------------------
;;; Find and invoke the next .SYSTEM file
.proc launch_next_sys_file
;; Update reset vector - now terminates.
lda #<quit
sta $03F2
lda #>quit
sta $03F3
eor #$A5
sta $03F4
ptr := $A5
num := $A7
len := $A8
lda DEVNUM ; stick with most recent device
sta read_block_params_unit_num
jsr read_block
lda data_buffer + VolumeDirectoryBlockHeader::entry_length
sta entry_length_mod
lda data_buffer + VolumeDirectoryBlockHeader::entries_per_block
sta entries_per_block_mod
lda #1
sta num
lda #<(data_buffer + VolumeDirectoryBlockHeader::header_length)
sta ptr
lda #>(data_buffer + VolumeDirectoryBlockHeader::header_length)
sta ptr+1
;; Process directory entry
entry: ldy #FileEntry::file_type ; file_type
lda (ptr),y
cmp #$FF ; type=SYS
bne next
ldy #FileEntry::storage_type
lda (ptr),y
and #$30 ; regular file (not directory, pascal)
beq next
lda (ptr),y
and #$0F ; name_length
sta len
tay
;; Compare suffix - is it .SYSTEM?
ldx #.strlen(SYSTEM_SUFFIX)-1
: lda (ptr),y
cmp suffix,x
bne next
dey
dex
bpl :-
;; Yes; is it *this* .SYSTEM file?
ldy self_name
cpy len
bne handle_sys_file
: lda (ptr),y
cmp self_name,y
bne handle_sys_file
dey
bne :-
sec
ror found_self_flag
;; Move to the next entry
next: lda ptr
clc
adc #$27 ; self-modified: entry_length
entry_length_mod := *-1
sta ptr
bcc :+
inc ptr+1
: inc num
lda num
cmp #$0D ; self-modified: entries_per_block
entries_per_block_mod := *-1
bcc entry
lda data_buffer + VolumeDirectoryBlockHeader::next_block
sta read_block_params_block_num
lda data_buffer + VolumeDirectoryBlockHeader::next_block + 1
sta read_block_params_block_num+1
ora read_block_params_block_num
beq not_found ; last block has next=0
jsr read_block
lda #0
sta num
lda #<(data_buffer + $04)
sta ptr
lda #>(data_buffer + $04)
sta ptr+1
jmp entry
;; Found a .SYSTEM file which is not this one; invoke
;; it if follows this one.
handle_sys_file:
bit found_self_flag
bpl next
;; Compose the path to invoke. First walk self path
;; backwards to '/'.
ldx PATHNAME
beq append
: dex
beq append
lda PATHNAME,x
eor #'/'
asl a
bne :-
;; Now append name of found file.
append: ldy #0
: iny
inx
lda (ptr),y
sta PATHNAME,x
cpy len
bcc :-
stx PATHNAME
jmp invoke_system_file
not_found:
jsr zstrout
HIASCIIZ CR, CR, CR, "* Unable to find next '.SYSTEM' file *", CR
bit KBDSTRB
: lda KBD
bpl :-
bit KBDSTRB
jmp quit
.endproc
;;; ------------------------------------------------------------
;;; Output a high-ascii, null-terminated string.
;;; String immediately follows the JSR.
.proc zstrout
ptr := $A5
pla ; read address from stack
sta ptr
pla
sta ptr+1
bne skip ; always (since data not on ZP)
next: cmp #(HI 'a') ; lower-case?
bcc :+
and lowercase_mask ; make upper-case if needed
: jsr COUT
skip: inc ptr
bne :+
inc ptr+1
: ldy #0
lda (ptr),y
bne next
lda ptr+1 ; restore address to stack
pha
lda ptr
pha
rts
.endproc
;;; ------------------------------------------------------------
;;; COUT a 2-digit number in A
.proc cout_number
ldx #(HI '0')
cmp #10 ; >= 10?
bcc tens
;; divide by 10, dividend(+'0') in x remainder in a
: sbc #10
inx
cmp #10
bcs :-
tens: pha
cpx #(HI '0')
beq units
txa
jsr COUT
units: pla
ora #(HI '0')
jsr COUT
rts
.endproc
;;; ------------------------------------------------------------
lowercase_mask:
.byte $FF ; Set to $DF on systems w/o lower-case
;;; ------------------------------------------------------------
;;; Invoke ProDOS QUIT routine.
.proc quit
PRODOS_CALL MLI_QUIT, quit_params
.byte 0 ; crash if QUIT fails
rts
.proc quit_params
.byte 4 ; param_count
.byte 0 ; quit_type
.word 0000 ; reserved
.byte 0 ; reserved
.word 0000 ; reserved
.endproc
.endproc
;;; ------------------------------------------------------------
;;; Read a disk block.
.proc read_block
PRODOS_CALL MLI_READ_BLOCK, read_block_params
bcs on_error
rts
.endproc
.proc read_block_params
.byte 3 ; param_count
unit_num: .byte $60 ; unit_num
.addr data_buffer ; data_buffer
block_num: .word 2 ; block_num - block 2 is volume directory
.endproc
read_block_params_unit_num := read_block_params::unit_num
read_block_params_block_num := read_block_params::block_num
;;; ------------------------------------------------------------
;;; Load/execute the system file in PATHNAME
.proc invoke_system_file
PRODOS_CALL MLI_OPEN, open_params
bcs on_error
lda open_params_ref_num
sta read_params_ref_num
PRODOS_CALL MLI_READ, read_params
bcs on_error
PRODOS_CALL MLI_CLOSE, close_params
bcs on_error
jmp SYS_ADDR ; Invoke loaded SYSTEM file
.endproc
;;; ------------------------------------------------------------
;;; Error handler - invoked if any ProDOS error occurs.
.proc on_error
pha
jsr zstrout
HIASCIIZ CR, CR, CR, "** Disk Error $"
pla
jsr PRBYTE
jsr zstrout
HIASCIIZ " **", CR
bit KBDSTRB
: lda KBD
bpl :-
bit KBDSTRB
jmp quit
.endproc
;;; ------------------------------------------------------------
.proc open_params
.byte 3 ; param_count
.addr PATHNAME ; pathname
.addr data_buffer ; io_buffer
ref_num:.byte 1 ; ref_num
.endproc
open_params_ref_num := open_params::ref_num
.proc read_params
.byte 4 ; param_count
ref_num:.byte 1 ; ref_num
.addr SYS_ADDR ; data_buffer
.word MAX_DW ; request_count
.word 0 ; trans_count
.endproc
read_params_ref_num := read_params::ref_num
.proc close_params
.byte 1 ; param_count
ref_num:.byte 0 ; ref_num
.endproc
;;; ------------------------------------------------------------
found_self_flag:
.byte 0
suffix: .byte SYSTEM_SUFFIX
self_name:
PASCAL_STRING "CRICKET.SYSTEM"
;;; ------------------------------------------------------------
;;; Cricket Clock Driver - copied into ProDOS
.proc driver
scratch := $3A ; ZP scratch location
;; Initialize
php
sei
lda COMMAND ; save status of command register
pha
;; Configure SSC
lda #%00001011 ; no parity/echo/interrupts, RTS low, DTR low
sta COMMAND
lda #%10011110 ; 9600 baud, 8 data bits, 2 stop bits
sta CONTROL
;; Send command
: lda STATUS
and #(1 << 4) ; transmit register empty? (bit 4)
beq :- ; nope, keep waiting
lda #HI('@') ; '@' command
sta TDREG
read_len := 7 ; read 7 bytes (w/m/d/y/H/M/S)
;; Read response, pushing to stack
ldy #(read_len-1)
rloop: ldx #0 ; x = retry loop counter low byte
lda #read_delay_hi ; scratch = retry loop counter high byte
sta scratch
check: lda STATUS ; did we get it?
and #(1 << 3) ; receive register full? (bit 3)
bne ready ; yes, we read the value
inx ; not yet, so keep trying
bne check ; until counter runs out
dec scratch
bne check
;; Read failed - restore stack and exit
reset: cpy #(read_len-1) ; anything left to restore?
beq done ; nope, exit
pla ; yep, clear it off the stack
iny
bne reset
;; Read succeeded - stuff it on the stack and continue
ready: lda RDREG
pha
dey
bpl rloop
;; Convert pushed response to ProDOS time field
pla ; day of week (unused)
pla ; minute
sta TIMELO ; -- stored as-is (TIMELO 5-0)
pla ; hour
sta TIMELO+1 ; -- stored as-is (TIMELO 12-8)
pla ; year
sta DATELO+1 ; -- will be shifted up by 1 (DATELO 15-9)
pla ; day
and #%00011111 ; -- masked, stored as is (DATELO 4-0)
sta DATELO
pla ; month
asl a ; -- shifted up (DATELO 8-5)
asl a
asl a
asl a
asl a
ora DATELO ; -- merge low 5 bits
sta DATELO
rol DATELO+1
pla ; seconds (unused)
;; Restore prior state
done: pla ; restore saved command state
sta COMMAND
plp
rts
.endproc
sizeof_driver := .sizeof(driver)
.assert sizeof_driver <= 125, error, "Clock code must be <= 125 bytes"
;;; ------------------------------------------------------------
sys_end:

100
inc/apple2.inc Normal file
View File

@ -0,0 +1,100 @@
;;; ============================================================
;;;
;;; More Apple II Symbols
;;;
;;; ============================================================
;;; ============================================================
;;; Soft Switches
;;; ============================================================
;;; I/O Soft Switches / Firmware
RAMRDOFF := $C002
RAMRDON := $C003
RAMWRTOFF := $C004
RAMWRTON := $C005
ALTZPOFF := $C008
ALTZPON := $C009
CLR80VID := $C00C
SET80VID := $C00D
RDALTZP := $C016
RD80STORE := $C018
RDPAGE2 := $C01C
TBCOLOR := $C022 ; IIgs: low nibble is text bg color, high is fg
CLOCKCTL := $C034 ; IIgs: low nibble is border color
BANKSEL := $C073 ; Select RamWorks bank
ROMIN2 := $C082 ; Read ROM; no write
ROMINWB1 := $C089 ; Read ROM; write RAM bank 1
RWRAM1 := $C08B ; Read/write RAM bank 1
;;; ============================================================
;;; I/O Registers (for Slot 2)
;;; ============================================================
TDREG := $C088 + $20 ; ACIA Transmit Register (write)
RDREG := $C088 + $20 ; ACIA Receive Register (read)
STATUS := $C089 + $20 ; ACIA Status/Reset Register
COMMAND := $C08A + $20 ; ACIA Command Register (read/write)
CONTROL := $C08B + $20 ; ACIA Control Register (read/write)
;;; ============================================================
;;; Other Slots
;;; ============================================================
SLOT3 := $C300
;;; ============================================================
;;; Monitor ROM routines
;;; ============================================================
INIT := $FB2F
SETTXT := $FB39
TABV := $FB5B
SETPWRC := $FB6F
VERSION := $FBB3
ZIDBYTE := $FBC0
BELL1 := $FBDD
HOME := $FC58
CLREOL := $FC9C
RDKEY := $FD0C
GETLN := $FD6A ; with prompt character
GETLN2 := $FD6F ; no prompt character
CROUT := $FD8E
PRBYTE := $FDDA
COUT := $FDED
COUT1 := $FDF0
SETINV := $FE80
SETNORM := $FE84
SETKBD := $FE89
SETVID := $FE93
BELL := $FF3A
;;; ============================================================
;;; Applesoft ROM routines
;;; ============================================================
;;; These require various ZP locations to be initialized.
LINPRNT := $ED24 ; print X,A as integer
;;; ============================================================
;;; Well-known RAM locations
;;; ============================================================
INPUT_BUFFER := $200
RESETVEC := $3F2
COL80HPOS := $57B
;;; ============================================================
;;; Applesoft Zero Page Locations
;;; ============================================================
TEMPPT := $52
TEMPST := $55
SHIFT_SIGN_EXT := $A4
SPEEDZ := $F1
FLASH_BIT := $F3

17
inc/ascii.inc Normal file
View File

@ -0,0 +1,17 @@
;;; ============================================================
;;; ASCII Code Points (also used as key codes)
;;; ============================================================
ASCII_BELL = $07 ; bell
ASCII_LEFT = $08 ; left arrow
ASCII_TAB = $09 ; tab
ASCII_DOWN = $0A ; down arrow
ASCII_UP = $0B ; up arrow
ASCII_CR = $0D ; carriage return
ASCII_RIGHT = $15 ; right arrow
ASCII_SYN = $16 ; scroll text window up
ASCII_ETB = $17 ; scroll text window down
ASCII_CLEAR = $18 ; clear
ASCII_EM = $19 ; move cursor to upper left
ASCII_ESCAPE = $1B ; escape
ASCII_DELETE = $7F ; delete

6
inc/driver_postamble.inc Normal file
View File

@ -0,0 +1,6 @@
.ifndef BUILD_SETUP_FILE
poporg
reloc_end := *
.endif

515
inc/driver_preamble.inc Normal file
View File

@ -0,0 +1,515 @@
.define PRODUCT "Unknown"
;;; ------------------------------------------------------------
;; SYS files load at $2000; relocates self to $1000
.org SYS_ADDR
.ifndef BUILD_SETUP_FILE
dst_addr := $1000
jmp relocate
.else
jmp main
.endif
.byte MM, DD, YY ; version date stamp
.ifndef BUILD_SETUP_FILE
;;; ------------------------------------------------------------
;;; Relocate this code from $2000 (.SYSTEM start location) to $1000
;;; and start executing there. This is done so that the next .SYSTEM
;;; file can be loaded/run at $2000.
.proc relocate
src := reloc_start
dst := dst_addr
ldx #(reloc_end - reloc_start + $FF) / $100 ; pages
ldy #0
load: lda src,y ; self-modified
load_hi := *-1
sta dst,y ; self-modified
store_hi := *-1
iny
bne load
inc load_hi
inc store_hi
dex
bne load
jmp main
.endproc
;;; ============================================================
;;; Start of relocated code
reloc_start := *
pushorg dst_addr
;;; ============================================================
;;; Main routine
;;; ============================================================
.proc main
jsr save_chain_info
jsr init_system
jsr maybe_install_driver
jmp launch_next
.endproc
;;; ============================================================
;;; Preserve state needed to chain to next file
;;; ============================================================
.proc save_chain_info
;; --------------------------------------------------
;; Save most recent device for later, when chaining
;; to next .SYSTEM file.
lda DEVNUM
sta devnum
;; --------------------------------------------------
;; Identify the name of this SYS file, which should be present at
;; $280 with or without a path prefix. Search pathname buffer
;; backwards for '/', then copy name into `self_name`.
;; Find '/' (which may not be present, prefix is optional)
ldx PATHNAME
beq no_name
ldy #0 ; Y = length
: lda PATHNAME,x
and #$7f ; ignore high bit
cmp #'/'
beq copy_name
iny
dex
bne :-
;; Copy name into `self_name` buffer
copy_name:
cpy #0
beq no_name
sty self_name
ldx PATHNAME
: lda PATHNAME,x
sta self_name,y
dex
dey
bne :-
;; Done
rts
no_name:
lda #0
sta self_name
rts
.endproc
devnum: .byte 0
self_name: .res 16
;;; ============================================================
;;; Init system state
;;; ============================================================
;;; Before installing, get the system to a known state.
.proc init_system
cld
bit ROMIN2
;; Update reset vector - ProDOS QUIT
lda #<quit
sta RESETVEC
lda #>quit
sta RESETVEC+1
eor #$A5
sta RESETVEC+2
;; Quit 80-column firmware
lda #$95 ; Ctrl+U (quit 80 col firmware)
jsr COUT
;; Reset I/O
sta CLR80VID
sta CLRALTCHAR
sta CLR80COL
jsr SETNORM
jsr INIT
jsr SETVID
jsr SETKBD
;; Update System Bit Map
ldx #BITMAP_SIZE-1
lda #%00000001 ; protect page $BF
: sta BITMAP,x
lda #%00000000 ; nothing else protected until...
dex
bne :-
lda #%11001111 ; ZP ($00), stack ($01), text page 1 ($04-$07)
sta BITMAP
;; Determine lowercase support
lda MACHID
and #$88 ; IIe or IIc (or IIgs) ?
bne :+
lda #$DF
sta lowercase_mask ; lower case to upper case
: rts
.endproc
;;; ============================================================
;;; Find and invoke the next .SYSTEM file
;;; ============================================================
online_buf := $1C00
io_buf := $1C00
dir_buf := $2000
block_len = $200
DEFINE_ON_LINE_PARAMS on_line_params,,online_buf
DEFINE_OPEN_PARAMS open_params, PATHNAME, io_buf
DEFINE_READ_PARAMS read_params, SYS_ADDR, SYS_LEN
DEFINE_READ_PARAMS read_block_params, dir_buf, block_len
DEFINE_CLOSE_PARAMS close_params
.proc launch_next
;; Read directory and look for .SYSTEM files; find this
;; one, and invoke the following one.
ptr := $A5
num := $A7
len := $A8
;; --------------------------------------------------
;; Own name found? If not, just quit
lda self_name
bne :+
jmp quit
;; --------------------------------------------------
;; Find name of boot device, copy into PATHNAME
: lda devnum
sta on_line_params::unit_num
MLI_CALL ON_LINE, on_line_params
bcc :+
jmp on_error
: lda #'/' ; Prefix by '/'
sta PATHNAME+1
lda online_buf
and #$0F ; Mask off length
sta PATHNAME
ldx #0 ; Copy name
: lda online_buf+1,x
sta PATHNAME+2,x
inx
cpx PATHNAME
bne :-
inx ; One more for '/' prefix
stx PATHNAME
;; Open directory
MLI_CALL OPEN, open_params
bcc :+
jmp on_error
: lda open_params::ref_num
sta read_block_params::ref_num
sta close_params::ref_num
;; Read first "block"
MLI_CALL READ, read_block_params
bcc :+
MLI_CALL CLOSE, close_params
jmp on_error
;; Get sizes out of header
: lda dir_buf + VolumeDirectoryHeader::entry_length
sta entry_length_mod
lda dir_buf + VolumeDirectoryHeader::entries_per_block
sta entries_per_block_mod
lda #1
sta num
;; Set up pointers to entry
lda #<(dir_buf + .sizeof(VolumeDirectoryHeader))
sta ptr
lda #>(dir_buf + .sizeof(VolumeDirectoryHeader))
sta ptr+1
;; Process directory entry
entry: ldy #FileEntry::file_type ; file_type
lda (ptr),y
cmp #$FF ; type=SYS
bne next
ldy #FileEntry::storage_type_name_length
lda (ptr),y
and #$30 ; regular file (not directory, pascal)
beq next
lda (ptr),y
and #$0F ; name_length
sta len
tay
;; Compare suffix - is it .SYSTEM?
ldx suffix
: lda (ptr),y
cmp suffix,x
bne next
dey
dex
bne :-
;; Yes; is it *this* .SYSTEM file?
ldy self_name
cpy len
bne handle_sys_file
: lda (ptr),y
cmp self_name,y
bne handle_sys_file
dey
bne :-
sec
ror found_self_flag
;; Move to the next entry
next: lda ptr
clc
adc #$27 ; self-modified: entry_length
entry_length_mod := *-1
sta ptr
bcc :+
inc ptr+1
: inc num
lda num
cmp #$0D ; self-modified: entries_per_block
entries_per_block_mod := *-1
bcc entry
;; Read next "block"
MLI_CALL READ, read_block_params
bcs not_found
;; Set up pointers to entry
lda #0
sta num
lda #<(dir_buf + $04)
sta ptr
lda #>(dir_buf + $04)
sta ptr+1
jmp entry
;; --------------------------------------------------
;; Found a .SYSTEM file which is not this one; invoke
;; it if follows this one.
handle_sys_file:
bit found_self_flag
bpl next
MLI_CALL CLOSE, close_params
;; Compose the path to invoke.
ldx PATHNAME
inx
lda #'/'
sta PATHNAME,x
ldy #0
: iny
inx
lda (ptr),y
sta PATHNAME,x
cpy len
bcc :-
stx PATHNAME
jmp invoke_system_file
not_found:
MLI_CALL CLOSE, close_params
jmp quit
.endproc
;;; ------------------------------------------------------------
;;; Load/execute the system file in PATHNAME
.proc invoke_system_file
MLI_CALL OPEN, open_params
bcs on_error
lda open_params::ref_num
sta read_params::ref_num
sta close_params::ref_num
MLI_CALL READ, read_params
php
MLI_CALL CLOSE, close_params
plp
bcs on_error
jmp SYS_ADDR ; Invoke loaded SYSTEM file
.endproc
;;; ------------------------------------------------------------
;;; Error handler - invoked if any ProDOS error occurs.
.proc on_error
pha
jsr zstrout
scrcode "\r\r* Disk Error $"
.byte 0
pla
jsr PRBYTE
jsr zstrout
scrcode " *\r"
.byte 0
bit KBDSTRB
: lda KBD
bpl :-
bit KBDSTRB
jmp quit
.endproc
.proc quit
MLI_CALL QUIT, quit_params
brk ; crash if QUIT fails
DEFINE_QUIT_PARAMS quit_params
.endproc
;;; ============================================================
;;; Data
suffix:
PASCAL_STRING ".SYSTEM"
found_self_flag:
.byte 0
.else
;;; ============================================================
;;; Main routine
;;; ============================================================
.proc main
jsr maybe_install_driver
MLI_CALL QUIT, quit_params
brk ; crash if QUIT fails
DEFINE_QUIT_PARAMS quit_params
.endproc
.endif
;;; ============================================================
;;; Common Routines
;;; ============================================================
;;; ------------------------------------------------------------
;;; Use this for logging the result of a driver. Uses `zstrout`.
.proc log_message
jsr CROUT
jsr CROUT
;; fall through
.endproc
;;; ------------------------------------------------------------
;;; Output a high-ascii, null-terminated string.
;;; String immediately follows the JSR.
.proc zstrout
ptr := $A5
pla ; read address from stack
sta ptr
pla
sta ptr+1
bne skip ; always (since data not on ZP)
next: cmp #HI('a') ; lower-case?
bcc :+
and lowercase_mask ; make upper-case if needed
: jsr COUT
skip: inc ptr
bne :+
inc ptr+1
: ldy #0
lda (ptr),y
bne next
lda ptr+1 ; restore address to stack
pha
lda ptr
pha
rts
.endproc
lowercase_mask:
.byte $FF ; Set to $DF on systems w/o lower-case
;;; ------------------------------------------------------------
;;; COUT a 2-digit number in A
.proc cout_number
ldx #HI('0')
cmp #10 ; >= 10?
bcc tens
;; divide by 10, dividend(+'0') in x remainder in a
: sbc #10
inx
cmp #10
bcs :-
tens: pha
cpx #HI('0')
beq units
txa
jsr COUT
units: pla
ora #HI('0')
jsr COUT
rts
.endproc
;;; ------------------------------------------------------------
;;; COUT the current ProDOS date
.proc cout_date
lda DATELO+1 ; month
ror a
pha
lda DATELO
pha
rol a
rol a
rol a
rol a
and #%00001111
jsr cout_number
lda #HI('/') ; /
jsr COUT
pla ; day
and #%00011111
jsr cout_number
lda #HI('/') ; /
jsr COUT
pla ; year
jsr cout_number
rts
.endproc

123
inc/macros.inc Normal file
View File

@ -0,0 +1,123 @@
;;; ============================================================
;;; Generic Macros
;;; ============================================================
.define _is_immediate(arg) (.match (.mid (0, 1, {arg}), #))
.define _is_register(arg) (.match ({arg}, x) .or .match ({arg}, y))
.define _is_y_register(arg) (.match ({arg}, y))
.define _immediate_value(arg) (.right (.tcount ({arg})-1, {arg}))
.macro _op_lo op, arg
.if _is_immediate {arg}
op #<_immediate_value {arg}
.else
op arg
.endif
.endmacro
.macro _op_hi op, arg
.if _is_immediate {arg}
op #>_immediate_value {arg}
.else
op arg+1
.endif
.endmacro
;;; ============================================================
;;; Temporary org change, for relocated routines
__pushorg_depth__ .set 0
.macro pushorg addr
::__pushorg_depth__ .set ::__pushorg_depth__ + 1
.ident(.sprintf("__pushorg_saved__%d", ::__pushorg_depth__)) := *
.org addr
.ident(.sprintf("__pushorg_start__%d", ::__pushorg_depth__)) := *
.endmacro
.macro poporg
.org .ident(.sprintf("__pushorg_saved__%d", ::__pushorg_depth__)) + (* - .ident(.sprintf("__pushorg_start__%d", ::__pushorg_depth__)))
::__pushorg_depth__ .set ::__pushorg_depth__ - 1
.endmacro
;;; ============================================================
;;; Length-prefixed string
;;;
;;; Can include control chars by using:
;;;
;;; PASCAL_STRING {"abc",$0D,"def"}
.macro PASCAL_STRING str,res
.local data
.local end
.byte end - data
data: .byte str
end:
.if .paramcount > 1
.res res - (end - data), 0
.endif
.endmacro
;;; ============================================================
;;; Common patterns
.macro copy arg1, arg2, arg3, arg4
.if _is_register {arg2} && _is_register {arg4}
;; indexed load/indexed store
lda arg1,arg2
sta arg3,arg4
.elseif _is_register {arg2}
;; indexed load variant (arg2 is x or y)
lda arg1,arg2
sta arg3
.elseif _is_register {arg3}
;; indexed store variant (arg3 is x or y)
lda arg1
sta arg2,arg3
.else
lda arg1
sta arg2
.endif
.endmacro
;;; Copy 16-bit value
;;; copy16 #$1111, $2222 ; immediate, absolute
;;; copy16 $1111, $2222 ; absolute, absolute
;;; copy16 $1111,x, $2222 ; indirect load, absolute store
;;; copy16 $1111, $2222,x ; absolute load, indirect store
;;; copy16 $1111,x $2222,x ; indirect load, indirect store
;;; copy16 #$1111, $2222,x ; immediate load, indirect store
.macro copy16 arg1, arg2, arg3, arg4
.if _is_register {arg2} && _is_register {arg4}
;; indexed load/indexed store
lda arg1,arg2
sta arg3,arg4
lda arg1+1,arg2
sta arg3+1,arg4
.elseif _is_register {arg2}
;; indexed load variant (arg2 is x or y)
lda arg1,arg2
sta arg3
lda arg1+1,arg2
sta arg3+1
.elseif _is_register {arg3}
;; indexed store variant (arg3 is x or y)
_op_lo lda, {arg1}
sta arg2,arg3
_op_hi lda, {arg1}
sta arg2+1,arg3
.else
_op_lo lda, {arg1}
sta arg2
_op_hi lda, {arg1}
sta arg2+1
.endif
.endmacro
;;; ============================================================
;;; Set the high bit on the passed byte
.define HI(c) ((c)|$80)

478
inc/prodos.inc Normal file
View File

@ -0,0 +1,478 @@
;;; ============================================================
;;;
;;; ProDOS MLI
;;;
;;; ============================================================
;;; Entry point / Global Page
MLI := $BF00 ; Entry point
DATETIME := $BF06 ; JMP to clock routine
DEVADR := $BF10 ; Device driver addresses ($BF10-$BF2F)
NODEV := $BF10 ; "No Device Connected" entry (slot 0)
DEVNUM := $BF30 ; Most recent accessed device
DEVCNT := $BF31 ; Number of on-line devices minus 1
DEVLST := $BF32 ; Up to 14 units ($BF32-$BF3F)
BITMAP := $BF58 ; System memory bitmap
BITMAP_SIZE = $18 ; Bits for pages $00 to $BF
DATELO := $BF90 ; Date lo
DATEHI := $BF91 ; Date hi
TIMELO := $BF92 ; Time lo
TIMEHI := $BF93 ; Time hi
LEVEL := $BF94 ; File level
MACHID := $BF98 ; Machine ID
SLTBYT := $BF99 ; '1' bits indicate rom in slot (bit#)
IVERSION := $BFFD ; Interpreter Version
KVERSION := $BFFF ; ProDOS Kernel Version
;;; Patch Locations
SELECTOR := $D100
BLOCK_SIZE = $200
PATHNAME := $280
SYS_ADDR := $2000 ; Load address for SYS files
SYS_LEN = $BF00 - SYS_ADDR ; Maximum SYS file length
;;; ============================================================
;;; MLI Calls
;;; ============================================================
;;; Housekeeping Calls
CREATE = $C0
DESTROY = $C1
RENAME = $C2
SET_FILE_INFO = $C3
GET_FILE_INFO = $C4
ON_LINE = $C5
SET_PREFIX = $C6
GET_PREFIX = $C7
;;; Filing Calls
OPEN = $C8
NEWLINE = $C9
READ = $CA
WRITE = $CB
CLOSE = $CC
FLUSH = $CD
SET_MARK = $CE
GET_MARK = $CF
SET_EOF = $D0
GET_EOF = $D1
SET_BUF = $D2
GET_BUF = $D3
;;; System Calls
GET_TIME = $82
ALLOC_INTERRUPT = $40
DEALLOC_INTERRUPT = $41
QUIT = $65
;;; Direct Disk Access Commands
READ_BLOCK = $80
WRITE_BLOCK = $81
;;; ============================================================
;;; File Types
;;; ============================================================
FT_TYPELESS = $00
FT_BAD = $01
FT_TEXT = $04 ; ASCII Text File *
FT_BINARY = $06 ; Generic Binary File *
FT_GRAPHICS = $08 ; Graphics File
FT_DIRECTORY = $0F ; Directory *
FT_ADB = $19 ; AppleWorks Database *
FT_AWP = $1A ; AppleWorks Word Processing *
FT_ASP = $1B ; AppleWorks Spreadsheet *
FT_SRC = $B0 ; IIgs system type; re-used?
FT_S16 = $B3 ; IIgs Application Program
FT_PAS = $EF ; Pascal Area *
FT_CMD = $F0 ; ProDOS Command File *
FT_INT = $FA ; Integer BASIC Program *
FT_IVR = $FB ; Integer BASIC Variable File *
FT_BASIC = $FC ; Applesoft BASIC Program *
FT_VAR = $FD ; Applesoft BASIC Variable File *
FT_REL = $FE ; EDASM/Contiki Relocatable File *
FT_SYSTEM = $FF ; ProDOS System File *
;;; Types marked with * are known to BASIC.SYSTEM and have an
;;; associated three-letter abbreviation.
;;; ============================================================
;;; Access
;;; ============================================================
ACCESS_DEFAULT = %11000011
ACCESS_LOCKED = %00100001
;;; ============================================================
;;; Storage Types
;;; ============================================================
ST_STANDARD_FILE = $01
ST_LINKED_DIRECTORY = $0D
ST_VOLUME_DIRECTORY = $0F
;;; ============================================================
;;; Errors
;;; ============================================================
ERR_IO_ERROR = $27
ERR_DEVICE_NOT_CONNECTED = $28
ERR_WRITE_PROTECTED = $2B
ERR_INVALID_PATHNAME = $40
ERR_INVALID_REFERENCE = $43
ERR_PATH_NOT_FOUND = $44
ERR_VOL_NOT_FOUND = $45
ERR_FILE_NOT_FOUND = $46
ERR_DUPLICATE_FILENAME= $47
ERR_OVERRUN_ERROR = $48
ERR_VOLUME_DIR_FULL = $49
ERR_END_OF_FILE = $4C
ERR_ACCESS_ERROR = $4E
ERR_DUPLICATE_VOLUME = $57
ERR_NETWORK_ERROR = $88
;;; ============================================================
;;; Directory Structures
;;; ============================================================
STORAGE_TYPE_MASK = $F0
NAME_LENGTH_MASK = $0F
;;; Volume Directory Header structure
.struct VolumeDirectoryHeader
prev_block .word
next_block .word
storage_type_name_length .byte
file_name .byte 15
reserved .byte 8
creation_date .word
creation_time .word
version .byte
min_version .byte
access .byte
entry_length .byte
entries_per_block .byte
file_count .word
;; same through here ---------
bit_map_pointer .word
total_blocks .word
.endstruct
.assert .sizeof(VolumeDirectoryHeader) = $2B, error, "incorrect struct size"
;;; Subdirectory Header structure
.struct SubdirectoryHeader
prev_block .word
next_block .word
storage_type_name_length .byte
file_name .byte 15
reserved .byte 8
creation_date .word
creation_time .word
version .byte
min_version .byte
access .byte
entry_length .byte
entries_per_block .byte
file_count .word
;; same through here ---------
parent_pointer .word
parent_entry_number .byte
parent_entry_length .byte
.endstruct
.assert .sizeof(SubdirectoryHeader) = $2B, error, "incorrect struct size"
;; File Entry structure
.struct FileEntry
storage_type_name_length .byte
file_name .byte 15
file_type .byte
key_pointer .word
blocks_used .word
eof .faraddr
creation_date .word
creation_time .word
version .byte
min_version .byte
access .byte
aux_type .word
mod_date .word
mod_time .word
header_pointer .word
.endstruct
.assert .sizeof(FileEntry) = $27, error, "incorrect struct size"
;;; ============================================================
;;; ProDOS Driver Protocol
;;; ============================================================
;;; Addresses for command parameters
DRIVER_COMMAND := $42
DRIVER_UNIT_NUMBER := $43
DRIVER_BUFFER := $44
DRIVER_BLOCK_NUMBER := $46
;;; Commands
DRIVER_COMMAND_STATUS = 0
DRIVER_COMMAND_READ = 1
DRIVER_COMMAND_WRITE = 2
DRIVER_COMMAND_FORMAT = 3
;;; ============================================================
;;; Macros
;;; ============================================================
.macro MLI_CALL op, addr
jsr MLI
.byte op
.addr addr
.endmacro
.macro DEFINE_OPEN_PARAMS name, pn, io, rn
.if .xmatch(.string(pn), "pathname")
;; If 'pathname' is passed then expansion yields a circular reference.
.error "Can't pass 'pathname' label to DEFINE_*_PARAMS"
.endif
.if .xmatch(.string(io), "io_buffer")
.error "Can't pass 'io_buffer' label to DEFINE_*_PARAMS"
.endif
.proc name
param_count: .byte 3
pathname: .addr pn
io_buffer: .addr io
.ifnblank rn
ref_num: .byte rn
.else
ref_num: .byte 0
.endif
.endproc
.endmacro
.macro DEFINE_READ_PARAMS name, db, rc
.proc name
param_count: .byte 4
ref_num: .byte 0
data_buffer: .addr db
request_count: .word rc
trans_count: .word 0
.endproc
.endmacro
.macro DEFINE_WRITE_PARAMS name, db, rc
.proc name
param_count: .byte 4
ref_num: .byte 0
data_buffer: .addr db
request_count: .word rc
trans_count: .word 0
.endproc
.endmacro
.macro DEFINE_CLOSE_PARAMS name
.proc name
param_count: .byte 1
ref_num: .byte 0
.endproc
.endmacro
.macro DEFINE_FLUSH_PARAMS name
.proc name
param_count: .byte 1
ref_num: .byte 0
.endproc
.endmacro
.macro DEFINE_GET_FILE_INFO_PARAMS name, pn
.if .xmatch(.string(pn), "pathname")
;; If 'pathname' is passed then expansion yields a circular reference.
.error "Can't pass 'pathname' label to DEFINE_*_PARAMS"
.endif
.proc name
param_count: .byte $A
pathname: .addr pn
access: .byte 0
file_type: .byte 0
aux_type: .word 0
storage_type: .byte 0
blocks_used: .word 0
mod_date: .word 0
mod_time: .word 0
create_date: .word 0
create_time: .word 0
.endproc
.endmacro
.macro DEFINE_SET_MARK_PARAMS name, pos
.proc name
param_count: .byte 2
ref_num: .byte 0
position: .faraddr pos
.endproc
.endmacro
.macro DEFINE_ON_LINE_PARAMS name, un, db
.proc name
param_count: .byte 2
.ifnblank un
unit_num: .byte un
.else
unit_num: .byte 0
.endif
data_buffer: .addr db
.endproc
.endmacro
.macro DEFINE_READ_BLOCK_PARAMS name, db, bn
.proc name
param_count: .byte 3
unit_num: .byte 0
data_buffer: .addr db
block_num: .word bn
.endproc
.endmacro
.macro DEFINE_WRITE_BLOCK_PARAMS name, db, bn
.proc name
param_count: .byte 3
unit_num: .byte 0
data_buffer: .addr db
block_num: .word bn
.endproc
.endmacro
.macro DEFINE_ALLOC_INTERRUPT_PARAMS name, ic
.proc alloc_interrupt_params
param_count: .byte 2
int_num: .byte 0
int_code: .addr ic
.endproc
.endmacro
.macro DEFINE_DEALLOC_INTERRUPT_PARAMS name
.proc dealloc_interrupt_params
param_count: .byte 1
int_num: .byte 0
.endproc
.endmacro
.macro DEFINE_QUIT_PARAMS name, ext, pathname
.proc name
param_count: .byte 4
.ifnblank ext
.byte ext
.else
.byte 0
.endif
.ifnblank pathname
.word pathname
.else
.word 0
.endif
.byte 0
.word 0
.endproc
.endmacro
.macro DEFINE_SET_PREFIX_PARAMS name, pn
.if .xmatch(.string(pn), "pathname")
;; If 'pathname' is passed then expansion yields a circular reference.
.error "Can't pass 'pathname' label to DEFINE_*_PARAMS"
.endif
.proc name
param_count: .byte 1
pathname: .addr pn
.endproc
.endmacro
.macro DEFINE_GET_PREFIX_PARAMS name, pn
.if .xmatch(.string(pn), "pathname")
;; If 'pathname' is passed then expansion yields a circular reference.
.error "Can't pass 'pathname' label to DEFINE_*_PARAMS"
.endif
.proc name
param_count: .byte 1
pathname: .addr pn
.endproc
.endmacro
.macro DEFINE_DESTROY_PARAMS name, pn
.if .xmatch(.string(pn), "pathname")
;; If 'pathname' is passed then expansion yields a circular reference.
.error "Can't pass 'pathname' label to DEFINE_*_PARAMS"
.endif
.proc name
param_count: .byte 1
pathname: .addr pn
.endproc
.endmacro
.macro DEFINE_CREATE_PARAMS name, pn, ac, ft, at, st
.if .xmatch(.string(pn), "pathname")
;; If 'pathname' is passed then expansion yields a circular reference.
.error "Can't pass 'pathname' label to DEFINE_*_PARAMS"
.endif
.proc name
param_count: .byte 7
pathname: .addr pn
.ifnblank ac
access: .byte ac
.else
access: .byte 0
.endif
.ifnblank ft
file_type: .byte ft
.else
file_type: .byte 0
.endif
.ifnblank at
aux_type: .word at
.else
aux_type: .word 0
.endif
.ifnblank st
storage_type: .byte st
.else
storage_type: .byte 0
.endif
create_date: .word 0
create_time: .word 0
.endproc
.endmacro
.macro DEFINE_SET_EOF_PARAMS name, eo
.proc name
param_count: .byte 2
ref_num: .byte 0
eof: .faraddr eo
.endproc
.endmacro
.macro DEFINE_GET_EOF_PARAMS name
.proc name
param_count: .byte 2
ref_num: .byte 0
eof: .faraddr 0
.endproc
.endmacro
.macro DEFINE_RENAME_PARAMS name, pn, np
.if .xmatch(.string(pn), "pathname")
;; If 'pathname' is passed then expansion yields a circular reference.
.error "Can't pass 'pathname' label to DEFINE_*_PARAMS"
.endif
.proc name
param_count: .byte 2
pathname: .addr pn
new_pathname: .addr np
.endproc
.endmacro

View File

@ -1,718 +0,0 @@
;;; NS.CLOCK.SYSTEM
;;; Original by "CAP" 04/21/91
;;; http://www.apple2.org.za/gswv/a2zine/GS.WorldView/v1999/Oct/MISC/NSC.Disk.TXT
;;; Modification history available at:
;;; https://github.com/inexorabletash/cricket
.setcpu "6502"
.linecont +
.include "apple2.inc"
.include "opcodes.inc"
.include "./common.inc"
;;; ------------------------------------------------------------
data_buffer = $1800
.define SYSTEM_SUFFIX ".SYSTEM"
.define PRODUCT "No-Slot Clock"
;;; ------------------------------------------------------------
.org $1000
;; Loaded at $2000 but relocates to $1000
;;; ------------------------------------------------------------
sys_start:
sec
bcs relocate
.byte MM, DD, YY ; version date stamp
;;; ------------------------------------------------------------
;;; Relocate this code from $2000 (.SYSTEM start location) to $1000
;;; and start executing there. This is done so that the next .SYSTEM
;;; file can be loaded/run at $2000.
.proc relocate
src := SYS_ADDR
dst := $1000
ldx #(sys_end - sys_start + $FF) / $100 ; pages
ldy #0
load: lda src,y ; self-modified
load_hi := *-1
sta dst,y ; self-modified
store_hi := *-1
iny
bne load
inc load_hi
inc store_hi
dex
beq find_self_name ; done
jmp load
.endproc
;;; ------------------------------------------------------------
;;; Identify the name of this SYS file, which should be present at
;;; $280 with or without a path prefix. This is used when searching
;;; for the next .SYSTEM file to execute.
.proc find_self_name
;; Search pathname buffer backwards for '/', then
;; copy name into |self_name|; this is used later
;; to find/invoke the next .SYSTEM file.
;; Find '/' (which may not be present, prefix is optional)
lda #0
sta $A8
ldx PATHNAME
beq pre_install
floop: inc $A8
dex
beq copy
lda PATHNAME,x
eor #'/'
asl a
bne floop
;; Copy name into |self_name| buffer
copy: ldy #0
cloop: iny
inx
lda PATHNAME,x
sta self_name,y
cpy $A8
bcc cloop
sty self_name
.endproc
;; Fall through...
;;; ------------------------------------------------------------
;;; Before installing, get the system to a known state and
;;; ensure there is not a previous clock driver installed.
.proc pre_install
cld
bit ROMIN2
;; Update reset vector - re-invokes this code.
lda #<pre_install
sta $03F2
lda #>pre_install
sta $03F3
eor #$A5
sta $03F4
;; Quit 80-column firmware
lda #$95 ; Ctrl+U (quit 80 col firmware)
jsr COUT
;; Reset stack
ldx #$FF
txs
;; Reset I/O
sta CLR80VID
sta CLRALTCHAR
jsr SETVID
jsr SETKBD
jsr SETNORM
jsr INIT
;; Update System Bit Map
ldx #BITMAP_SIZE-1
lda #%00000001 ; protect page $BF
: sta BITMAP,x
lda #%00000000 ; nothing else protected until...
dex
bne :-
lda #%11001111 ; ZP ($00), stack ($01), text page 1 ($04-$07)
sta BITMAP
lda MACHID
and #$88 ; IIe or IIc (or IIgs) ?
bne :+
lda #$DF
sta lowercase_mask ; lower case to upper case
: lda MACHID
and #$01 ; existing clock card?
beq detect_nsc ; nope, check for NSC
;; Chain with no message
jmp launch_next_sys_file
.endproc
;;; ------------------------------------------------------------
;;; Detect NSC. Scan slot ROMs and main ROMs. Try reading
;;; each location several times, and validate results before
;;; installing driver.
.proc detect_nsc
;; Preserve date/time
ldy #3 ; copy 4 bytes
: lda DATELO,y
sta saved,y
dey
bpl :-
;; Check slot ROMs
lda #>$CFFF
ldy #<$CFFF
sta ld4+2
sty ld4+1
sta st4+2
sty st4+1
lda #0
sta slot
lda #3 ; treat slot 0 as slot 3
sloop: ora #$C0 ; A=$Cs
sta st1+2
rloop: sta ld1+2
sta ld2+2
sta st2+2
lda #3 ; 3 tries - need valid results each time
sta tries
try: jsr driver ; try reading date/time
lda DATELO+1 ; check result
ror a
lda DATELO
rol a
rol a
rol a
rol a
and #$0F
beq next
cmp #13 ; month
bcs next
lda DATELO
and #$1F
beq next
cmp #32 ; day
bcs next
lda TIMELO+1
cmp #24 ; hours
bcs next
lda TIMELO
cmp #60 ; minutes
bcs next
dec tries
bne try
beq install_driver ; all tries look valid
next: inc slot
lda slot
cmp #8
bcc sloop ; next slot
bne not_found
;; Not found in slot ROM, try main ROMs ???
lda #>$C015
ldy #<$C015
sta ld4+2
sty ld4+1
ldy #$07
sta st1+2
sty st1+1
dey
sta st4+2
sty st4+1
lda #>$C800
bne rloop
;; Restore date/time
not_found:
ldy #3
: lda saved,y
sta DATELO,y
dey
bpl :-
;; Show failure message
jsr MON_HOME
jsr zstrout
HIASCIIZ CR, CR, CR, PRODUCT, " - Not Found."
jmp launch_next_sys_file
saved: .byte 0, 0, 0, 0
tries: .byte 3
slot: .byte 0
.endproc
;;; ------------------------------------------------------------
;;; Install NSC Driver. Copy into address at DATETIME vector,
;;; update the vector and update MACHID bits to signal a clock
;;; is present.
.proc install_driver
ptr := $A5
lda DATETIME+1
sta ptr
clc
adc #(unlock - driver - 1)
sta ld3+1
lda DATETIME+2
sta ptr+1
adc #0
sta ld3+2
lda RWRAM1
lda RWRAM1
ldy #sizeof_driver-1
loop: lda driver,y
sta (ptr),y
dey
bpl loop
;; Set the "Recognizable Clock Card" bit
lda MACHID
ora #$01
sta MACHID
lda #OPC_JMP_abs
sta DATETIME
;; Invoke the driver to init the time
jsr DATETIME
;; Display success message
bit ROMIN2
jsr MON_HOME
jsr zstrout
HIASCIIZ CR, CR, CR, PRODUCT, " - Installed "
;; Display the current date
lda DATELO+1 ; month
ror a
pha
lda DATELO
pha
rol a
rol a
rol a
rol a
and #%00001111
jsr cout_number
lda #(HI '/') ; /
jsr COUT
pla ; day
and #%00011111
jsr cout_number
lda #(HI '/') ; /
jsr COUT
pla ; year
jsr cout_number
jsr CROUT
.endproc
;;; ------------------------------------------------------------
;;; Find and invoke the next .SYSTEM file
.proc launch_next_sys_file
;; Update reset vector - now terminates.
lda #<quit
sta $03F2
lda #>quit
sta $03F3
eor #$A5
sta $03F4
ptr := $A5
num := $A7
len := $A8
lda DEVNUM ; stick with most recent device
sta read_block_params_unit_num
jsr read_block
lda data_buffer + VolumeDirectoryBlockHeader::entry_length
sta entry_length_mod
lda data_buffer + VolumeDirectoryBlockHeader::entries_per_block
sta entries_per_block_mod
lda #1
sta num
lda #<(data_buffer + VolumeDirectoryBlockHeader::header_length)
sta ptr
lda #>(data_buffer + VolumeDirectoryBlockHeader::header_length)
sta ptr+1
;; Process directory entry
entry: ldy #FileEntry::file_type ; file_type
lda (ptr),y
cmp #$FF ; type=SYS
bne next
ldy #FileEntry::storage_type
lda (ptr),y
and #$30 ; regular file (not directory, pascal)
beq next
lda (ptr),y
and #$0F ; name_length
sta len
tay
;; Compare suffix - is it .SYSTEM?
ldx #.strlen(SYSTEM_SUFFIX)-1
: lda (ptr),y
cmp suffix,x
bne next
dey
dex
bpl :-
;; Yes; is it *this* .SYSTEM file?
ldy self_name
cpy len
bne handle_sys_file
: lda (ptr),y
cmp self_name,y
bne handle_sys_file
dey
bne :-
sec
ror found_self_flag
;; Move to the next entry
next: lda ptr
clc
adc #$27 ; self-modified: entry_length
entry_length_mod := *-1
sta ptr
bcc :+
inc ptr+1
: inc num
lda num
cmp #$0D ; self-modified: entries_per_block
entries_per_block_mod := *-1
bcc entry
lda data_buffer + VolumeDirectoryBlockHeader::next_block
sta read_block_params_block_num
lda data_buffer + VolumeDirectoryBlockHeader::next_block + 1
sta read_block_params_block_num+1
ora read_block_params_block_num
beq not_found ; last block has next=0
jsr read_block
lda #0
sta num
lda #<(data_buffer + $04)
sta ptr
lda #>(data_buffer + $04)
sta ptr+1
jmp entry
;; Found a .SYSTEM file which is not this one; invoke
;; it if follows this one.
handle_sys_file:
bit found_self_flag
bpl next
;; Compose the path to invoke. First walk self path
;; backwards to '/'.
ldx PATHNAME
beq append
: dex
beq append
lda PATHNAME,x
eor #'/'
asl a
bne :-
;; Now append name of found file.
append: ldy #0
: iny
inx
lda (ptr),y
sta PATHNAME,x
cpy len
bcc :-
stx PATHNAME
jmp invoke_system_file
not_found:
jsr zstrout
HIASCIIZ CR, CR, CR, "* Unable to find next '.SYSTEM' file *", CR
bit KBDSTRB
: lda KBD
bpl :-
bit KBDSTRB
jmp quit
.endproc
;;; ------------------------------------------------------------
;;; Output a high-ascii, null-terminated string.
;;; String immediately follows the JSR.
.proc zstrout
ptr := $A5
pla ; read address from stack
sta ptr
pla
sta ptr+1
bne skip ; always (since data not on ZP)
next: cmp #(HI 'a') ; lower-case?
bcc :+
and lowercase_mask ; make upper-case if needed
: jsr COUT
skip: inc ptr
bne :+
inc ptr+1
: ldy #0
lda (ptr),y
bne next
lda ptr+1 ; restore address to stack
pha
lda ptr
pha
rts
.endproc
;;; ------------------------------------------------------------
;;; COUT a 2-digit number in A
.proc cout_number
ldx #(HI '0')
cmp #10 ; >= 10?
bcc tens
;; divide by 10, dividend(+'0') in x remainder in a
: sbc #10
inx
cmp #10
bcs :-
tens: pha
cpx #(HI '0')
beq units
txa
jsr COUT
units: pla
ora #(HI '0')
jsr COUT
rts
.endproc
;;; ------------------------------------------------------------
lowercase_mask:
.byte $FF ; Set to $DF on systems w/o lower-case
;;; ------------------------------------------------------------
;;; Invoke ProDOS QUIT routine.
.proc quit
PRODOS_CALL MLI_QUIT, quit_params
.byte 0 ; crash if QUIT fails
rts
.proc quit_params
.byte 4 ; param_count
.byte 0 ; quit_type
.word 0000 ; reserved
.byte 0 ; reserved
.word 0000 ; reserved
.endproc
.endproc
;;; ------------------------------------------------------------
;;; Read a disk block.
.proc read_block
PRODOS_CALL MLI_READ_BLOCK, read_block_params
bcs on_error
rts
.endproc
.proc read_block_params
.byte 3 ; param_count
unit_num: .byte $60 ; unit_num
.addr data_buffer ; data_buffer
block_num: .word 2 ; block_num - block 2 is volume directory
.endproc
read_block_params_unit_num := read_block_params::unit_num
read_block_params_block_num := read_block_params::block_num
;;; ------------------------------------------------------------
;;; Load/execute the system file in PATHNAME
.proc invoke_system_file
PRODOS_CALL MLI_OPEN, open_params
bcs on_error
lda open_params_ref_num
sta read_params_ref_num
PRODOS_CALL MLI_READ, read_params
bcs on_error
PRODOS_CALL MLI_CLOSE, close_params
bcs on_error
jmp SYS_ADDR ; Invoke loaded SYSTEM file
.endproc
;;; ------------------------------------------------------------
;;; Error handler - invoked if any ProDOS error occurs.
.proc on_error
pha
jsr zstrout
HIASCIIZ CR, CR, CR, "** Disk Error $"
pla
jsr PRBYTE
jsr zstrout
HIASCIIZ " **", CR
bit KBDSTRB
: lda KBD
bpl :-
bit KBDSTRB
jmp quit
.endproc
;;; ------------------------------------------------------------
.proc open_params
.byte 3 ; param_count
.addr PATHNAME ; pathname
.addr data_buffer ; io_buffer
ref_num:.byte 1 ; ref_num
.endproc
open_params_ref_num := open_params::ref_num
.proc read_params
.byte 4 ; param_count
ref_num:.byte 1 ; ref_num
.addr SYS_ADDR ; data_buffer
.word MAX_DW ; request_count
.word 0 ; trans_count
.endproc
read_params_ref_num := read_params::ref_num
.proc close_params
.byte 1 ; param_count
ref_num:.byte 0 ; ref_num
.endproc
;;; ------------------------------------------------------------
found_self_flag:
.byte 0
suffix: .byte SYSTEM_SUFFIX
self_name:
PASCAL_STRING "NS.CLOCK.SYSTEM"
;;; ------------------------------------------------------------
;;; NSC driver - modified as needed and copied into ProDOS
driver:
php
sei
ld4: lda $CFFF ; self-modified
pha
st1: sta $C300 ; self-modified
ld1: lda $C304 ; self-modified
ldx #8
;; Unlock the NSC by bit-banging.
uloop:
ld3: lda unlock-1,x ; self-modified
sec
ror a ; a bit at a time
: pha
lda #0
rol a
tay
ld2: lda $C300,y ; self-modified
pla
lsr a
bne :-
dex
bne uloop
;; Read 8 bytes * 8 bits of clock data into $200...$207
ldx #8
bloop: ldy #8
st2:
: lda $C304 ; self-modified
ror a
ror $01FF,x
dey
bne :-
lda $01FF,x ; got 8 bits
lsr a ; BCD to binary
lsr a ; shift out tens
lsr a
lsr a
tay
beq donebcd
lda $01FF,x
and #$0F ; mask out units
clc
: adc #10 ; and add tens as needed
dey
bne :-
sta $01FF,x
donebcd:
dex
bne bloop
;; Now $200...$207 is y/m/d/w/H/M/S/f
;; Update ProDOS date/time.
lda $0204 ; hour
sta TIMELO+1
lda $0205 ; minute
sta TIMELO
lda $0201 ; month
asl a
asl a
asl a
asl a
asl a
ora $0202 ; day
sta DATELO
lda $0200 ; year
rol a
sta DATELO+1
pla
bmi done
st4: sta $CFFF ; self-modified
done: plp
rts
unlock:
;; NSC unlock sequence
.byte $5C, $A3, $3A, $C5
.byte $5C, $A3, $3A, $C5
.byte $00
sizeof_driver := * - driver
.assert sizeof_driver <= 125, error, "Clock code must be <= 125 bytes"
;;; ------------------------------------------------------------
sys_end:

59
package.sh Executable file
View File

@ -0,0 +1,59 @@
#!/bin/bash
# Use Cadius to create a disk image for distribution
# https://github.com/mach-kernel/cadius
set -e
PACKDIR=$(mktemp -d)
IMGFILE="prodos-drivers.po"
VOLNAME="drivers"
rm -f "$IMGFILE"
cadius CREATEVOLUME "$IMGFILE" "$VOLNAME" 140KB --no-case-bits --quiet
cadius CREATEFOLDER "$IMGFILE" "/$VOLNAME/SETUPS" --no-case-bits --quiet
add_file () {
cp "$1" "$PACKDIR/$2"
cadius ADDFILE "$IMGFILE" "$3" "$PACKDIR/$2" --no-case-bits --quiet
}
# Drivers
for file in \
"bbb" "buhbye" "bye" "selector" \
"clock" "cricket" "dclock" "fn.clock" "ns.clock" "romxrtc" \
"ram.drv" \
"zipchip" \
"me.first" "pause"; do
add_file "out/$file.system.SYS" "$file.system#FF0000" "/$VOLNAME"
add_file "out/$file.setup.SYS" "$file.setup#FF0000" "/$VOLNAME/SETUPS"
done
add_file "out/setup.system.SYS" "setup.system#FF0000" "/$VOLNAME"
add_file "out/quit.system.SYS" "quit.system#FF0000" "/$VOLNAME"
# Utilities
add_file "out/date.BIN" "date#062000" "/$VOLNAME"
cadius CREATEFOLDER "$IMGFILE" "/$VOLNAME/CRICKET.UTIL" --no-case-bits --quiet
add_file "out/cricket.util/set.datetime.BIN" "set.datetime#062000" "/$VOLNAME/CRICKET.UTIL"
add_file "out/cricket.util/set.date.BIN" "set.date#062000" "/$VOLNAME/CRICKET.UTIL"
add_file "out/cricket.util/set.time.BIN" "set.time#062000" "/$VOLNAME/CRICKET.UTIL"
add_file "out/cricket.util/test.BIN" "test#062000" "/$VOLNAME/CRICKET.UTIL"
cadius CREATEFOLDER "$IMGFILE" "/$VOLNAME/NSCLOCK.UTIL" --no-case-bits --quiet
add_file "out/nsclock.util/set.datetime.BIN" "set.datetime#062000" "/$VOLNAME/NSCLOCK.UTIL"
cadius CREATEFOLDER "$IMGFILE" "/$VOLNAME/TEXTCOLORS" --no-case-bits --quiet
cadius CREATEFOLDER "$IMGFILE" "/$VOLNAME/SETUPS/TEXTCOLORS" --no-case-bits --quiet
for file in a2green bw deepblue gray gsblue mint pink wb; do
add_file "out/${file}.system.SYS" "${file}.system#FF0000" "/$VOLNAME/TEXTCOLORS"
add_file "out/${file}.setup.SYS" "${file}.setup#FF0000" "/$VOLNAME/TEXTCOLORS/SETUPS"
done
rm -r "$PACKDIR"
cadius CATALOG "$IMGFILE"

43
ram.drv/Makefile Normal file
View File

@ -0,0 +1,43 @@
CAFLAGS = --target apple2enh --list-bytes 0
LDFLAGS = --config apple2-asm.cfg
OUTDIR = ../out
HEADERS = $(wildcard *.inc) $(wildcard ../inc/*.inc)
TARGETS = \
$(OUTDIR)/ram.drv.system.SYS \
$(OUTDIR)/ram.drv.setup.SYS
export LOG_SUCCESS=1
export LOG_FAILURE=1
# For timestamps
MM = $(shell date "+%-m")
DD = $(shell date "+%-d")
YY = $(shell date "+%-y")
DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY) \
-D LOG_SUCCESS=$(LOG_SUCCESS) -D LOG_FAILURE=$(LOG_FAILURE)
XATTR := $(shell command -v xattr 2> /dev/null)
.PHONY: clean all
all: $(OUTDIR) $(TARGETS)
$(OUTDIR):
mkdir -p $(OUTDIR)
clean:
rm -f $(OUTDIR)/*.o
rm -f $(OUTDIR)/*.list
rm -f $(TARGETS)
$(OUTDIR)/%.o: %.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.setup.o: %.system.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) -D BUILD_SETUP_FILE --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.SYS: $(OUTDIR)/%.o
ld65 $(LDFLAGS) -o $@ $<

15
ram.drv/README.md Normal file
View File

@ -0,0 +1,15 @@
# Disassembly of Glen E. Bredon's `RAM.DRV.SYSTEM` for Apple II ProDOS
This was started before realizing what the origin of the `RAM.SYSTEM`
found on a MouseDesk 2.0 disk image file was.
There is a more complete diassembly with commentary at:
http://boutillon.free.fr/Underground/Outils/Ram_Drv_System/Ram_Drv_System.html
## Project Details
* The `orig` branch compiles to to match the original.
* The `master` branch has additions, including:
* Chains to next `.SYSTEM` file in dir order (not hard coded)
* Chains to next `.SYSTEM` file on non-block devices (e.g. file shares)

BIN
ram.drv/orig/RAM.SYSTEM.SYS Normal file

Binary file not shown.

715
ram.drv/ram.drv.system.s Normal file
View File

@ -0,0 +1,715 @@
;;; Disassembly of "RAM.SYSTEM" found on Mouse Desk 2.0 images
;;; Based on Glen E. Bredon's "RAM.DRV.SYSTEM"
;;; Some details c/o http://boutillon.free.fr/Underground/Outils/Ram_Drv_System/Ram_Drv_System.html
;;;
;;; Modifications:
;;; * Chain to next .SYSTEM file dynamically
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "opcodes.inc"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
;;; ============================================================
;;;
;;; Driver Installer
;;;
;;; ============================================================
;;; ============================================================
;;; Configuration Parameters
.undef PRODUCT
.define PRODUCT "RAMWorks RAM Disk"
zp_sig_addr := $06
zpproc_addr := $B0
zpproc_relay_addr := $2D0
driver_target := $FF00 ; Install location in ProDOS
kMaxUsableBanks = 24 ; Why is this capped so low???
; (driver has room for another ~20?)
banks_to_reserve: .byte 0 ; banks to reserve (e.g. for AppleWorks)
unitnum: .byte $03 ; S3D1; could be $B for S3D2
;;; ============================================================
;;; Install the driver
.proc maybe_install_driver
sta CLR80COL
ldy #0
sty BANKSEL
sta ALTZPON ; Use ZP to probe banks
;; Clear map1 / map2 (256 bytes) to $FF
lda #$FF
: sta map1,y
iny
bne :-
;; Stash first two bytes of each bank (128 possible banks)
: sty BANKSEL
lda $00
sta stash_00,y
lda $01
sta stash_01,y
iny
bpl :-
dey
;; Write bank num/complement at $0/$1
: sty BANKSEL
sty $00
tya
eor #$FF
sta $01
dey
bne :-
;; Y = 0
;; Reset signature bytes on main/aux banks
sty BANKSEL
sty $00
sty $01
sta ALTZPOFF
sty $00
sty $01
sta ALTZPON
lda banks_to_reserve
sta reserved_banks
;;; ============================================================
;; Copy into every bank
ldy #1
bank_loop:
;; Check bank for signature bytes (bank num/complement at $0/$1)
sty BANKSEL
cpy $00
bne next_bank
tya
eor #$FF
eor $01
bne next_bank
cpy $00 ; Bank 0 (aux) is reserved for 128k apps
bne next_bank
;; Flag as available in map2
;; (map2,N = N if available, $FF otherwise)
tya
sta map2,y
;; Skip over reserved banks, then start storing them in the map
ldx reserved_banks
bne :+
sta first_used_bank
: dec reserved_banks
bpl next_bank
sta map1,y
;; (map1,N = N if available, $FF otherwise - also???)
;; Copy helper proc into bank's ZP
ldx #sizeof_zpproc
: lda zpproc-1,x
sta zpproc_addr-1,x
dex
bne :-
next_bank:
iny
bpl bank_loop
;;; ============================================================
;; Y = $80
;; Restore stashed $0/$1 bytes of back
;; (except first, in first_used_bank ???)
loop0: lda map2-1,y
bmi :+
cmp first_used_bank
beq :+
sta BANKSEL
lda stash_00-1,y
sta $00
lda stash_01-1,y
sta $01
: dey
bne loop0
;; Y = 0
sty BANKSEL
sty $00
;; Count number of available banks, and populate
;; driver_bank_list with list of banks.
ldx #$FF
loop1: inx
cpx #kMaxUsableBanks
bcs break
loop2: iny
bmi break
lda map1,y
bmi loop2
sta driver_bank_list,x
bpl loop1
break:
;; Patch driver with block-specific data
;; X = number of available banks
;; Compute number of blocks
txa
lsr a
sta vol_dir_header+VolumeDirectoryHeader::total_blocks+1
ror vol_dir_header+VolumeDirectoryHeader::total_blocks
stx driver_block_x ; num banks
dex ; -1
stx num_banks_minus_one
bmi fail ; 0 banks? give up.
lda vol_dir_header+VolumeDirectoryHeader::total_blocks
sec
sbc driver_block_x
and #$F8
sta vol_dir_header+VolumeDirectoryHeader::total_blocks
sta driver_blocks_lo
bcs :+
dec vol_dir_header+VolumeDirectoryHeader::total_blocks+1
: lda vol_dir_header+VolumeDirectoryHeader::total_blocks+1
sta driver_blocks_hi
lda driver_bank_list
sta BANKSEL
lda $00
beq fail
;; Check for ZP signature - if not found, set it and install.
ldx #sig_len-1
: lda sig,x
cmp zp_sig_addr,x
bne set_sig
dex
bpl :-
bit BUTN1 ; escape hatch in case of loop ???
bmi L21F0
jmp do_install
fail: jmp install_failure
sloop: lda sig,x
set_sig:
sta zp_sig_addr,x
dex
bpl sloop
;;; ============================================================
;;; Prepare key blocks in
L21F0: sta ALTZPOFF
;; Stamp current date/time into vol_dir_header
ldy #3
: lda DATELO,y
sta vol_dir_header+VolumeDirectoryHeader::creation_date,y
dey
bpl :-
;; Fill pages $06-$0F with 00-FF
sta RAMWRTON
iny
tya
: sta $0600,y ; Block 2 - volume dir
sta $0700,y
sta $0800,y ; Block 3 - volume dir
sta $0900,y
sta $0A00,y ; Block 4 - volume dir
sta $0B00,y
sta $0C00,y ; Block 5 - volume dir
sta $0D00,y
sta $0E00,y ; Block 6 - volume bitmap
sta $0F00,y
iny
bne :-
;; Copy vol_dir_header into page $06
ldy #.sizeof(VolumeDirectoryHeader)-1
: lda vol_dir_header,y
sta $0600,y
dey
bpl :-
ldy #$02
sty $0800
iny
sty $0A00
iny
sty $0C00
sty $0802
iny
sty $0A02
ptr := $3C
lda vol_dir_header+VolumeDirectoryHeader::total_blocks
sta ptr
lda vol_dir_header+VolumeDirectoryHeader::total_blocks+1
lsr a
ror ptr
lsr a
ror ptr
lsr a
ror ptr
clc
adc #$0E
sta ptr+1
ldy #0
tya
: sta (ptr),y
lda ptr
sec
sbc #1
sta ptr
lda #$FF
bcs :-
dec ptr+1
ldx ptr+1
cpx #$0E
bcs :-
lda #$01
sta $0E00
;;; ============================================================
do_install:
lda #0
sta RAMWRTOFF
sta ALTZPOFF
sta BANKSEL
bit LCBANK1
bit LCBANK1
lda #OPC_CLD ; signature
cmp driver_target
beq copy_driver
sta ALTZPON ; Maybe in AUX?
cmp driver_target
beq copy_driver
cmp $DE00 ; ???
beq copy_driver
sta ALTZPOFF
;; Copy driver into place
copy_driver:
ldy #0
: lda driver_src,y
sta driver_target,y
iny
cpy #sizeof_driver
bcc :-
;; Check if unitnum already has a device
ldy DEVCNT
: lda DEVLST,y
lsr a
lsr a
lsr a
lsr a
cmp unitnum
beq install_device
dey
bpl :-
;; Otherwise, insert at end. ProDOS resolves names to devices
;; from last to first, so fast RAM disks should go last.
inc DEVCNT
ldy DEVCNT
;; Install device in ProDOS via DEVLST/DEVADR.
;; (Y has index in DEVLST)
install_device:
lda unitnum
asl a
tax
asl a
asl a
asl a
sta on_line_params+1 ; unit_number
ora #$0E ; $3E - signature byte used by DeskTop
sta DEVLST,y
copy16 #(driver_target+1), DEVADR,x
;; Did we install into S3D2?
lda unitnum
cmp #$0B ; Slot 3 Drive 2
beq finish
;; No, so uninstall S3D2 (regular /RAM)
ldy DEVCNT
: lda DEVLST,y
and #$F0
cmp #$B0 ; Slot 3 drive 2 i.e. normal /RAM
beq found
dey
bpl :-
bmi finish ; always
;; Actually remove from DEVLST
slot3d2_devadr := DEVADR + $10 + 3*2
found: ldx slot3d2_devadr + 1
inx
bne finish
: copy DEVLST+1,y, DEVLST,y
iny
cpy DEVCNT
bcc :-
beq :-
dec DEVCNT
copy16 NODEV, slot3d2_devadr ; clear driver
finish: bit ROMIN2
MLI_CALL ON_LINE, on_line_params
ldx #$00
lda on_line_params_buffer
ora L239F
bne install_success
bcc install_success
copy #$FF, L239F
sta ALTZPON
copy driver_bank_list, BANKSEL
stx $06
stx BANKSEL
stx vol_dir_header+VolumeDirectoryHeader::total_blocks
jmp maybe_install_driver ; retry???
install_success:
sta ALTZPOFF
.if ::LOG_SUCCESS
jsr log_message
scrcode PRODUCT, " - "
.byte 0
.endif ; ::LOG_SUCCESS
;; Initialize Applesoft zero page locations required by LINPRNT
copy #0, SHIFT_SIGN_EXT ; required by FP routines
copy #TEMPST, TEMPPT ; string descriptor pointer
copy #1, SPEEDZ ; output delay
copy #0, FLASH_BIT ; character set mask
ldx vol_dir_header+VolumeDirectoryHeader::total_blocks
lda vol_dir_header+VolumeDirectoryHeader::total_blocks+1
jsr LINPRNT
jsr zstrout
scrcode " Blocks"
.byte 0
rts
install_failure:
sta ALTZPOFF
.if ::LOG_FAILURE
jsr log_message
scrcode PRODUCT, " - Not Found"
.byte 0
.endif ; ::LOG_FAILURE
rts
;;; ============================================================
;;; Installed on zero page of each bank at $B0
.proc zpproc
pushorg ::zpproc_addr
sta $E0 ; dst1 hi
bcs :+
sty $E0 ; dst1 hi
tay
: lda #$00
sta RAMWRTON
bcc :+
txa
ldx #$00
sta RAMWRTOFF
sta RAMRDON
;; One block = two pages
: sty $DD ; src1 hi
iny
sty $E3 ; src2 hi
sta $DF ; dst1 lo
sta $E5 ; dst2 lo
stx $DC ; src1 lo
stx $E2 ; src2 lo
ldy $E0 ; dst1 hi
iny
sty $E6 ; dst2 hi
ldy #$00
: lda $1000,y ; src1
sta $1000,y ; dst1
lda $1000,y ; src2
sta $1000,y ; dst2
iny
bne :-
sta RAMWRTOFF
sta RAMRDOFF
clc
bit $02E4
rts
poporg
.endproc
sizeof_zpproc := .sizeof(zpproc)
;;; ============================================================
on_line_params_buffer := $220
DEFINE_ON_LINE_PARAMS on_line_params, $30, on_line_params_buffer
num_banks_minus_one:
.byte 0
L239F: .byte 0
sig: scrcode "GEB" ; signature sequence - Glen E. Bredon
sig_len = * - sig
;; Volume Directory Header
.proc vol_dir_header
.word 0 ; preceding block number
.word $03 ; succeeding block number
.byte ST_VOLUME_DIRECTORY << 4 | 3 ; storage type / name length
.byte "RAM" ; name field is 15 bytes
.res 15-3
.res 8, 0 ; reserved (8 bytes)
.word 0, 0 ; creation date/time
.byte 1 ; version (1 = ProDOS 2.0)
.byte 0 ; min_version
.byte ACCESS_DEFAULT ; access
.byte $27 ; entry_length
.byte $D ; entries_per_block
.word 0 ; file_count
.word 6 ; bit_map_pointer
blocks: .word 0 ; total_blocks
.endproc
.assert .sizeof(vol_dir_header) = .sizeof(VolumeDirectoryHeader), error, "Size mismatch"
.endproc
;;; ============================================================
;;; Ram Disk Driver - installed at $FF00
;;; ============================================================
.proc driver_src
pushorg ::driver_target
driver_start := *
start: cld ; used as a signature
lda DRIVER_COMMAND
bne not_status
driver_blocks_lo := *+1
ldx #0 ; self-modified - blocks low
driver_blocks_hi := *+1
ldy #0 ; self-modified - blocks high
LFF09: clc
bcc LFF83 ; always
not_status:
cmp #DRIVER_COMMAND_FORMAT
beq LFF09
;; COMMAND_READ or COMMAND_WRITE
LFF10: lda #$27
bcs rts1
lda RD80STORE
pha
sta CLR80COL
;; Save $40/41
lda $40
pha
lda $41
pha
lda DRIVER_BUFFER
sta $40
ldx DRIVER_BUFFER+1
inx
stx $41
jsr install_zpproc_relay
zpproc_relay_patch1_offset := $04
stx zpproc_relay_addr + zpproc_relay_patch1_offset
lda RDALTZP
zpproc_relay_patch2_offset := $14
sta zpproc_relay_addr + zpproc_relay_patch2_offset
lda DRIVER_BLOCK_NUMBER+1
pha
tax
lda DRIVER_BLOCK_NUMBER
LFF3C: sec
: iny
sbc #$7F
bcs :-
dex
bpl LFF3C
tya
adc DRIVER_BLOCK_NUMBER
bcc :+
inc DRIVER_BLOCK_NUMBER+1
: asl a
tay
lda DRIVER_BLOCK_NUMBER+1
rol a
tax
pla
sta DRIVER_BLOCK_NUMBER+1
driver_block_x := *+1
cpx #$0 ; self-modified - ???
bcs LFF74
tya
sbc #191
cmp #16
bcs :+
adc #208
tay
bit LCBANK2
: lda DRIVER_COMMAND
lsr a ; carry set = READ, clear = WRITE
lda bank_list,x
ldx DRIVER_BUFFER
jsr zpproc_relay_addr
bit LCBANK1
LFF74: jsr install_zpproc_relay
;; Restore $40/41
pla
sta $41
pla
sta $40
pla
bpl LFF83
sta SET80COL
LFF83: lda #$00
bcs LFF10
rts1: rts
install_zpproc_relay:
ldy #sizeof_zpproc_relay+1
: ldx zpproc_relay-1,y
lda zpproc_relay_addr-1,y
sta zpproc_relay-1,y
txa
sta zpproc_relay_addr-1,y
dey
bne :-
ldx DRIVER_BUFFER+1
bpl done
bit DRIVER_BUFFER+1
bvc done
: ldx $8000,y
lda (DRIVER_BUFFER),y
sta $8000,y
txa
sta (DRIVER_BUFFER),y
ldx $8100,y
lda ($40),y
sta $8100,y
txa
sta ($40),y
iny
bne :-
ldx #$80
done: rts
bank_list:
.res ::kMaxUsableBanks, 0
.proc zpproc_relay
sta BANKSEL
patch_loc1 := *+1
lda #$00
sta ALTZPON
jsr zpproc_addr
sty BANKSEL
bmi :+
sta ALTZPOFF
: rts
patch_loc2 := *
.endproc
sizeof_zpproc_relay := .sizeof(zpproc_relay)
patch_loc1_offset := zpproc_relay::patch_loc1 - zpproc_relay
patch_loc2_offset := zpproc_relay::patch_loc2 - zpproc_relay
;; These offsets can't be used directly due to ca65 addressing mode
;; assumptions, so just verify they are correct.
.assert zpproc_relay_patch1_offset = patch_loc1_offset, error, "Offset mismatch"
.assert zpproc_relay_patch2_offset = patch_loc2_offset, error, "Offset mismatch"
.byte 0
poporg
.endproc
sizeof_driver := .sizeof(driver_src)
driver_blocks_lo := driver_src + driver_src::driver_blocks_lo - driver_src::driver_start
driver_blocks_hi := driver_src + driver_src::driver_blocks_hi - driver_src::driver_start
driver_block_x := driver_src + driver_src::driver_block_x - driver_src::driver_start
driver_bank_list := driver_src + driver_src::bank_list - driver_src::driver_start
;;; ============================================================
;;; Scratch space beyond code used during driver install
reserved_banks := *
first_used_bank := *+1
map1 := *+2 ; len: $80
map2 := *+2+$80 ; len: $80
stash_00 := *+2+$100 ; len: $80
stash_01 := *+2+$180 ; len: $80
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

9
ram.drv/res/go.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
# Run this from the ram.drv.system directory
set -e
source "res/util.sh"
#do_make clean
do_make all

15
ram.drv/res/util.sh Normal file
View File

@ -0,0 +1,15 @@
function cecho {
case $1 in
red) tput setaf 1 ; shift ;;
green) tput setaf 2 ; shift ;;
yellow) tput setaf 3 ; shift ;;
esac
echo -e "$@"
tput sgr0
}
function do_make {
make $MAKE_FLAGS "$1" \
&& (cecho green "make $1 good") \
|| (tput blink ; cecho red "MAKE $1 BAD" ; return 1)
}

45
selectors/Makefile Normal file
View File

@ -0,0 +1,45 @@
CAFLAGS = --target apple2enh --list-bytes 0
LDFLAGS = --config apple2-asm.cfg
OUTDIR = ../out
HEADERS = $(wildcard *.inc) $(wildcard ../inc/*.inc)
TARGETS = \
$(OUTDIR)/selector.system.SYS \
$(OUTDIR)/selector.setup.SYS \
$(OUTDIR)/bye.system.SYS \
$(OUTDIR)/bye.setup.SYS \
$(OUTDIR)/bbb.system.SYS \
$(OUTDIR)/bbb.setup.SYS \
$(OUTDIR)/buhbye.system.SYS \
$(OUTDIR)/buhbye.setup.SYS
# For timestamps
MM = $(shell date "+%-m")
DD = $(shell date "+%-d")
YY = $(shell date "+%-y")
DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY)
XATTR := $(shell command -v xattr 2> /dev/null)
.PHONY: clean all
all: $(OUTDIR) $(TARGETS)
$(OUTDIR):
mkdir -p $(OUTDIR)
clean:
rm -f $(OUTDIR)/*.o
rm -f $(OUTDIR)/*.list
rm -f $(TARGETS)
$(OUTDIR)/%.o: %.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.setup.o: %.system.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) -D BUILD_SETUP_FILE --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.SYS: $(OUTDIR)/%.o
ld65 $(LDFLAGS) -o $@ $<

46
selectors/README.md Normal file
View File

@ -0,0 +1,46 @@
# ProDOS Program Selectors ("BYE" commands)
The ProDOS operating system for the Apple II personal computer line supported a quit routine (invoked from BASIC with the `BYE` command) allowing the user to launch a new system file once previous system file had exited.
This selector code evolved over time, and the memory location that the routine was stored at was documented, allowing users to install customized versions coded to fit within a mere 768 bytes.
## ProDOS 1.0 through 1.8 - Selector
The earliest versions of ProDOS supported a simple 40-column-friendly selector prompting:
* `ENTER PREFIX (PRESS "RETURN" TO ACCEPT)"`
* `ENTER PATHNAME OF NEXT APPLICATION`
This was not particularly user friendly.
If you want to use this selector with any version of ProDOS, add the `SELECTOR.SYSTEM` file to your boot sequence. You can follow it with `QUIT.SYSTEM` in your driver sequence to show the selector on startup.
## Bird's Better Bye
[Alan Bird](https://alanlbird.wordpress.com/products/) wrote a replacement called **Bird's Better Bye** that would patch itself into ProDOS. Directories and system files could be selected with arrows keys and the Return key, Escape would back up a directory level, Tab would change drives. This also functioned in 40 column mode.
If you want to use this selector with any version of ProDOS, add the `BBB.SYSTEM` file to your boot sequence. You can follow it with `QUIT.SYSTEM` in your driver sequence to show the selector on startup.
## ProDOS 1.9 and 2.0.x - 80-column Selector
ProDOS 1.9 introduced a much improved menu-driven selector showing a list of files and allowing navigation of the file system with the keyboard. Directories and system files could be selected with arrows keys and the Return key, Escape would back up a directory level, Tab would change drives. This required 80 columns and a 65C02 processor, and took advantage of the [MouseText characters](https://en.wikipedia.org/wiki/MouseText) to show folder glyphs for directories.
If these versions of ProDOS were started on systems without 40 column support, the previous version of the selector would be loaded instead (both were present in the PRODOS system file.)
If you want to use this selector with any version of ProDOS, add the `BYE.SYSTEM` file to your boot sequence. You can follow it with `QUIT.SYSTEM` in your driver sequence to show the selector on startup.
This was inspired by the work of Dave Cotter who created a similarly named file to patch the selector back in. It can be found at: http://www.lazilong.com/apple_ii/bye.sys/bye.html
## ProDOS 2.4 - Bitsy Bye
The new (unofficial) releases of [ProDOS 2.4](http://www.callapple.org/uncategorized/announcing-prodos-2-4-for-all-apple-ii-computers/) by John Brooks include a replacement quit routine called Bitsy Bye, a collaboration with Peter Ferrie. This new quit routine is far more powerful, allowing access to BASIC and binary files (and more), drive selection, type-down, more entries, and so on. It uses only 40 columns, and does not require a 65C02 processor.
If you want to use this selector, use a version of ProDOS 2.4 from https://prodos8.com/
## Buh-Bye - Enhanced 80-column selector
Since I prefered the look of the ProDOS 80-column selector to Bitsy Bye, but missed the type-down ability, I modified the 80-column selector to tighten up the code added seeking if an alphabetical key is typed. Hit 'C' and the list will scroll to the next file starting with 'C'.
I erroneously thought that the ProDOS 80-column selector was _Bird's Better Bye_ and named this "Bell's Better Bird's Better Bye" or "Buh-Bye".
If you want to use this selector with any version of ProDOS, add the `BUHBYE.SYSTEM` file to your boot sequence. You can follow it with `QUIT.SYSTEM` in your driver sequence to show the selector on startup.

592
selectors/bbb.system.s Normal file
View File

@ -0,0 +1,592 @@
;;; Disassembly of Bird's Better Bye, 40 column program selector
;;; (Found in a copy of ProDOS 1.8, but not believed to be original?)
;;;
;;; Installer wrapper added by Joshua Bell inexorabletash@gmail.com
.setcpu "65C02"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
.include "../inc/ascii.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
;;; ------------------------------------------------------------
;;; ProDOS Technical Reference Manual, 5.1.5.2:
;;;
;;; ProDOS MLI call $65, the QUIT call, moves addresses $D100 through
;;; $D3FF from the second 4K bank of RAM of the language card to
;;; $1000, and executes a JMP to $1000. What initially resides in that
;;; area is Apple's dispatcher code.
;;; ------------------------------------------------------------
;;; Installer
;;; ------------------------------------------------------------
max_size = $300
.proc maybe_install_driver
src := install_src
end := install_src + install_size
dst := $D100 ; Install location in ProDOS (bank 2)
src_ptr := $19
dst_ptr := $1B
sta ALTZPOFF
lda ROMIN
lda ROMIN
lda #>src
sta src_ptr+1
lda #<src
sta src_ptr
lda #>dst
sta dst_ptr+1
lda #<dst
sta dst_ptr
loop: lda (src_ptr)
sta (dst_ptr)
inc src_ptr
bne :+
inc src_ptr+1
: inc dst_ptr
bne :+
inc dst_ptr+1
: lda src_ptr+1
cmp #>end
bne loop
lda src_ptr
cmp #<end
bne loop
lda (src_ptr)
sta (dst_ptr)
sta ALTZPOFF
sta ROMINWB1
sta ROMINWB1
rts
.endproc
;;; ------------------------------------------------------------
;;; Selector
;;; ------------------------------------------------------------
install_src := *
pushorg $1000
.proc bbb
PREFIX := $280
kMaxFilesDisplayed = 16
filename_table := $1700
device := $6 ; current device index (in DEVLST)
name_len := $8
index := $9 ; current displayed file index
name_ptr := $D
num_files := $F ; number of displayed files
type_table := $10 ; one entry per line, high bit set if SYS
;; Copied from directory header
entry_length := $A5
entries_per_block := $A6
file_count := $A7
entry_index := $A9 ; within "block"
dir_buf := SYS_ADDR
;;; ============================================================
;;; Code
;;; ============================================================
;; Signal to ProDOS that this is modified.
cld
inc $03F4 ; ???
;; Page in normal banks, reset screen to 40 columns.
lda ROMIN2
jsr SETTXT
jsr SETVID
jsr SETKBD
jsr SETNORM
sta CLR80VID
sta CLRALTCHAR
sta CLR80COL
;; Clear system bitmap.
lda #$01
sta BITMAP+BITMAP_SIZE-1
lda #$00
ldx #BITMAP_SIZE-2
: sta BITMAP,x
dex
bpl :-
lda #$CF
sta BITMAP
lda default_devnum
beq :+ ; always?
sta DEVNUM
: ldx DEVCNT
: stx device
lda DEVLST,x
and #$F0
cmp DEVNUM
beq set_devnum
dex
bpl :-
next_drive:
ldx device
cpx DEVCNT
bcc :+
ldx #$FF
: inx
stx device
lda DEVLST,x
set_devnum:
sta on_line_unit_num
sta DEVNUM
jsr HOME
MLI_CALL ON_LINE, on_line_params
bcs error_relay
lda PREFIX+1
and #$0F
beq error_relay
adc #$02
sta PREFIX
tax
set_prefix_length:
lda #'/'
sta PREFIX+1
sta PREFIX,x
lda #0
sta PREFIX+1,x
sta index
sta read_request_count+1
sta set_mark_position+1
sta set_mark_position+2
ldx #msg_select_drive
lda #10 ; HTAB
jsr ShowMessage
ldx #msg_select_file
lda #9 ; HTAB
jsr ShowMessage
;; Show current prefix
ldx #0
: lda PREFIX+1,x
beq :+
jsr Cout
inx
bne :-
: MLI_CALL OPEN, open_params
error_relay:
bcs next_drive_on_error
jsr AssignRefNum
lda #.sizeof(SubdirectoryHeader)
sta read_request_count
MLI_CALL READ, read_params
bcs next_drive_on_error
ldx #3
: lda dir_buf + SubdirectoryHeader::entry_length,x
sta entry_length,x
dex
bpl :-
sta read_request_count
lda #1
sta entry_index
lda file_count ; empty?
ora file_count+1
bne next_file
: jmp finish_dir ; empty!
;; Loop over file entries
next_file:
bit file_count+1 ; negative?
bmi :-
skip: lda set_mark_position+1
and #$FE
sta set_mark_position+1
ldy entry_index
lda #0
cpy entries_per_block
bcc next_entry_in_block
tay
sty entry_index
inc set_mark_position+1 ; next block - two pages
: inc set_mark_position+1
next_entry_in_block:
dey
clc
bmi next_block
adc entry_length
bcc next_entry_in_block
bcs :- ; always
next_block:
adc #4 ; skip prev/next block pointers
sta set_mark_position
MLI_CALL SET_MARK, get_eof_params
next_drive_on_error:
bcs do_next_drive
MLI_CALL READ, read_params
bcs do_next_drive
inc entry_index
lda dir_buf
beq skip ; deleted entry
and #NAME_LENGTH_MASK
sta dir_buf
dec file_count
bne :+
dec file_count+1
: ror dir_buf + FileEntry::access ; check low bit
bcc next_file
lda dir_buf + FileEntry::file_type
cmp #FT_DIRECTORY
beq :+
cmp #FT_SYSTEM
bne next_file
: ldx index
cpx #kMaxFilesDisplayed
bcs finish_dir
sta type_table,x
jsr SetNamePtrAndLen
ldy #$0F ; max name length
: lda dir_buf,y
sta (name_ptr),y
dey
bpl :-
jsr DisplayFilename
inc index
jmp next_file
;;; ============================================================
do_next_drive:
jmp next_drive
up_dir:
ldx PREFIX
: dex
beq input_loop
lda PREFIX,x
cmp #'/'
bne :-
dex
beq input_loop
stx PREFIX
set_new_prefix:
inc PREFIX
jsr HOME
ldx PREFIX
jmp set_prefix_length
select_next_file:
jsr DisplayFilename
ldx index
inx
cpx num_files
bcc set_index
ldx #0
beq set_index ; always
select_prev_file:
jsr DisplayFilename
ldx index
bne :+
ldx num_files
: dex
set_index:
stx index
jmp redisplay_selection
finish_dir:
MLI_CALL CLOSE, close_params
next_drive_relay:
bcs do_next_drive
lda index
beq do_next_drive
sta num_files
lda #0
sta index
redisplay_selection:
jsr SETINV
jsr DisplayFilename
jsr SETNORM
;; fall through
;;; ============================================================
input_loop:
lda KBD
bpl input_loop
sta KBDSTRB
cmp #HI(ASCII_TAB)
beq do_next_drive
cmp #HI(ASCII_LEFT)
beq select_prev_file
cmp #HI(ASCII_UP)
beq select_prev_file
cmp #HI(ASCII_RIGHT)
beq select_next_file
cmp #HI(ASCII_DOWN)
beq select_next_file
cmp #HI(ASCII_ESCAPE)
beq up_dir
cmp #HI(ASCII_CR)
bne input_loop
lda BUTN0
bpl SelectFile
lda ROMIN
lda ROMIN
lda DEVNUM
sta $D3A6
lda ROMIN2
jmp input_loop
;;; ============================================================
.proc SelectFile
MLI_CALL SET_PREFIX, set_prefix_params
bcs next_drive_relay
jsr SetNamePtrAndLen
ldx PREFIX
: iny
lda (name_ptr),y
inx
sta PREFIX,x
cpy name_len
bcc :-
stx PREFIX
ldy index
lda type_table,y
bmi InvokeSystemFile
jmp set_new_prefix
.endproc
;;; ============================================================
.proc InvokeSystemFile
MLI_CALL OPEN, open_params
: bcs next_drive_relay
jsr AssignRefNum
MLI_CALL GET_EOF, get_eof_params
bcs :-
lda get_eof_eof
sta read_request_count
lda get_eof_eof+1
sta read_request_count+1
MLI_CALL READ, read_params
php
MLI_CALL CLOSE, close_params
bcc :+
pla
err: jmp do_next_drive
: plp
bcs err
jmp SYS_ADDR
.endproc
;;; ============================================================
;;; Call with HTAB in A, message table offset in X
.proc ShowMessage
sta CH
: lda message_table,x
beq done
jsr Cout
inx
bne :-
done: rts
.endproc
;;; ============================================================
.proc Cout
ora #$80
jmp COUT1
.endproc
;;; ============================================================
.proc SetNamePtrAndLen
lda index
asl a ; * 16 (name length)
asl a
asl a
asl a
sta name_ptr
lda #>filename_table
sta name_ptr+1
ldy #$00
lda (name_ptr),y
sta name_len
rts
.endproc
;;; ============================================================
.proc DisplayFilename
lda #5
sta CH
lda index
clc
adc #$05
jsr TABV
jsr space
ldx index
lda type_table,x ; folder?
bmi :+ ; nope
lda #HI('/') ; yes - suffix with '/'
jsr Cout
: jsr SetNamePtrAndLen
: iny
lda (name_ptr),y
jsr Cout
cpy name_len
bcc :-
space: lda #HI(' ')
jmp Cout
.endproc
;;; ============================================================
.proc AssignRefNum
lda open_ref_num
sta read_ref_num
sta get_eof_ref_num ; also set_mark_position
rts
.endproc
;;; ============================================================
default_devnum:
.byte 0
;;; ============================================================
;;; Messages
;;; ============================================================
message_table:
msg_select_drive := * - message_table
scrcode "TAB: SELECT DRIVE\r"
.byte 0
msg_select_file := * - message_table
scrcode "RETURN: SELECT FILE\r\r"
.byte 0
;;; ============================================================
;;; Parameter Blocks
;;; ============================================================
;;; OPEN params
open_params:
.byte 3
open_pathname:
.addr PREFIX
open_io_buffer:
.addr $1800
open_ref_num:
.byte 0
;;; CLOSE params
close_params:
.byte 1
.byte 0
;;; ON_LINE params
on_line_params:
.byte $02
on_line_unit_num:
.byte $60
on_line_buffer:
.addr PREFIX+1
;;; READ params
read_params:
.byte 4
read_ref_num:
.byte 0
read_data_buffer:
.addr $2000
read_request_count:
.word 0
read_transfer_count:
.word 0
;;; SET_PREFIX params
set_prefix_params:
.byte 1
.addr PREFIX
;;; GET_EOF params
;;; SET_MARK params
get_eof_params:
set_mark_params:
.byte 2
get_eof_ref_num:
set_mark_ref_num:
.byte 0
get_eof_eof:
set_mark_position:
.faraddr $A0A0A0
;;; ------------------------------------------------------------
.endproc
.assert .sizeof(bbb) - bbb <= $300, error, "Must fit in $300 bytes"
install_size = $300
poporg
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

701
selectors/buhbye.system.s Normal file
View File

@ -0,0 +1,701 @@
;;; Enhancements to the 80-column ProDOS 8 Selector ("BYE") found in
;;; ProDOS 1.9 and 2.0.x.
;;;
;;; Modifications by Joshua Bell inexorabletash@gmail.com
;;; * alpha key advances to next matching filename
;;; * replaced directory enumeration (smaller, per PDTRM)
;;; * installs, then chains to next .SYSTEM file
.setcpu "65C02"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
.include "../inc/ascii.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
;;; ------------------------------------------------------------
;;; ProDOS Technical Reference Manual, 5.1.5.2:
;;;
;;; ProDOS MLI call $65, the QUIT call, moves addresses $D100 through
;;; $D3FF from the second 4K bank of RAM of the language card to
;;; $1000, and executes a JMP to $1000. What initially resides in that
;;; area is Apple's dispatcher code.
;;; ------------------------------------------------------------
;;; Installer
;;; ------------------------------------------------------------
max_size = $300
.proc maybe_install_driver
src := install_src
end := install_src + install_size
dst := $D100 ; Install location in ProDOS (bank 2)
src_ptr := $19
dst_ptr := $1B
sta ALTZPOFF ; TODO: Necessary?
lda ROMIN ; write bank 2
lda ROMIN
lda #<src ; src_ptr = src
sta src_ptr
lda #>src
sta src_ptr+1
lda #<dst ; dst_ptr = dst
sta dst_ptr
lda #>dst
sta dst_ptr+1
loop: lda (src_ptr) ; *src_ptr = *dst_ptr
sta (dst_ptr)
inc src_ptr ; src_ptr++
bne :+
inc src_ptr+1
: inc dst_ptr ; dst_ptr++
bne :+
inc dst_ptr+1
: lda src_ptr+1 ; src_ptr == end ?
cmp #>end
bne loop
lda src_ptr
cmp #<end
bne loop
sta ALTZPOFF ; TODO: Necessary?
sta ROMINWB1
sta ROMINWB1
rts
.endproc
;;; ------------------------------------------------------------
;;; Selector
;;; ------------------------------------------------------------
install_src := *
pushorg $1000
.proc selector
prefix := $280 ; length-prefixed
filenames := $1400 ; each is length + 15 bytes
read_buffer := $2000 ; Also, start location for launched SYS files
;; Device/Prefix enumeration
cur_device_num := $65 ; device number to try
prefix_depth := $6B ; 0 = root
;; Directory enumeration
entry_pointer := $60 ; 2 bytes
block_entries := $62
active_entries := $63 ; 2 bytes
entry_length := $6E
entries_per_block := $6F
file_count := $70 ; 2 bytes
;; Found entries
current_entry := $67 ; index of current entry
num_entries := $68 ; length of |filenames| (max 128)
curr_len := $69 ; length of current entry name
curr_ptr := $6C ; address of current entry name (in |filenames|)
types_table := $74 ; high bit clear = dir, set = sys
;; Entry display
page_start := $73 ; index of first entry shown on screen
row_count := $6A ; number of rows in this page
top_row := 2 ; first row used on screen
bottom_row := 21 ; last row used on screen
;;; ------------------------------------------------------------
cld ; ProDOS protocol for QUIT routine
lda ROMIN2 ; Page in ROM for reads, writes ignored
bit KBDSTRB ; Clear keyboard strobe
lda #$A0
jsr SLOT3 ; Activate 80-Column Firmware
;; Update system bitmap
ldx #BITMAP_SIZE-1 ; zero it all out
: stz BITMAP,x
dex
bpl :-
inc BITMAP+BITMAP_SIZE-1 ; protect ProDOS global page
lda #%11001111 ; protect zp, stack, text page 1
sta BITMAP
;; Find device
ldx DEVCNT
: lda DEVLST,x
and #%11110000
cmp DEVNUM
beq check_device
dex
bra :-
next_device:
ldx cur_device_num
dex
bpl :+
ldx DEVCNT
: lda DEVLST,x
check_device:
stx cur_device_num
sta on_line_params_unit
MLI_CALL ON_LINE, on_line_params
bcs next_device
stz prefix_depth
lda prefix+1
and #$0F
beq next_device
adc #2
tax
;; Resize prefix to length x and open the directory for reading
.proc resize_prefix_and_open
stx prefix
lda #'/'
sta prefix+1 ; ensure prefix is at least '/'
sta prefix,x ; and ends with '/'
stz prefix+1,x ; and is null terminated
stz num_entries
;;; Enumerate directory
;;; Algorithm from ProDOS Technical Reference Manual B.2.5
;; Open the directory
jsr do_open
bcc :+
;; Open failed
fail: lda prefix_depth ; root?
beq next_device
jsr pop_prefix ; and go up a level
bra resize_prefix_and_open
;; Open succeeded
: inc prefix_depth
;; Read a block (512 bytes) into buffer
stz read_params_request
lda #2
sta read_params_request+1
jsr do_read
bcs fail
;; Store entry_length (byte), entries_per_block (byte), file_count (word)
ldx #3
: lda read_buffer + SubdirectoryHeader::entry_length,x
sta entry_length,x
dex
bpl :-
;; Any entries?
lda file_count
ora file_count+1
beq close_dir
;; Skip header entry
clc
lda #<(read_buffer+4) ; 4 bytes for prev/next pointers
adc entry_length
sta entry_pointer
lda #>(read_buffer+4)
adc #0 ; TODO: Can skip this if entry_length << 256
sta entry_pointer+1
;; Prepare to process entry two (first "entry" is header)
lda #2
sta block_entries
while_loop:
;; Check if entry is active
lda (entry_pointer)
beq done_entry
;; Check file type
ldy #FileEntry::file_type
lda (entry_pointer),y
cmp #FT_DIRECTORY
beq store_entry
cmp #FT_SYSTEM
bne done_active_entry
store_entry:
;; Store type
ldx num_entries
sta types_table,x
;; Copy name into |filenames|
jsr update_curr_ptr ; current entry in X
ldy #15 ; max name length (length byte copied too)
: lda (entry_pointer),y
sta (curr_ptr),y
dey
bpl :-
; storage_type/name_length in A
and #%00001111 ; mask off name_length (remove storage_type)
sta (curr_ptr) ; store length
inc num_entries
done_active_entry:
dec file_count
bpl :+
dec file_count+1
:
done_entry:
;; Seen all active entries?
lda file_count
ora file_count+1
beq close_dir
;; Seen all entries in this block?
lda block_entries
cmp entries_per_block
bne next_in_block
;; Grab next block
next_block:
jsr do_read ; read another block
bcs fail
lda #1 ; first entry in non-key block
sta block_entries
lda #<(read_buffer+4) ; 4 bytes for prev/next pointers
sta entry_pointer
lda #>(read_buffer+4)
sta entry_pointer+1
bra end_while
;; Next entry in current block
next_in_block:
clc
lda entry_pointer
adc entry_length
sta entry_pointer
bcc :+
inc entry_pointer+1
:
inc block_entries
end_while:
;; Check to see if we have room
bit num_entries ; max is 128
bpl while_loop
close_dir:
MLI_CALL CLOSE, close_params
;; fall through
.endproc
;;; ------------------------------------------------------------
.proc draw_screen
jsr SETTXT ; TEXT
jsr HOME ; HOME
lda #23 ; VTAB 23
jsr TABV
;; Print help text
lda #20 ; HTAB 20
sta COL80HPOS
ldy #(help_string - text_resources)
jsr cout_string
;; Draw prefix
jsr home
ldx #0
: lda prefix+1,x
beq :+
jsr ascii_cout
inx
bne :-
: stz current_entry
stz page_start
lda num_entries
beq selection_loop_keyboard_loop ; no entries (empty directory)
;; Draw entries
cmp #bottom_row ; more entries than fit?
bcc :+
lda #(bottom_row - top_row + 1)
: sta row_count
lda #top_row
sta WNDTOP
sta WNDLFT
lda #bottom_row+1
sta WNDWDTH
sta WNDBTM
loop: jsr draw_current_line
inc current_entry
dec row_count
bne loop
stz current_entry
beq selection_loop
.endproc
;;; ------------------------------------------------------------
;; Remove level from prefix; returns new length in X
.proc pop_prefix
ldx prefix
loop: dex
lda prefix,x
cmp #'/'
bne loop
cpx #1
bne done
ldx prefix
done:
;; Fall through...
.endproc
handy_rts:
rts
;;; ------------------------------------------------------------
.proc on_down
jsr down_common
bra selection_loop
.endproc
;;; ------------------------------------------------------------
.proc on_up
ldx current_entry ; first one?
beq selection_loop
dec current_entry ; go to previous
lda CV
cmp #top_row ; at the top?
bne selection_loop
dec page_start ; yes, adjust page and
lda #ASCII_SYN ; scroll screen up
jsr COUT
;; fall through
.endproc
;;; ------------------------------------------------------------
.proc selection_loop
jsr SETINV
jsr draw_current_line
keyboard_loop:
lda KBD
bpl keyboard_loop
sta KBDSTRB
jsr SETNORM
cmp #HI(ASCII_TAB)
beq next_drive
cmp #HI(ASCII_ESCAPE)
beq on_escape
ldx num_entries
beq keyboard_loop ; if empty, no navigation
pha
jsr draw_current_line
pla
cmp #HI(ASCII_CR)
beq on_return
cmp #HI(ASCII_DOWN)
beq on_down
cmp #HI(ASCII_UP)
beq on_up
;; fall through
.endproc
selection_loop_keyboard_loop := selection_loop::keyboard_loop
;;; ------------------------------------------------------------
.proc on_alpha
loop: jsr down_common
jsr draw_current_line
lda KBD
and #$5F ; make ASCII and uppercase
ldy #1
cmp (curr_ptr),y ; key = first char ?
beq selection_loop
bra loop
.endproc
;;; ------------------------------------------------------------
.proc on_escape
jsr pop_prefix ; leaves length in X
dec prefix_depth
bra resize_prefix_and_open_jmp
.endproc
;;; ------------------------------------------------------------
.proc down_common
lda current_entry
inc a
cmp num_entries ; past the limit?
bcc :+
pla ; yes - abort subroutine
pla
bra selection_loop
: sta current_entry ; go to next
lda CV
cmp #bottom_row ; at the bottom?
bne handy_rts
inc page_start ; yes, adjust page and
lda #ASCII_ETB ; scroll screen down
jmp COUT ; implicit rts
.endproc
;;; ------------------------------------------------------------
next_drive: ; relay for branches
jmp next_device
inc_resize_prefix_and_open:
inx
resize_prefix_and_open_jmp:
jmp resize_prefix_and_open
;;; ------------------------------------------------------------
.proc on_return
MLI_CALL SET_PREFIX, set_prefix_params
bcs next_drive
ldx current_entry
jsr update_curr_ptr
ldx prefix
: iny
lda (curr_ptr),y
inx
sta prefix,x
cpy curr_len
bcc :-
stx prefix
ldy current_entry
lda types_table,y
bpl inc_resize_prefix_and_open ; is directory???
;; nope, system file, so...
;; fall through
.endproc
;;; ------------------------------------------------------------
.proc launch_sys_file
jsr SETTXT
jsr HOME
lda #HI(ASCII_RIGHT) ; Right arrow ???
jsr COUT
jsr do_open
bcs next_drive
lda #$FF ; Load up to $FFFF bytes
sta read_params_request
sta read_params_request+1
jsr do_read
php
MLI_CALL CLOSE, close_params
plp
bcs next_drive
jmp read_buffer ; Invoke the loaded code
.endproc
;;; ------------------------------------------------------------
.proc cout_string
loop: lda help_string,y
beq handy_rts2
jsr COUT
iny
bra loop
.endproc
;;; ------------------------------------------------------------
;; Compute address/length of curr_ptr/curr_len
;; Call with entry index in X. Returns with Y = 0
.proc update_curr_ptr
stz curr_ptr+1
txa
asl a
rol curr_ptr+1
asl a
rol curr_ptr+1
asl a
rol curr_ptr+1
asl a
rol curr_ptr+1
sta curr_ptr
lda #>filenames
clc
adc curr_ptr+1
sta curr_ptr+1
lda (curr_ptr)
sta curr_len
ldy #0 ; needed by caller
;; fall through
.endproc
handy_rts2:
rts
;;; ------------------------------------------------------------
.proc draw_current_line
lda #2 ; hpos = 2
sta COL80HPOS
ldx current_entry ; vpos = entry - page_start + 2
txa
sec
sbc page_start
inc a
inc a
jsr TABV
lda types_table,x
bmi name ; is sys file?
;; Draw folder glyph
stz COL80HPOS
lda INVFLG
pha
ldy #(folder_string - text_resources) ; Draw folder glyphs
jsr cout_string
pla
sta INVFLG
;; Work around MouseText deactivation bug on Franklin ACE 2X00/500
;; The Apple IIe/IIc/IIgs and the Franklin ACE 2X00/500 all use the
;; screen hole address $4FB to store a video firmware operating
;; mode byte, e.g. whether or not MouseText output is active. For
;; the Apple IIe and IIgs, see the Apple IIe Technical Reference
;; Manual, Appendix J Monitor Firmware Listing for the definition.
;; (Bit 0 is set when MouseText is inactive and clear when active.)
;; The Apple IIc also uses bit 0 the same way, but bits 3 and 7 are
;; also normally set. The Franklin Ace 2X00 and 500 set bit 6
;; when MouseText mode is activated by printing $1B, but fails to
;; clear it when printing $18, so MouseText remains active. This
;; seems to be a bug in the firmware. Work around it by detecting
;; the unique byte signature left behind and resetting it.
lda $4FB ; $4FB = $30 normally (V flag clear)
cmp #$40 ; $4FB = $70 if MT is active (V flag set)
bne :+ ; $4FB = $40 if you try to disable MT!
lda #$30 ; Bug in firmware? We're not sure yet.
sta $4FB ; Any way, set it manually.
:
;; Draw the name
name: jsr space
jsr update_curr_ptr
loop: iny
lda (curr_ptr),y
jsr ascii_cout
cpy curr_len
bcc loop
space: lda #HI(' ')
bne cout ; implicit RTS
.endproc
home: lda #HI(ASCII_EM) ; move cursor to top left
;; Sets high bit before calling COUT
ascii_cout:
ora #$80
cout: jmp COUT
;;; ------------------------------------------------------------
.proc do_open
MLI_CALL OPEN, open_params
lda open_params_ref_num
sta read_params_ref_num
rts
.endproc
.proc do_read
MLI_CALL READ, read_params
rts
.endproc
;;; ------------------------------------------------------------
text_resources := *
.proc help_string
scrcode "RETURN: Select | TAB: Chg Vol | ESC: Back"
.byte 0
.endproc
;; Mousetext sequence: Enable, folder left, folder right, disable
.proc folder_string
.byte $0F,$1B,$D8,$D9,$18,$0E
.byte 0 ; null terminated
.endproc
;;; ------------------------------------------------------------
DEFINE_OPEN_PARAMS open_params, prefix, $1C00
open_params_ref_num := open_params::ref_num
DEFINE_CLOSE_PARAMS close_params
DEFINE_ON_LINE_PARAMS on_line_params, $60, prefix+1
on_line_params_unit := on_line_params::unit_num
DEFINE_SET_PREFIX_PARAMS set_prefix_params, prefix
DEFINE_READ_PARAMS read_params, read_buffer, 0
read_params_ref_num := read_params::ref_num
read_params_request := read_params::request_count
;;; ------------------------------------------------------------
.endproc
.assert .sizeof(selector) <= max_size, error, .sprintf("Must fit in $300 bytes, is: $%x", .sizeof(selector))
install_size = .sizeof(selector)
poporg
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

663
selectors/bye.system.s Normal file
View File

@ -0,0 +1,663 @@
;;; Disassembly of the ProDOS 8 Selector ("BYE") found in ProDOS 1.9
;;; and 2.0.x and installed automatically for 80-column systems.
;;;
;;; Installer wrapper added by Joshua Bell inexorabletash@gmail.com
.setcpu "65C02"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
.include "../inc/ascii.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
;;; ------------------------------------------------------------
;;; ProDOS Technical Reference Manual, 5.1.5.2:
;;;
;;; ProDOS MLI call $65, the QUIT call, moves addresses $D100 through
;;; $D3FF from the second 4K bank of RAM of the language card to
;;; $1000, and executes a JMP to $1000. What initially resides in that
;;; area is Apple's dispatcher code.
;;; ------------------------------------------------------------
;;; Installer
;;; ------------------------------------------------------------
max_size = $300
.proc maybe_install_driver
src := install_src
end := install_src + install_size
dst := $D100 ; Install location in ProDOS (bank 2)
src_ptr := $19
dst_ptr := $1B
sta ALTZPOFF
lda ROMIN
lda ROMIN
lda #>src
sta src_ptr+1
lda #<src
sta src_ptr
lda #>dst
sta dst_ptr+1
lda #<dst
sta dst_ptr
loop: lda (src_ptr)
sta (dst_ptr)
inc src_ptr
bne :+
inc src_ptr+1
: inc dst_ptr
bne :+
inc dst_ptr+1
: lda src_ptr+1
cmp #>end
bne loop
lda src_ptr
cmp #<end
bne loop
lda (src_ptr)
sta (dst_ptr)
sta ALTZPOFF
sta ROMINWB1
sta ROMINWB1
rts
.endproc
;;; ------------------------------------------------------------
;;; Selector
;;; ------------------------------------------------------------
install_src := *
pushorg $1000
.proc bbb
prefix := $280 ; length-prefixed
filenames := $1400 ; each is length + 15 bytes
read_buffer := $2000 ; Also, start location for launched SYS files
mark_params := $60
mark_ref_num := $61
mark_position := $62 ; 3-bytes
next_device_num := $65 ; next device number to try
current_entry := $67 ; index of current entry
num_entries := $68 ; length of |filenames|
curr_len := $69 ; length of current entry name
curr_ptr := $6C ; address of current entry name (in |filenames|)
prefix_depth := $6B ; 0 = root
entry_length := $6E
entries_per_block := $6F
file_count := $70 ; 2 bytes
entry_num := $72
page_start := $73 ; index of first entry shown on screen
max_entries := 128 ; max # of entries; more are ignored
types_table := $74 ; high bit clear = dir, set = sys
top_row := 2 ; first row used on screen
bottom_row := 21 ; last row used on screen
;;; ------------------------------------------------------------
cld ; ProDOS protocol for QUIT routine
lda ROMIN2 ; Page in ROM for reads, writes ignored
;; Point reset vector at this routine
stz RESETVEC
lda #>bbb
sta RESETVEC+1
jsr SETPWRC ; update validity check byte
lda #$A0
jsr SLOT3 ; Activate 80-Column Firmware
;; Update system bitmap
ldx #BITMAP_SIZE-1 ; zero it all out
: stz BITMAP,x
dex
bpl :-
inc BITMAP+BITMAP_SIZE-1 ; protect ProDOS global page
lda #%11001111 ; protect zp, stack, text page 1
sta BITMAP
;; Find device
lda #2
sta mark_params
ldx DEVCNT ; max device num
stx next_device_num
lda DEVNUM
bne check_device
next_device:
ldx next_device_num
lda DEVLST,x
cpx #1
bcs :+
ldx DEVCNT
inx
: dex
stx next_device_num
check_device:
sta on_line_params_unit
MLI_CALL ON_LINE, on_line_params
bcs next_device
stz prefix_depth
lda prefix+1
and #$0F
beq next_device
adc #2
tax
;; Resize prefix to length x and open the directory for reading
.proc resize_prefix_and_open
stx prefix
lda #'/'
sta prefix+1
sta prefix,x
stz prefix+1,x
MLI_CALL OPEN, open_params
bcc :+
;; Open failed
lda prefix_depth ; root?
beq next_device
jsr BELL1 ; no, but failed; beep
jsr pop_prefix ; and go up a level
stx prefix
jmp keyboard_loop
;; Open succeeded
: inc prefix_depth
stz num_entries
lda open_params_ref_num
sta read_params_ref_num
sta mark_ref_num
lda #.sizeof(SubdirectoryHeader)
sta read_params_request
stz read_params_request+1
jsr do_read
bcs finish_read2
;; Store entry_length/entries_per_block/file_count
ldx #3
: lda read_buffer + SubdirectoryHeader::entry_length,x
sta entry_length,x
dex
bpl :-
sta read_params_request
lda #1
sta entry_num
stz mark_position+1
stz mark_position+2
lda file_count
ora file_count+1
bne next_file_entry ; any files?
finish_read2:
bra finish_read
next_file_entry:
bit file_count+1 ; wrap around?
bmi finish_read2
;; TODO: The math here is mysterious; understand and document
floop: lda mark_position+1
and #$FE
sta mark_position+1
ldy entry_num
lda #0
cpy entries_per_block
bcc :+
tay
sty entry_num
inc mark_position+1
carry: inc mark_position+1
: dey
clc
bmi :+
adc entry_length
bcc :-
bcs carry
: adc #4
sta mark_position
MLI_CALL SET_MARK, mark_params
bcs finish_read2
jsr do_read
bcs finish_read2
inc entry_num
lda read_buffer + FileEntry::storage_type_name_length
and #$F0 ; mask off storage_type
beq floop ; inactive file entry
dec file_count
bne :+
dec file_count+1
;; Check read access
: ror read_buffer + FileEntry::access
bcc next_file_entry
;; Check file type
lda read_buffer + FileEntry::file_type
cmp #FT_DIRECTORY
beq :+
cmp #FT_SYSTEM
bne next_file_entry
;; Check to see if we have room
: ldx num_entries
cpx #max_entries
bcs finish_read
;; Store type
sta types_table,x
;; Copy name
jsr update_curr_ptr
ldy #$0F ; name length + 1 (includes length byte)
: lda read_buffer,y
sta (curr_ptr),y
dey
bpl :-
iny ; Y = 0
and #$0F ; mask off name length (remove storage_type)
sta (curr_ptr),y ; store length
;; Next
inc num_entries
bne next_file_entry
next: jmp next_device
finish_read:
MLI_CALL CLOSE, close_params
bcs next
;; fall through
.endproc
;;; ------------------------------------------------------------
.proc draw_screen
jsr SETTXT ; TEXT
jsr HOME ; HOME
lda #23 ; VTAB 23
jsr TABV
;; Print help text
ldy #0
lda #20 ; HTAB 20
jsr cout_string_hpos
;; Draw prefix
jsr home
ldx #0
: lda prefix+1,x
beq :+
jsr ascii_cout
inx
bne :-
: stz current_entry
stz page_start
lda num_entries
beq keyboard_loop ; no entries (empty directory)
row_count := $6A
cmp #bottom_row ; more entries than fit?
bcc :+
lda #(bottom_row - top_row + 1)
: sta row_count
lda #2
sta WNDTOP
sta WNDLFT
lda #22
sta WNDWDTH
sta WNDBTM
loop: jsr draw_current_line
inc current_entry
dec row_count
bne loop
stz current_entry
beq draw_current_line_inv
.endproc
;;; ------------------------------------------------------------
.proc on_up
jsr draw_current_line ; clear inverse selection
ldx current_entry
beq draw_current_line_inv ; first one? just redraw
dec current_entry ; go to previous
lda CV
cmp #top_row ; at the top?
bne draw_current_line_inv ; if not, just draw
dec page_start ; yes, adjust page and
lda #ASCII_SYN ; scroll screen up
bne draw_current_line_with_char
.endproc
;;; ------------------------------------------------------------
.proc on_down
jsr draw_current_line ; clear inverse selection
ldx current_entry
inx
cpx num_entries ; past the limit?
bcs draw_current_line_inv ; yes, just redraw
stx current_entry ; go to next
lda CV
cmp #bottom_row ; at the bottom?
bne draw_current_line_inv ; if not, just draw
inc page_start ; yes, adjust page and
lda #ASCII_ETB ; scroll screen down
;; fall through
.endproc
;;; ------------------------------------------------------------
draw_current_line_with_char:
jsr COUT
draw_current_line_inv:
jsr SETINV
jsr draw_current_line
;; fall through
;;; ------------------------------------------------------------
.proc keyboard_loop
lda KBD
bpl keyboard_loop
sta KBDSTRB
jsr SETNORM
ldx num_entries
beq :+ ; no up/down/return if empty
cmp #HI(ASCII_CR)
beq on_return
cmp #HI(ASCII_DOWN)
beq on_down
cmp #HI(ASCII_UP)
beq on_up
: cmp #HI(ASCII_TAB)
beq next_drive
cmp #HI(ASCII_ESCAPE)
bne keyboard_loop
;; fall through
.endproc
;;; ------------------------------------------------------------
.proc on_escape
jsr pop_prefix ; leaves length in X
dec prefix_depth
bra resize_prefix_and_open_jmp
.endproc
;;; ------------------------------------------------------------
;; Remove level from prefix; returns new length in X
.proc pop_prefix
ldx prefix
loop: dex
lda prefix,x
cmp #'/'
bne loop
cpx #1
bne done
ldx prefix
done: rts
.endproc
;;; ------------------------------------------------------------
next_drive:
jmp next_device
inc_resize_prefix_and_open:
inx
resize_prefix_and_open_jmp:
jmp resize_prefix_and_open
;;; ------------------------------------------------------------
.proc on_return
MLI_CALL SET_PREFIX, set_prefix_params
bcs next_drive
ldx current_entry
jsr update_curr_ptr
ldx prefix
: iny
lda (curr_ptr),y
inx
sta prefix,x
cpy curr_len
bcc :-
stx prefix
ldy current_entry
lda types_table,y
bpl inc_resize_prefix_and_open ; is directory???
;; nope, system file, so...
;; fall through
.endproc
;;; ------------------------------------------------------------
.proc launch_sys_file
jsr SETTXT
jsr HOME
lda #HI(ASCII_RIGHT) ; Right arrow
jsr COUT
MLI_CALL OPEN, open_params
bcs next_drive
lda open_params_ref_num
sta read_params_ref_num
lda #$FF ; Load up to $FFFF bytes
sta read_params_request
sta read_params_request+1
jsr do_read
php
MLI_CALL CLOSE, close_params
plp
bcs next_drive
jmp read_buffer ; Invoke the loaded code
.endproc
;;; ------------------------------------------------------------
cout_string_hpos:
sta CH
.proc cout_string
loop: lda help_string,y
beq done
jsr COUT
iny
bne loop
done: rts
.endproc
;;; ------------------------------------------------------------
;; Compute address/length of curr_ptr/curr_len
;; Call with entry index in X.
.proc update_curr_ptr
stz curr_ptr+1
txa
asl a
rol curr_ptr+1
asl a
rol curr_ptr+1
asl a
rol curr_ptr+1
asl a
rol curr_ptr+1
sta curr_ptr
lda #>filenames
clc
adc curr_ptr+1
sta curr_ptr+1
ldy #0
lda (curr_ptr),y
sta curr_len
rts
.endproc
;;; ------------------------------------------------------------
.proc draw_current_line
lda #2 ; hpos = 2
sta COL80HPOS
ldx current_entry ; vpos = entry - page_start + 2
txa
sec
sbc page_start
inc a
inc a
jsr TABV
lda types_table,x
bmi name ; is sys file?
;; Draw folder glyph
stz COL80HPOS
lda INVFLG
pha
ldy #(folder_string - string_start) ; Draw folder glyphs
jsr cout_string
pla
sta INVFLG
;; Draw the name
name: jsr space
jsr update_curr_ptr
loop: iny
lda (curr_ptr),y
jsr ascii_cout
cpy curr_len
bcc loop
space: lda #HI(' ')
bne cout ; implicit RTS
;; fall through
.endproc
home: lda #HI(ASCII_EM) ; move cursor to top left
;; Sets high bit before calling COUT
ascii_cout:
ora #$80
cout: jmp COUT
;;; ------------------------------------------------------------
.proc do_read
MLI_CALL READ, read_params
rts
.endproc
;;; ------------------------------------------------------------
string_start := *
.proc help_string
scrcode "RETURN: Select | TAB: Chg Vol | ESC: Back"
.byte 0
.endproc
;; Mousetext sequence: Enable, folder left, folder right, disable
.proc folder_string
.byte $0F,$1B,$D8,$D9,$18,$0E
.byte 0 ; null terminated
.endproc
;;; ------------------------------------------------------------
.proc open_params
params: .byte 3
path: .addr prefix
buffer: .addr $1C00
ref_num:.byte 0
.endproc
open_params_ref_num := open_params::ref_num
.proc close_params
params: .byte 1
ref_num:.byte 0
.endproc
.proc on_line_params
params: .byte 2
unit: .byte $60
buffer: .addr prefix+1
.endproc
on_line_params_unit := on_line_params::unit
.proc set_prefix_params
params: .byte 1
path: .addr prefix
.endproc
.proc read_params
params: .byte 4
ref_num:.byte 1
buffer: .word read_buffer
request:.word 0
trans: .word 0
.endproc
read_params_ref_num := read_params::ref_num
read_params_request := read_params::request
.assert read_params::request - bbb <= $300, error, "Must fit in $300 bytes"
;;; ------------------------------------------------------------
.endproc
install_size = $300
poporg
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

499
selectors/selector.system.s Normal file
View File

@ -0,0 +1,499 @@
;;; Disassembly of ProDOS QUIT handler (program selector)
;;; This is a 40-column selector that prompts for prefix/pathname,
;;; installed by default except on 80-column systems in 1.9 and
;;; later.
;;;
;;; Installer wrapper added by Joshua Bell inexorabletash@gmail.com
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
.include "../inc/ascii.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
;;; ------------------------------------------------------------
;;; ProDOS Technical Reference Manual, 5.1.5.2:
;;;
;;; ProDOS MLI call $65, the QUIT call, moves addresses $D100 through
;;; $D3FF from the second 4K bank of RAM of the language card to
;;; $1000, and executes a JMP to $1000. What initially resides in that
;;; area is Apple's dispatcher code.
;;; ------------------------------------------------------------
;;; Installer
;;; ------------------------------------------------------------
max_size = $300
.proc maybe_install_driver
src := install_src
end := install_src + install_size
dst := $D100 ; Install location in ProDOS (bank 2)
src_ptr := $19
dst_ptr := $1B
sta ALTZPOFF
lda ROMIN
lda ROMIN
lda #>src
sta src_ptr+1
lda #<src
sta src_ptr
lda #>dst
sta dst_ptr+1
lda #<dst
sta dst_ptr
loop: ldy #0
lda (src_ptr),y
sta (dst_ptr),y
inc src_ptr
bne :+
inc src_ptr+1
: inc dst_ptr
bne :+
inc dst_ptr+1
: lda src_ptr+1
cmp #>end
bne loop
lda src_ptr
cmp #<end
bne loop
lda (src_ptr),y
sta (dst_ptr),y
sta ALTZPOFF
sta ROMINWB1
sta ROMINWB1
rts
.endproc
;;; ------------------------------------------------------------
;;; Selector
;;; ------------------------------------------------------------
install_src := *
pushorg $1000
.proc selector
PREFIX := $280
;;; ============================================================
;;; Code
;;; ============================================================
;; Page in normal banks, reset screen to 40 columns.
lda ROMIN2
sta CLR80VID
sta CLRALTCHAR
sta CLR80COL
jsr SETNORM
jsr INIT
jsr SETVID
jsr SETKBD
;; Clear system bitmap
ldx #$17
lda #$01
sta BITMAP,x
dex
lda #$00
: sta BITMAP,x
dex
bpl :-
lda #$CF
sta BITMAP
;;; ============================================================
.proc PromptForPrefix
jsr HOME
jsr CROUT
ldx #msg_enter_prefix
jsr ShowMessage
lda #$03
sta CV
jsr CROUT
MLI_CALL GET_PREFIX, get_prefix_params
ldx PREFIX
lda #$00
sta $0281,x
ldx PREFIX
beq L105D
L1052: lda PREFIX,x
ora #$80
sta $05FF,x
dex
bne L1052
L105D: ldx #$00
dec CV
jsr CROUT
input_loop:
jsr RDKEY
cmp #HI(ASCII_CR)
beq try_set_prefix
pha
jsr CLREOL
pla
cmp #HI(ASCII_ESCAPE)
beq PromptForPrefix
cmp #HI(ASCII_CLEAR)
reprompt_for_prefix: ; used as a relay
beq PromptForPrefix
cmp #HI(ASCII_TAB)
beq bad_prefix_key
cmp #HI(ASCII_DELETE)
beq :+
cmp #HI(ASCII_LEFT)
bne not_backspace
: cpx #$00
beq :+
dec CH
dex
: jsr CLREOL
jmp input_loop
not_backspace:
bcs maybe_alphanumeric
bad_prefix_key:
jsr BELL
jmp input_loop
maybe_alphanumeric:
cmp #HI('Z')+1
bcc :+
and #%11011111 ; convert uppercase to lowercase
: cmp #HI('.')
bcc bad_prefix_key
cmp #HI('Z')+1
bcs bad_prefix_key
cmp #HI('9')+1
bcc :+
cmp #HI('A')
bcc bad_prefix_key
;; Place character, if it fits
: inx
cpx #39 ; screen max
bcs reprompt_for_prefix
sta PREFIX,x
jsr COUT
jmp input_loop
try_set_prefix:
cpx #0
beq PromptForPathname
stx PREFIX
MLI_CALL SET_PREFIX, get_prefix_params
bcc PromptForPathname
jsr BELL
lda #$00
reprompt_for_prefix_relay:
beq reprompt_for_prefix ; used as a relay
.endproc
;;; ============================================================
.proc PromptForPathname
jsr HOME
jsr CROUT
ldx #msg_enter_pathname
jsr ShowMessage
reset_pathname:
lda #3
sta CV
jsr CROUT
ldx #0
input_loop:
jsr RDKEY
cmp #HI(ASCII_ESCAPE)
bne :+
lda CH
bne PromptForPathname
beq PromptForPrefix::reprompt_for_prefix_relay
: cmp #HI(ASCII_CLEAR)
reprompt_for_pathname: ; used as a relay
beq PromptForPathname
cmp #HI(ASCII_TAB)
beq bad_pathname_key
cmp #HI(ASCII_DELETE)
beq :+
cmp #HI(ASCII_LEFT)
bne not_backspace2
: jmp backspace
not_backspace2:
bcs maybe_alphanumeric2
bad_pathname_key:
jsr BELL
jmp input_loop
maybe_alphanumeric2:
cmp #HI(ASCII_CR)
beq try_launch_file
cmp #HI('Z')+1
bcc :+
and #%11011111 ; convert uppercase to lowercase
: cmp #HI('.')
bcc bad_pathname_key
cmp #HI('Z')+1
bcs bad_pathname_key
cmp #HI('9')+1
bcc :+
cmp #HI('A')
bcc bad_pathname_key
;; Place character, if it fits
: pha
jsr CLREOL
pla
jsr COUT
inx
cpx #39 ; screen max
bcs reprompt_for_pathname
sta PREFIX,x
jmp input_loop
;;; --------------------------------------------------
try_launch_file:
lda #HI(' ')
jsr COUT
stx PREFIX
MLI_CALL GET_FILE_INFO, get_file_info_params
bcc :+
jmp HandleError
: lda get_file_info_file_type
cmp #FT_SYSTEM
beq :+
lda #$01
jmp HandleError
: lda #$00
sta close_ref_num
MLI_CALL CLOSE, close_params
bcc :+
jmp HandleError
: lda get_file_info_access
and #$01
bne :+
lda #ERR_IO_ERROR
jmp HandleError
: MLI_CALL OPEN, open_params
bcc :+
jmp HandleError
: lda open_ref_num
sta read_ref_num
sta get_eof_ref_num
MLI_CALL GET_EOF, get_eof_params
bcs HandleError
lda get_eof_eof+2
beq :+
lda #ERR_IO_ERROR
bne HandleError
: lda get_eof_eof
sta read_request_count
lda get_eof_eof+1
sta read_request_count+1
MLI_CALL READ, read_params
php
MLI_CALL CLOSE, close_params
bcc launch
: plp
bne HandleError
plp
launch: bcs :-
jmp SYS_ADDR
backspace:
lda CH
beq :+
dex
lda #HI(' ')
jsr COUT
dec CH
dec CH
jsr COUT
dec CH
: jmp input_loop
.endproc
;;; ============================================================
.proc ShowMessage
loop: lda message_table,x
beq done
jsr COUT
inx
bne loop
done: rts
.endproc
;;; ============================================================
.proc HandleError
tmp := $DE
sta tmp
lda #12
sta CV
jsr CROUT
lda tmp
cmp #$01
bne :+
ldx #msg_not_sys
bne show ; always
: cmp #ERR_INVALID_PATHNAME
beq not_found
cmp #ERR_PATH_NOT_FOUND
beq not_found
cmp #ERR_VOL_NOT_FOUND
beq not_found
cmp #ERR_FILE_NOT_FOUND
beq not_found
ldx #msg_io_error
bne show
not_found:
ldx #msg_not_found
show: jsr ShowMessage
jmp PromptForPathname::reset_pathname
.endproc
;;; ============================================================
;;; Messages
;;; ============================================================
message_table:
msg_enter_prefix := * - message_table
scrcode "ENTER PREFIX (PRESS \"RETURN\" TO ACCEPT)"
.byte 0
msg_enter_pathname := * - message_table
scrcode "ENTER PATHNAME OF NEXT APPLICATION"
.byte 0
msg_not_sys := * - message_table
.byte HI(ASCII_BELL)
scrcode "NOT A TYPE \"SYS\" FILE"
.byte 0
msg_io_error := * - message_table
.byte HI(ASCII_BELL)
scrcode "I/O ERROR "
.byte 0
msg_not_found := * - message_table
.byte HI(ASCII_BELL)
scrcode "FILE/PATH NOT FOUND "
.byte 0
;;; ============================================================
;;; ProDOS MLI Call Parameters
;;; ============================================================
;;; GET_FILE_INFO params
get_file_info_params:
.byte $A
get_file_info_pathname:
.addr PREFIX
get_file_info_access:
.byte 0
get_file_info_file_type:
.byte 0
get_file_info_aux_type: .word 0
get_file_info_storage_type: .byte 0
get_file_info_blocks_used: .word 0
get_file_info_mod_date: .word 0
get_file_info_mod_time: .word 0
get_file_info_create_date: .word 0
get_file_info_create_time: .word 0
;;; OPEN params
open_params:
.byte 3
open_pathname:
.addr PREFIX
open_io_buffer:
.addr $1800
open_ref_num:
.byte 0
;;; CLOSE params
close_params:
.byte 1
close_ref_num:
.byte 0
;;; READ params
read_params:
.byte 4
read_ref_num:
.byte 0
read_data_buffer:
.addr SYS_ADDR
read_request_count:
.word 0
read_trans_count:
.word 0
;;; GET_EOF params
get_eof_params:
.byte 2
get_eof_ref_num:
.byte 0
get_eof_eof:
.faraddr 0
;;; GET_PREFIX params
get_prefix_params:
.byte 1
get_prefix_pathname:
.addr PREFIX
;;; ============================================================
.endproc
install_size = $300
poporg
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

35
setup/Makefile Normal file
View File

@ -0,0 +1,35 @@
CAFLAGS = --target apple2enh --list-bytes 0
LDFLAGS = --config apple2-asm.cfg
OUTDIR = ../out
HEADERS = $(wildcard *.inc) $(wildcard ../inc/*.inc)
TARGETS = \
$(OUTDIR)/setup.system.SYS
# For timestamps
MM = $(shell date "+%-m")
DD = $(shell date "+%-d")
YY = $(shell date "+%-y")
DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY)
XATTR := $(shell command -v xattr 2> /dev/null)
.PHONY: clean all
all: $(OUTDIR) $(TARGETS)
$(OUTDIR):
mkdir -p $(OUTDIR)
clean:
rm -f $(OUTDIR)/*.o
rm -f $(OUTDIR)/*.list
rm -f $(TARGETS)
$(OUTDIR)/%.o: %.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.SYS: $(OUTDIR)/%.o
ld65 $(LDFLAGS) -o $@ $<

30
setup/README.md Normal file
View File

@ -0,0 +1,30 @@
# SETUP.SYSTEM
The November 1987 edition of Call-A.P.P.L.E features an article by Sean Nolan, ["SYSTEM.SETUP - A Proposed Startup File Standard"](https://www.callapple.org/magazines-4/call-a-p-p-l-e/setup-system-a-proposed-startup-file-standard/). The article was reprinted in [Beneath Apple DOS ProDOS 2020](https://archive.org/details/beneath-apple-dos-prodos-2020). The proposal combines the ProDOS-8 notion of running the first .SYSTEM file found on disk and the convention of chaining to the next .SYSTEM file, with the ProDOS-16 notion of enumerating a directory of startup files. A main `SETUP.SYSTEM` file is provided which enumerates all files in a `SETUPS/` directory. These "setup files" are BIN or SYS files which work like standard ProDOS-8 drivers. The advantages of this approach are:
* The top level directory only needs one SYSTEM file plus `SETUPS/`, which reduces clutter.
* Each individual setup file is simpler than stand-alone SYSTEM files, as they don't need to implement chaining.
Which approach you use is a matter of taste.
## How do you use these?
If you choose this approach, use a tool like Copy II Plus or [Apple II DeskTop](https://github.com/a2stuff/a2d) to copy and arrange `SETUP.SYSTEM` as the first `.SYSTEM` file in your root directory. Create a `SETUPS/` directory, and copy the appropriate `.SETUPS` files there. A boot disk image catalog that is used on multiple different hardware configurations might include:
* `PRODOS` - the operating system, e.g. [ProDOS 2.4](https://prodos8.com/)
* `SETUP.SYSTEM` - install No-Slot clock driver, if present
* `QUIT.SYSTEM` - invoke the Quit handler immediately, as a program selector
* `BASIC.SYSTEM` - which will not be automatically invoked, but is available to manually invoke
* `SETUPS/NS.CLOCK.SYSTEM` - install No-Slot clock driver, if present
* `SETUPS/ROMXRTC.SYSTEM` - install ROMX clock driver, if present
* `SETUPS/FN.CLOCK.SYSTEM` - install FujiNet clock driver, if present
* `SETUPS/DCLOCK.SYSTEM` - install DClock clock driver, if present
* `SETUPS/CRICKET.SYSTEM` - install Cricket! clock driver, if present
* `SETUPS/ZIPCHIP.SYSTEM` - slow the ZIP CHIP on speaker access, if present
* `SETUPS/RAM.DRV.SYSTEM` - install RamWorks RAM disk driver, if present
* `SETUPS/BUHBYE.SYSTEM` - install a customized Quit handler to replace the built-in one
* `SETUPS/PAUSE.SYSTEM` - pause for a moment, so that you can inspect the output of the above
## Notes
The `SETUP.SYSTEM` program is not modified from the published version except that it no longer clears the screen between running each file in `SETUPS/`, so that any logged output remains visible.

329
setup/setup.system.s Normal file
View File

@ -0,0 +1,329 @@
;;;
;;; SETUP.SYSTEM by Sean Nolan
;;;
;;; A Proposed Startup File Standard
;;;
;;; Published in Call-APPLE, November, 1987
;;; This program is in the public domain.
;;;
;;; This program mimics the ProDOS 16
;;; SYSTEM.SETUP convention. It can be used
;;; to install RAM disk drivers, clock
;;; drivers, and IIGS Classic Desk
;;; Accessories on bootup under ProDOS 8.
;;;
;;; This program loads and calls all BINary
;;; and SYStem files in a subdirectory named
;;; SETUPS. It then looks for the second
;;; system program in the volume directory
;;; whose name ends in ".SYSTEM", and runs
;;; that.
;;;
;;;
;;; Original code clears the screen before/after each driver. Skip it.
NO_HOME = 1
.define ORG .org
.define DFB .byte
.define DA .addr
.define DS .res
.define ASC .byte
.define ASCH scrcode
.feature labels_without_colons +
.feature loose_string_term +
.include "apple2.mac"
.if 0
TYP $FF ;save as a system file
.endif
ORG $BD00 ;load at $2000, but run at $BD00
;;; ****************** equates
CH = $24
IN2 = $280
FILETYPE = IN2+16
AUXCODE = IN2+31
RESET = $3F2
IOBUFFER = $B900
PRODOS = $BF00
QUITVECT = $BF03
DEVNUM = $BF30
BITMAP = $BF58
INIT = $FB2F
VTABZ = $FC24
HOME = $FC58
RDKEY = $FD0C
SETVID = $FE93
SETKBD = $FE89
SETNORM = $FE84
;;; ****************** boot code
VOLNAME = * ;The first 17 bytes are overwritten with the
;name of the volume from which this was run.
LDX #1 ;mark page $BD as free in the system bitmap
STX BITMAP+23 ;so we can put Online result in our code.
DEX ;relocate this program to $BD00-BEFF
LOOP1 LDA $2000,X
STA $BD00,X
LDA $2100,X
STA $BE00,X
INX
BNE LOOP1
DEX
TXS ;init stack pointer
JMP ENTER ;jump to relocated code
DIRNAME DFB 6 ;DirName and VolName must be in the same page
ASCH "SETUPS"
;;; ****** Get name of boot volume
ENTER LDA DEVNUM ;get name of last volume accessed
STA ONLINEN
JSR PRODOS
DFB $C5 ;ONLINE
DA ONLINEP
LDA VOLNAME+1 ;insert a slash nefore the name
AND #$0F
TAX
INX
STX VOLNAME
LDA #$2F ;/
STA VOLNAME+1
LDA QUITVECT+1 ;save original quit vector
STA QUITMOD1+1
LDA QUITVECT+2
STA QUITMOD2+1
;;; ****** Clean up before &amp; after calling files
MAINLOOP LDX #2 ;point Reset vector and ProDOS
LOOP3 LDA JUMP+1,X ;Quit vectors to MainLoop
STA RESET,X
LDA JUMP,X
STA QUITVECT,X
DEX
BPL LOOP3
TXS ;fix stack pointer (X=$FF)
JSR CLOSE ;close all open files
LDX #23 ;clear system bit map
LDA #0
LOOP2 STA BITMAP,X
DEX
BPL LOOP2
LDA #$CF ;mark pages 0,1,4-7 as used
STA BITMAP
LDA #%111 ;mark pages $BD-$BF as used
STA BITMAP+23
LDA $C082 ;Language card off
STA $C00C ;40-column
STA $C00E ;normal character set
STA $C000 ;80STORE off
JSR SETNORM ;normal
JSR INIT ;display text page 1
JSR SETVID ;PR#0
JSR SETKBD ;IN#0
;;; Make sure boot volume is around
;;; AND set prefix to the boot volume
.if NO_HOME
VOLMOUNT
.else
VOLMOUNT JSR HOME
.endif
JSR PRODOS ;set prefix to volume
DFB $C6 ;SET PREFIX
DA PFX2P
BCC VOLOK
LDX #13
LOOP6 LDA VOLTEXT-1,X ;print message "insert volume"
STA $5A8+4,X
DEX
BNE LOOP6
LOOP7 LDA VOLNAME+1,X ;print volume name
ORA #$80
STA $5A8+19,X
INX
CPX VOLNAME
BCC LOOP7
LDA #35 ;go to CH=35, CV=11
STA CH
LDA #11
JSR VTABZ
JSR RDKEY ;wait for keypress
JMP VOLMOUNT
;;; ****** Get name of next file at IN2
VOLOK JSR NEXTFILE ;get name of next file at IN2
BCS EXITLOOP ;if error, we're done with setup files
;;; ****** Load and call setup file
JSR PRODOS ;set prefix to SETUPS
DFB $C6 ;SET PREFIX
DA PFX1P
JSR READFILE ;read in file whose name is at IN@
;and call it if there was no error.
JUMP JMP MAINLOOP ;3 bytes here copied into ProDOS quit vector
DFB $BD^$A5 ;3 bytes here are copied into reset vector
EXITLOOP INC RESET+2 ;scramble reset vector
QUITMOD1 LDA #0 ;restore original quit vector
STA QUITVECT+1
QUITMOD2 LDA #0
STA QUITVECT+2
;;; ****** Look for second system program on disk
LDA #0 ;modify NextFile routine so that it searches
STA NUMBER+1 ;the volume directory for system files only.
STA CHEKTYPE+1
LDA #<VOLNAME ;NamePtr+1 does not bneed to be changed
STA NAMEPTR ;since VolName and DirName are in the same page
NEXTSYS JSR NEXTFILE
BCS QUIT
LDX IN2 ;see if file ends with ".SYSTEM"
LDY #6
LOOP4 LDA IN2,X ;I expect pathname at IN2 in low ASCII
CMP SYSTEXT,Y
BNE NEXTSYS
DEX
DEY
BPL LOOP4
INC MOD+1
MOD LDA #$FF ;the first .SYSTEM program we find is this
BEQ NEXTSYS ;one, so skip it and look for next one.
JSR READFILE ;if successful, never come back
QUIT JSR PRODOS
DFB $65 ;QUIT
DA QUITP
SYSTEXT ASC '.SYSTEM'
;;; Get name of next system file or binary file
;;;
;;; This routine is set up to look for both SYSTEM and
;;; BINary files in the SETUPs subdirectory. It is later
;;; modified to search for SYSTEM files only in the
;;; volume directory. The locations which are changed
;;; are ChekType+1, Number+1, and NamePtr (in the Open
;;; parametr list)
;;;
;;; Returns carry if not found, clear if found.
NEXTFILE JSR PRODOS
DFB $C8 ;OPEN
DA OPENP
BCS CLOSE
LDA OPENN
STA MARKN
STA READN
JSR PRODOS ;Read in first 39 bytes of directory to
DFB $CA ;IN2. This gets the number of entries per
DA READP ;block and number of bytes per entry.
BCS CLOSE
LDA IN2+35 ;save number of bytes per directory entry
STA ENTSIZE+1
LDA IN2+36 ;save number of entries per directory block
STA ENTRIES+1
NEXTENT INC NUMBER+1
NUMBER LDA #0 ;self-modified operand
;;; Retrieve catalog entry #A
LDX #$FE ;build page index in X
LOOP5 INX
INX
ENTRIES CMP #13
BCC OK
SBC ENTRIES+1
BCS LOOP5 ;always
OK TAY
LDA #4 ;1st entry per directory block starts 4 bytes in
LOOP10 DEY
BMI OK2
CLC
ENTSIZE ADC #39 ;add size of directory entry
BCC LOOP10
INX
BNE LOOP10 ;always
OK2 STA MARK ;save mark in file
STX MARK+1
JSR PRODOS ;set the mark
DFB $CE ;SET_MARK
DA MARKP
BCS CLOSE
JSR PRODOS ;read in directory info
DFB $CA ;READ
DA READP
BCS CLOSE
LDA IN2 ;make sure that file is not deleted
BEQ NEXTENT
AND #$0F
STA IN2
LDA FILETYPE ;make sure file type is correct
EOR #$FF ;we look for system programs...
BEQ CLOSE
CHEKTYPE EOR 6^$FF ;...and binary ones.
BNE NEXTENT
CLOSE PHP ;close all files - do not change carry
JSR PRODOS
DFB $CC ;CLOSE
DA CLOSEP
PLP
ANRTS RTS
;;; Read file and call it.
;;; Name should be found at IN2
;;; Prefix must be set.
READFILE LDX FILETYPE ;if a system program, set to read to $2000
LDA #$20
INX
BEQ SETDEST
LDX AUXCODE ;else, set to read in file at address
LDA AUXCODE+1 ;found in auxcode
SETDEST STX READ2D
STA READ2D+1
JSR PRODOS ;Open file
DFB $C8 ;OPEN
DA OPEN2P
BCS CLOSE
LDA OPEN2N
STA READ2N
JSR PRODOS ;Read file into memory
DFB $CA ;READ
DA READ2P
JSR CLOSE
BCS ANRTS
JMP (READ2D) ;call the file just loaded
;;; ****** ProDOS MLI parameter lists
ONLINEP DFB 2 ;Online parameter list
ONLINEN DS 1
DA VOLNAME+1
;;;
PFX1P DFB 1 ;to set prefix to SETUP
DA DIRNAME
;;;
PFX2P DFB 1 ;to set prefix to volume directory
DA VOLNAME
;;;
QUITP DFB 4,0,0,0,0,0,0
;;;
CLOSEP DFB 1,0 ;close all files
;;;
OPENP DFB 3 ;open directory
NAMEPTR DA DIRNAME ;pathname pointer
DA IOBUFFER
OPENN DS 1 ;reference number
;;;
MARKP DFB 2 ;set mark in directory
MARKN DS 1
MARK DS 3
;;;
READP DFB 4 ;read directory
READN DS 1
DA IN2 ;target address
DA 39 ;length
DS 2
;;;
OPEN2P DFB 3 ;open setup or system file
DA IN2
DA IOBUFFER
OPEN2N DS 1
;;;
READ2P DFB 4 ;read setup or system file
READ2N DS 1
READ2D DS 2 ;destination of file is self-mod here
DA $B900-$800 ;ask for largest possible that will fit
DS 2
;;;
VOLTEXT ASCH "INSERT VOLUME"
.if 0
CHK ;checksum - eor for all previous bytes
.else
.byte $E8
.endif

53
textcolors/Makefile Normal file
View File

@ -0,0 +1,53 @@
CAFLAGS = --target apple2enh --list-bytes 0
LDFLAGS = --config apple2-asm.cfg
OUTDIR = ../out
HEADERS = $(wildcard *.inc) $(wildcard ../inc/*.inc)
TARGETS = \
$(OUTDIR)/a2green.system.SYS \
$(OUTDIR)/a2green.setup.SYS \
$(OUTDIR)/bw.system.SYS \
$(OUTDIR)/bw.setup.SYS \
$(OUTDIR)/deepblue.system.SYS \
$(OUTDIR)/deepblue.setup.SYS \
$(OUTDIR)/gray.system.SYS \
$(OUTDIR)/gray.setup.SYS \
$(OUTDIR)/gsblue.system.SYS \
$(OUTDIR)/gsblue.setup.SYS \
$(OUTDIR)/mint.system.SYS \
$(OUTDIR)/mint.setup.SYS \
$(OUTDIR)/pink.system.SYS \
$(OUTDIR)/pink.setup.SYS \
$(OUTDIR)/wb.system.SYS \
$(OUTDIR)/wb.setup.SYS
# For timestamps
MM = $(shell date "+%-m")
DD = $(shell date "+%-d")
YY = $(shell date "+%-y")
DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY)
XATTR := $(shell command -v xattr 2> /dev/null)
.PHONY: clean all
all: $(OUTDIR) $(TARGETS)
$(OUTDIR):
mkdir -p $(OUTDIR)
clean:
rm -f $(OUTDIR)/*.o
rm -f $(OUTDIR)/*.list
rm -f $(TARGETS)
$(OUTDIR)/%.o: %.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.setup.o: %.system.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) -D BUILD_SETUP_FILE --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.BIN $(OUTDIR)/%.SYS: $(OUTDIR)/%.o
ld65 $(LDFLAGS) -o $@ $<

35
textcolors/README.md Normal file
View File

@ -0,0 +1,35 @@
# Text Color Utilities
Author: @chrisparana
These utilities will work with the Apple IIgs or on any Apple II equipped with a VidHD card. Each will set the color of the text, background, and border and then invoke the ProDOS quit handler immediately.
Useful if you'd like to automatically set a theme at boot or select one manually afterwards. Originally created because I wanted my GS to use a different set of colors when booting my ProDOS 8 partition then what I have set as default in the Control Panel.
To have these themes applied at boot, place one of the theme SYSTEM at the end of your load chain.
---
[A2GREEN.SYSTEM](a2green.system.s)
* Apple Monitor II green phosphor theme
[BW.SYSTEM](bw.system.s)
* White text on black background
[DEEPBLUE.SYSTEM](deepblue.system.s)
* White text on deep blue background
[GRAY.SYSTEM](gray.system.s)
* Dark gray text on light gray background
[GSBLUE.SYSTEM](gsblue.system.s)
* The Apple IIgs system defaults
[MINT.SYSTEM](mint.system.s)
* A minty flavored theme
[PINK.SYSTEM](pink.system.s)
* Dark gray text on a pink background with light blue borders
[WB.SYSTEM](wb.system.s)
* Black text on a black background

View File

@ -0,0 +1,25 @@
.setcpu "6502"
.include "apple2.inc"
.include "apple2.mac"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
.proc maybe_install_driver
lda #$C0 ; text: bright greeen on black
sta TBCOLOR
lda #$00 ; border: black
sta CLOCKCTL
rts
.endproc
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

25
textcolors/bw.system.s Normal file
View File

@ -0,0 +1,25 @@
.setcpu "6502"
.include "apple2.inc"
.include "apple2.mac"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
.proc maybe_install_driver
lda #$0f ; text: black on white
sta TBCOLOR
lda #$0f ; border: white
sta CLOCKCTL
rts
.endproc
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

View File

@ -0,0 +1,25 @@
.setcpu "6502"
.include "apple2.inc"
.include "apple2.mac"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
.proc maybe_install_driver
lda #$f2 ; text: white on dark blue
sta TBCOLOR
lda #$02 ; text: dark blue
sta CLOCKCTL
rts
.endproc
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

25
textcolors/gray.system.s Normal file
View File

@ -0,0 +1,25 @@
.setcpu "6502"
.include "apple2.inc"
.include "apple2.mac"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
.proc maybe_install_driver
lda #$5a ; text: dark gray on light gray
sta TBCOLOR
lda #$0a ; border: gray
sta CLOCKCTL
rts
.endproc
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

View File

@ -0,0 +1,25 @@
.setcpu "6502"
.include "apple2.inc"
.include "apple2.mac"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
.proc maybe_install_driver
lda #$f6 ; border: white on medium blue
sta TBCOLOR
lda #$06 ; border: medium blue
sta CLOCKCTL
rts
.endproc
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

25
textcolors/mint.system.s Normal file
View File

@ -0,0 +1,25 @@
.setcpu "6502"
.include "apple2.inc"
.include "apple2.mac"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
.proc maybe_install_driver
lda #$2e ; text: dark blue on bright green
sta TBCOLOR
lda #$0e ; border: bright green
sta CLOCKCTL
rts
.endproc
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

25
textcolors/pink.system.s Normal file
View File

@ -0,0 +1,25 @@
.setcpu "6502"
.include "apple2.inc"
.include "apple2.mac"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
.proc maybe_install_driver
lda #$5b ; text: gray on pink
sta TBCOLOR
lda #$07 ; border: light blue
sta CLOCKCTL
rts
.endproc
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

25
textcolors/wb.system.s Normal file
View File

@ -0,0 +1,25 @@
.setcpu "6502"
.include "apple2.inc"
.include "apple2.mac"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
.proc maybe_install_driver
lda #$f0 ; text: white on black
sta TBCOLOR
lda #$00 ; border: black
sta CLOCKCTL
rts
.endproc
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

46
util/Makefile Normal file
View File

@ -0,0 +1,46 @@
CAFLAGS = --target apple2enh --list-bytes 0
LDFLAGS = --config apple2-asm.cfg
OUTDIR = ../out
HEADERS = $(wildcard *.inc) $(wildcard ../inc/*.inc)
TARGETS = \
$(OUTDIR)/quit.system.SYS \
$(OUTDIR)/pause.system.SYS \
$(OUTDIR)/pause.setup.SYS \
$(OUTDIR)/me.first.system.SYS \
$(OUTDIR)/me.first.setup.SYS \
$(OUTDIR)/date.BIN
# For timestamps
MM = $(shell date "+%-m")
DD = $(shell date "+%-d")
YY = $(shell date "+%-y")
DEFINES = -D DD=$(DD) -D MM=$(MM) -D YY=$(YY)
XATTR := $(shell command -v xattr 2> /dev/null)
.PHONY: clean all
all: $(OUTDIR) $(TARGETS)
$(OUTDIR):
mkdir -p $(OUTDIR)
clean:
rm -f $(OUTDIR)/*.o
rm -f $(OUTDIR)/*.list
rm -f $(TARGETS)
$(OUTDIR)/%.o: %.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.setup.o: %.system.s $(HEADERS)
ca65 $(CAFLAGS) $(DEFINES) -D BUILD_SETUP_FILE --listing $(basename $@).list -o $@ $<
$(OUTDIR)/%.BIN $(OUTDIR)/%.SYS: $(OUTDIR)/%.o
ld65 $(LDFLAGS) -o $@ $<
ifdef XATTR
xattr -wx prodos.AuxType '00 20' $@
endif

10
util/README.md Normal file
View File

@ -0,0 +1,10 @@
# Utilities
* [DATE](date.s)
* Prints the current ProDOS date/time, to verify the time is set and updating. Run from the BASIC prompt: `-DATE`
* [QUIT.SYSTEM](quit.system.s)
* This invokes the ProDOS quit handler immediately. It can be used as the last in a chain of "driver" installers to invoke the program selector, e.g. if you want to also keep `BASIC.SYSTEM` in your root directory but not launch it.
* [PAUSE.SYSTEM](pause.system.s)
* Waits for a fraction of a second before invoking the next driver file. Useful in case the log messages from the driver installers go by too quickly!
* [ME.FIRST.SYSTEM](me.first.system.s)
* Moves the current volume to the end of DEVLST. Niche, but useful in some circumstances.

View File

@ -1,31 +1,32 @@
;;; Query ProDOS and print the current date/time
;;; Output is: MM/DD/YY HH:MM
.setcpu "6502"
.org $2000
.include "apple2.inc"
.include "common.inc"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
start:
PRODOS_CALL MLI_GET_TIME, 0
MLI_CALL GET_TIME, 0
jsr zstrout
HIASCIIZ "Time: "
;;; DATEHI ($BF91) DATELO ($BF90)
;;; 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
;;; +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
;;; DATE: | year | month | day |
;;; +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
;;;
;;; TIMEHI ($BF93) TIMELO ($BF92)
;;; 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
;;; +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
;;; TIME: |0 0 0| hour | |0 0| minute |
;;; +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+
lda TIMELO+1 ; hour
jsr cout_number
lda #HI(':') ; ':'
jsr COUT
lda TIMELO ; minute
jsr cout_number
jsr CROUT
jsr zstrout
HIASCIIZ "Date: "
;; Date
lda DATELO+1 ; month
ror a
@ -39,25 +40,44 @@ start:
and #%00001111
jsr cout_number
lda #(HI '/') ; /
lda #HI('/') ; /
jsr COUT
pla ; day
and #%00011111
jsr cout_number
lda #(HI '/') ; /
lda #HI('/') ; /
jsr COUT
pla ; year
jsr cout_number
lda #HI(' ')
jsr COUT
jsr COUT
;; Time
lda TIMELO+1 ; hour
and #%00011111
jsr cout_number
lda #HI(':') ; ':'
jsr COUT
lda TIMELO ; minute
and #%00111111
jsr cout_number
jsr CROUT
rts
;;; ------------------------------------------------------------
.proc cout_number
ldx #(HI '0')
ldx #HI('0')
cmp #10 ; >= 10?
bcc tens
@ -72,31 +92,7 @@ tens: pha
jsr COUT
units: pla
ora #(HI '0')
ora #HI('0')
jsr COUT
rts
.endproc
.proc zstrout
ptr := $A5
pla ; read address from stack
sta ptr
pla
sta ptr+1
bne skip ; always (since data not on ZP)
next: jsr COUT
skip: inc ptr
bne :+
inc ptr+1
: ldy #0
lda (ptr),y
bne next
lda ptr+1 ; restore address to stack
pha
lda ptr
pha
rts
.endproc

48
util/me.first.system.s Normal file
View File

@ -0,0 +1,48 @@
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
.proc maybe_install_driver
;; Find the current device's index in the list
ldx #0
: lda DEVLST,x
and #$F0
cmp DEVNUM
beq found
inx
cpx DEVCNT
bcc :-
bcs exit ; last one or not found
;; Save it
found: ldy DEVLST,x
;; Move everything up
: lda DEVLST+1,x
sta DEVLST,x
inx
cpx DEVCNT
bne :-
;; Place it at the end
tya
sta DEVLST,x
exit: rts
.endproc
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

30
util/pause.system.s Normal file
View File

@ -0,0 +1,30 @@
.setcpu "6502"
.linecont +
.feature string_escapes
.include "apple2.inc"
.include "apple2.mac"
.include "../inc/apple2.inc"
.include "../inc/macros.inc"
.include "../inc/prodos.inc"
.include "../inc/ascii.inc"
;;; ************************************************************
.include "../inc/driver_preamble.inc"
;;; ************************************************************
.proc maybe_install_driver
ldx #4
: lda #$FF
jsr $FCA8 ; WAIT
dex
bne :-
rts
.endproc
;;; ************************************************************
.include "../inc/driver_postamble.inc"
;;; ************************************************************

24
util/quit.system.s Normal file
View File

@ -0,0 +1,24 @@
.setcpu "6502"
.include "apple2.inc"
.include "../inc/apple2.inc"
.include "../inc/prodos.inc"
.org SYS_ADDR
cld
bit ROMIN2
sta CLR80VID
sta CLRALTCHAR
sta CLR80COL
jsr SETVID
jsr SETKBD
jsr SETNORM
jsr INIT
jsr HOME
MLI_CALL QUIT, quit_params
brk
DEFINE_QUIT_PARAMS quit_params