diff --git a/.github/workflows/commit.yml b/.github/workflows/commit.yml new file mode 100644 index 00000000..6847869f --- /dev/null +++ b/.github/workflows/commit.yml @@ -0,0 +1,32 @@ +name: Commit Build + +on: + push: + branches: + - master + - dev + tags-ignore: + - v* + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: rlespinasse/github-slug-action@1.1.0 + + - uses: actions/checkout@v2 + + - name: Build + run: | + sudo apt-get install libncurses-dev + make + make clean + rm -rf .git* + + - name: Upload Artifact + uses: actions/upload-artifact@v1 + with: + name: RomWBW-${{env.GITHUB_REF_SLUG}}-${{env.GITHUB_SHA_SHORT}} + path: . \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..5e5fbaaf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,51 @@ +name: Release Build + +on: + release: + types: published + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Create Package Label + run: | + LABEL=`echo "$GITHUB_REF" | sed "s|^refs/tags/||"` + echo "::set-env name=PKGLBL::$LABEL" + + - name: Display Diagnostics + run: | + echo PKGLBL: "$PKGLBL" + echo Upload URL: "${{github.event.release.upload_url}}" + echo GITHUB_TOKEN: "${{secrets.GITHUB_TOKEN}}" + + - name: Build + run: | + sudo apt-get install libncurses-dev + make + make clean + rm -rf .git* + + - name: Upload Artifact + uses: actions/upload-artifact@v1 + with: + name: RomWBW-${{env.PKGLBL}}-Package + path: . + + - name: Create Package Archive + run: | + zip -r Package.zip . + + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + with: + upload_url: ${{github.event.release.upload_url}} + asset_path: Package.zip + asset_name: RomWBW-${{env.PKGLBL}}-Package.zip + asset_content_type: application/zip \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4782ed5c --- /dev/null +++ b/.gitignore @@ -0,0 +1,96 @@ +# Not sure what patterns to apply +# So ignoring all generated files explicitly + +**/*.[Bb][Ii][Nn] +**/*.[Cc][Oo][Mm] +**/*.[Rr][Oo][Mm] +**/*.com +**/*.eeprom +**/*.hex +**/*.img +**/*.lib +**/*.lst +**/*.o +**/*.prn +**/*.rel +**/*.sym +**/*.sys +**/*.tmp +**/*/font*.asm + +Binary/**/*.mym +Binary/**/*.pt3 + +Source/**/eeprom +Source/Apps/Assign.com +Source/Apps/FDU/FDU.COM +Source/Apps/Format.com +Source/Apps/IntTest.com +Source/Apps/Mode.com +Source/Apps/OSLdr.com +Source/Apps/RTC.com +Source/Apps/SysCopy.com +Source/Apps/SysGen.com +Source/Apps/Talk.com +Source/Apps/Timer.com +Source/Apps/Tune/Tune.com +Source/BPBIOS/bpsys.bak +Source/BPBIOS/bpsys.dat +Source/BPBIOS/def-ww.lib +Source/CPM3/bios3.spr +Source/CPM3/bnkbios3.spr +Source/CPM3/gencpm.dat +Source/CPM3/options.lib +Source/CPM3/zpmbios3.spr +Source/HBIOS/Blank512KB.dat +Source/HBIOS/build.inc +Source/Images/blank144 +Source/Images/blankhd +Source/Prop/Spin/ParPortProp.list +Source/Prop/Spin/PropIO.list +Source/Prop/Spin/PropIO2.list +Source/ZPM3/bnkbios3.spr +Source/ZPM3/gencpm.com +Source/ZPM3/gencpm.com +Source/ZPM3/gencpm.dat + +Tools/Linux +Tools/Darwin + +Tools/unix/bin2asm/bin2asm +Tools/unix/cpmtools/cpmchattr +Tools/unix/cpmtools/cpmchmod +Tools/unix/cpmtools/cpmcp +Tools/unix/cpmtools/cpmls +Tools/unix/cpmtools/cpmrm +Tools/unix/cpmtools/fsck.cpm +Tools/unix/cpmtools/fsed.cpm +Tools/unix/cpmtools/mkfs.cpm +Tools/unix/lzsa/lzsa +Tools/unix/uz80as/uz80as +Tools/unix/zx/config.h +Tools/unix/zx/zx + +!Source/ver.lib +!Source/Apps/FAT/FAT.COM +!Source/BPBIOS/bpbuild.com +!Source/BPBIOS/movp112.com +!Source/BPBIOS/Z34RCP11/cledinst.com +!Source/BPBIOS/Z34RCP11/cledsave.com +!Source/Fonts +!Source/Images/**/*.[Cc][Oo][Mm] +!Source/RomDsk/**/*.[Cc][Oo][Mm] +!Source/UBIOS/FSFAT.BIN +!Source/UBIOS/UNA-BIOS.BIN +!Source/ZCCP/*.[Cc][Oo][Mm] +!Source/ZCPR-DJ/*.[Cc][Oo][Mm] +!Source/ZPM3/*.[Cc][Oo][Mm] +!Source/ZSDOS/*.[Cc][Oo][Mm] +!Tools/cpm/bin +!Tools/unix/zx +!Tools/zx + +Source/ZPM3/gencpm.com +Source/ZPM3/startzpm.com +Source/ZPM3/zccp.com +Source/ZPM3/zpmldr.com diff --git a/Binary/Apps/Clean.cmd b/Binary/Apps/Clean.cmd index cf656f2a..1427d697 100644 --- a/Binary/Apps/Clean.cmd +++ b/Binary/Apps/Clean.cmd @@ -2,5 +2,5 @@ setlocal if exist *.com del *.com - -setlocal & cd Tunes && call Clean || exit /b 1 & endlocal +if exist Tunes\*.pt? del Tunes\*.pt? +if exist Tunes\*.mym del Tunes\*.mym diff --git a/Binary/Apps/Makefile b/Binary/Apps/Makefile new file mode 100644 index 00000000..cfa80100 --- /dev/null +++ b/Binary/Apps/Makefile @@ -0,0 +1,10 @@ +TOOLS = ../../Tools +MOREDIFF := $(shell $(TOOLS)/unix/casefn.sh *.com Tunes/*) + +include $(TOOLS)/Makefile.inc + +all:: + mkdir -p Tunes + +clobber:: + rm -f *.bin *.com *.img *.rom *.pdf *.log *.eeprom *.COM *.BIN Tunes/*.mym Tunes/*.pt? diff --git a/Binary/Apps/Tunes/Clean.cmd b/Binary/Apps/Tunes/Clean.cmd deleted file mode 100644 index b57e2e0c..00000000 --- a/Binary/Apps/Tunes/Clean.cmd +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -setlocal - -if exist *.pt? del *.pt? diff --git a/Binary/Apps/Tunes/Demo.mym b/Binary/Apps/Tunes/Demo.mym deleted file mode 100644 index 255fc160..00000000 Binary files a/Binary/Apps/Tunes/Demo.mym and /dev/null differ diff --git a/Binary/Apps/Tunes/Demo1.mym b/Binary/Apps/Tunes/Demo1.mym deleted file mode 100644 index b224f321..00000000 Binary files a/Binary/Apps/Tunes/Demo1.mym and /dev/null differ diff --git a/Binary/Apps/Tunes/Demo3.mym b/Binary/Apps/Tunes/Demo3.mym deleted file mode 100644 index 808db891..00000000 Binary files a/Binary/Apps/Tunes/Demo3.mym and /dev/null differ diff --git a/Binary/Apps/Tunes/Demo3mix.mym b/Binary/Apps/Tunes/Demo3mix.mym deleted file mode 100644 index b5981848..00000000 Binary files a/Binary/Apps/Tunes/Demo3mix.mym and /dev/null differ diff --git a/Binary/Apps/Tunes/Demo4.mym b/Binary/Apps/Tunes/Demo4.mym deleted file mode 100644 index ed2e8a85..00000000 Binary files a/Binary/Apps/Tunes/Demo4.mym and /dev/null differ diff --git a/Binary/DiskList.txt b/Binary/DiskList.txt new file mode 100644 index 00000000..8ecedd63 --- /dev/null +++ b/Binary/DiskList.txt @@ -0,0 +1,167 @@ +*********************************************************************** +*** *** +*** R o m W B W *** +*** *** +*** Z80/Z180 System Software *** +*** *** +*********************************************************************** + +This directory ("Binary") is part of the RomWBW System Software +distribution archive. Refer to the ReadMe.txt file in this +directory for more information on the overall contents of the +directory. + +RomWBW includes a set of disk images that are ready to copy onto +a floppy or hard/CF/SD disk. You can use your modern computer +(Windows/Linux/Mac) to copy the disk image file onto your disk +media. The disk media will then be ready to use in your RomWBW +System. + +A description of the disk images is provided later in this file. +For more information on the creatioin of these images including +instructions for customizing them or creating your own, refer to +the ReadMe.txt file in the Source\Images directory. + +Installing Images +----------------- + +The following instructions apply to Windows computers. Alternatively, +you can use the "dd" command on Linux or Mac. + +First of all, a MAJOR WARNING!!!! The tools described below are +quite capable of obliterating your running Windows system drive. Use +with extreme caution and make sure you have backups. + +To install a floppy image on floppy media, you can use the tool +called RaWriteWin. This tool is included in the Tools directory of +the distribution. This tool will write your floppy image (fd_xxx.img) +to a floppy disk using a raw block transfer. The tool is GUI based +and it's operation is self explanatory. + +To install a hard disk image on a CF card or SD card, you must have +the appropriate media card slot on your computer. If you do, you can +use the tool called Win32DiskImager. This tool is also included in +the Tools directory of the distribution. It will write your +hard disk image (hd_xxx.img) to the designated media card. This tool +is also GUI based and self explanatory. + +The use of the SIMH emulator is outside of the scope of this document. +However, if you use SIMH, you will find that you can attach the hard +disk images to the emulator with lines such as the following in your +SIMH configuration file: + + | attach hdsk0 hd_cpm22.img + | set hdsk0 format=HDSK + | set hdsk0 geom=T:2048/N:256/S:512 + | set hdsk0 wrtenb + +Making Disk Images Bootable +--------------------------- + +The Operating System disk images below are ready to boot by the +RomWBW Boot Loader. However, if you update your RomWBW ROM, then +you should also update the system tracks of your bootable disk +images. You would use SYSCOPY to do this. SYSCOPY can also be +used to make a disk bootable if it is not already bootable. + +You would use a command like the following to make drive C bootable: + + | B>SYSCOPY C:=CPM.SYS + +The system file to use depends on the operating system you are trying +to boot from the slice you are initializing with SYSCOPY: + + CP/M 2.2 - cpm.sys + ZSDOS 1.1 - zsys.sys + CP/M 3 - cpmldr.sys + ZPM3 - cpmldr.sys + +Slices +------ + +A RomWBW CP/M filesystem is fixed at 8MB. This is because it is the +largest size filesystem supported by all common CP/M variants. Since +all modern hard disks (including SD Cards and CF Cards) are much +larger than 8MB, RomWBW supports the concept of "slices". This +simply means that you can concatenate multiple CP/M filesystems (up +to 256 of them) on a single physical hard disk and RomWBW will allow +you to assign drive letters to them and treat them as multiple +independent CP/M drives. + +With the exception of the hd_combo image, each of the disk images +includes a single CP/M file system (i.e., a single slice). However, +you can easily create a multi-slice disk image by merely concatenating +multiple images together. For example, if you wanted to create a 2 +slice disk image that has ZSDOS in the first slice and Wordstar in +the second slice, you could use the following command from a Windows +command prompt: + + | C:\RomWBW\Binary>copy /b hd_zsdos.img + hd_ws.img hd_multi.img + +You can now write hd_multi.img onto your SD or CF Card and you will +have ZSDOS in the first slice and Wordstar in the second slice. + +The hd_combo disk image is an example of this. It contains several +slices in one image file. The contents of this special disk image +are described below. + +The concept of slices applies ONLY to hard disks. Floppy disks are +not large enough to support multiple slices. + +Disk Image Contents +------------------- + +What follows is a brief description of the contents of the +disk images automatically provided in the RomWBW distribution. +Note that all of the OS images include the RomWBW custom +support apps. + +cpm22 - DRI CP/M 2.2 (Bootable Floppy and Hard Disk) + + Standard DRI CP/M 2.2 distribution files along with a few commonly + used utilities. + +zsdos - ZCPR1 + ZSDOS 1.1 (Bootable Floppy and Hard Disk) + + Contains ZCPR1 and ZSDOS 1.1. This is roughly equivalent to the + ROM boot contents, but provides a full set of the applications + and related files that would not all fit on the ROM drive. + +nzcom - NZCOM (Bootable Floppy and Hard Disk) + + Standard NZCOM distribution. Note that you will need to run the + NZCOM setup before this will run properly. You will need + to refer to the NZCOM documentation. + +cpm3 - DRI CP/M3 (Bootable Floppy and Hard Disk) + + Standard DRI CP/M 3 adaptation for RomWBW that is ready to run. + It can be started by running CPMLDR. + +zpm3 - ZPM3 (Bootable Floppy and Hard Disk) + + Simeon Cran's ZCPR 3 compatible OS for CP/M 3 adapted for RomWBW and + ready to run. It can be started by running CPMLDR (which seems + wrong, but ZPMLDR is somewhat broken). + +ws4 - WordStar 4 (Floppy and Hard Disk) + + Micropro Wordstar 4 full distribution. This image is not bootable + and is intended to be added as an additional slice to an OS image. + +bp - BPBIOS (Hard Disk only) + + Adaptation of BPBIOS for RomWBW. This is NOT complete and NOT + useable in it's current state. + +combo - Multi-Boot Combination (Bootable Hard Disk) + + A pre-created combo image that contains the following slices. The + slices are identical to the individual images listed above. + + Slice 0: cpm22 (bootable) + Slice 1: zsdos (bootable) + Slice 2: nzcom (bootable) + Slice 3: cpm3 (bootable) + Slice 4: zpm3 (bootable) + Slice 5: ws4 (not bootable) diff --git a/Binary/Makefile b/Binary/Makefile new file mode 100644 index 00000000..c856d1b8 --- /dev/null +++ b/Binary/Makefile @@ -0,0 +1,8 @@ +TOOLS = ../Tools +MOREDIFF := $(shell $(TOOLS)/unix/casefn.sh *.img *.rom *.com *.eeprom) +SUBDIRS = Apps + +include $(TOOLS)/Makefile.inc + +clobber:: + rm -f *.bin *.com *.img *.rom *.pdf *.log *.eeprom diff --git a/Binary/ReadMe.txt b/Binary/ReadMe.txt index 447e3c35..70156630 100644 --- a/Binary/ReadMe.txt +++ b/Binary/ReadMe.txt @@ -17,15 +17,14 @@ released the directory is populated with the default output files. However, the output of custom builds will be placed in this directory as well. -If you only see a few files in this directory, then you downloaded just -the source from GitHub. To retrieve the full release download package, -go to https://github.com/wwarthen/RomWBW. On this page, look for the -text "XX releases" where XX is a number. Click on this text to go to the -releases page. On this page, you will see the latest releases listed. -For each release, you will see a package file called something like -"RomWBW-2.9.0-Package.zip". Click on the package file for the release -you want to download. - +If you only see a few files in this directory, then you downloaded +just the source from GitHub. To retrieve the full release download +package, go to https://github.com/wwarthen/RomWBW. On this page, +look for the text "XX releases" where XX is a number. Click on this +text to go to the releases page. On this page, you will see the +latest releases listed. For each release, you will see a package +file called something like "RomWBW-2.9.0-Package.zip". Click on the +package file for the release you want to download. ROM Firmware Images (_.rom) ------------------------------------- @@ -33,15 +32,15 @@ ROM Firmware Images (_.rom) The files with a ".rom" extension are binary images ready to program into an appropriate PROM. These files are named with the format _.rom. refers to the primary platform such as Zeta, -N8, Mark IV, etc. refers to the specific configuration. When -released, there will be a standard configuration ("std") for each +N8, Mark IV, etc. refers to the specific configuration. In +general, there will be a standard configuration ("std") for each platform. So, for example, the file called MK4_std.rom is a ROM image for the Mark IV with the standard configuration. If a custom configuration called "custom" is created and built, a new file called MK4_custom.rom will be added to this directory. Documentation of the pre-built ROM Images is contained in the -RomList.txt file. +RomList.txt file in this directory. ROM Executable Images (_.com) --------------------------------------- @@ -49,22 +48,16 @@ ROM Executable Images (_.com) When a ROM image (".rom") is created, an executable version of the ROM is also created. These files have the same naming convention as the ROM Image files, but have the extension ".com". These files can -be copied to a working system and run like a normal application. +be copied to a working system and run like a normal CP/M application. When run on the target system, they install in RAM just like they had -been programmed into the ROM. This allows a new ROM build to be -tested without reprogramming the actual ROM. - -ROM Binary Images (_.img) ------------------------------------ +been loaded from ROM. This allows a new ROM build to be tested +without reprogramming the actual ROM. -Also when a ROM image is created, a third variation of the ROM is -created again with the same naming convention, but with the extension -of .img. These files are similar to the .com files in that they can -be used to test a ROM build without actually programming a new ROM. -The .img files are specifically for loading via UNA from a FAT file -system. The functionality of the UNA FAT file system loader is -beyond the scope of this document. +WARNING: In a few cases the .com file is too big to load. If you get +a message like "Full" or "BAD LOAD" when trying to load one of the +.com files, it is too big. In these cases, you will not be able to +test the ROM prior to programming it. VDU ROM Image (vdu.rom) ----------------------- @@ -73,7 +66,7 @@ The VDU video board requires a dedicated onboard ROM containing the font data. The "vdu.rom" file contains the binary data to program onto that chip. -Disk Images (fd*.img, hd*.img) +Disk Images (fd_*.img, hd_*.img) ------------------------------ RomWBW includes a mechanism for generating floppy disk and hard disk @@ -84,23 +77,28 @@ RomWBW-based system. Essentially, these files contain prepared floppy and hard disk images with a large set of programs and related files. By copying the contents of these files to appropriate media as described below, you -can quickly create ready-to-use media. +can quickly create ready-to-use media. Win32DiskImager or +RawWriteWin can be used to copy images directly to media. These +programs are included in the RomWBW Tools directory. -The fd*.img files are floppy disk images. They are sized for 1.44MB +The fd_*.img files are floppy disk images. They are sized for 1.44MB floppy media and can be copied to actual floppy disks using RawWriteWin (as long as you have access to a floppy drive on your Windows computer). The resulting floppy disks will be usable on any RomWBW-based system with floppy drive(s). -Likewise, the hd*.img files are hard disk images. Each file is +Likewise, the hd_*.img files are hard disk images. Each file is intended to be copied to the start of any type of hard disk media (typically a CF Card or SD Card). The resulting media will be usable on any RomWBW-based system that accepts the corresponding media type. -Note that the contents of the floppy/hard disk images are created by +Documentation of the pre-built disk images is contained in the +DiskList.txt file in this directory. + +The contents of the floppy/hard disk images are created by the BuildImages.cmd script in the Source directory. Additional information on how to generate custom disk images is found in the -Source\Images directory. +Source\Images ReadMe.txt file. Propeller ROM Images (*.eeprom) ------------------------------- diff --git a/Binary/RomList.txt b/Binary/RomList.txt index 5131da59..128e400e 100644 --- a/Binary/RomList.txt +++ b/Binary/RomList.txt @@ -6,70 +6,84 @@ *** *** *********************************************************************** -This directory ("Binary") is part of the RomWBW System Software -distribution archive. Refer to the ReadMe.txt file in this -directory for more information on the overall contents of the +This directory ("Binary") is part of the RomWBW System Software +distribution archive. Refer to the ReadMe.txt file in this +directory for more information on the overall contents of the directory. -When distributed, RomWBW contains a set of pre-built ROM images that -are ready to program onto the EEPROM of any of the Z80/Z180 based -RetroBrew Computers CPU boards. Additionally, any custom built ROM +When distributed, RomWBW contains a set of pre-built ROM images that +are ready to program onto the EEPROM of any of the Z80/Z180 based +RetroBrew Computers CPU boards. Additionally, any custom built ROM images will be placed in this directory. -All of the pre-built ROM images are 512KB. This size is compatible -with all of the Z80/Z180 systems. Some systems can accept different -size ROM images. Creating alternative sizes requires a custom ROM +All of the pre-built ROM images are 512KB. This size is compatible +with all of the Z80/Z180 systems. Some systems can accept different +size ROM images. Creating alternative sizes requires a custom ROM build (see ReadMe.txt in the Source directory). -It is critical that the right ROM Imgae be selected for the target -platform being used. The table below indicates the correct ROM +It is critical that the right ROM Imgae be selected for the target +platform being used. The table below indicates the correct ROM image to use for each platform: SBC V1/V2 SBC_std.rom + SBC SimH SBC_simh.rom Zeta V1 ZETA_std.rom Zeta V2 ZETA2_std.rom N8 N8_std.rom Mark IV MK4_std.rom - RC2014 RC_std.rom - RC2014 w/ Z180 RC180_std.rom - -You will find there is one additional ROM image called -"UNA_std.rom". This ROM image is an UNA-based RomWBW ROM image. As -such, this ROM image can be used on any Z80/Z180 platform supported -by John Coffman's UNA BIOS. Refer to RetroBrew Computers Wiki for + RC2014 w/ Z80 RCZ80_std.rom + RC2014 w/ Z180 RCZ180_nat.rom (native Z180 memory addressing) + RC2014 w/ Z180 RCZ180_ext.rom (external 512K RAM/ROM module) + SC-series SC126, SC130 + Easy Z80 EZZ180_std.rom + Dyno DYNO_std.rom + +You will find there is one additional ROM image called +"UNA_std.rom". This ROM image is an UNA-based RomWBW ROM image. As +such, this ROM image can be used on any Z80/Z180 platform supported +by John Coffman's UNA BIOS. Refer to RetroBrew Computers Wiki for more information on UNA hardware support. -For each of the ROM Images (".rom"), there are corresponding files -with the extensions of ".com" and ".img". The .com variant can be -copied to a functional RomWBW-based system and executed like a -normal application under CP/M or Z-System. This will load the new -ROM on-the-fly. It is an excellent way to test a ROM Image before -actually burning it. Similarly, the .img files can be loaded using +For each of the ROM Images (".rom"), there are corresponding files +with the extensions of ".com" and ".img". The .com variant can be +copied to a functional RomWBW-based system and executed like a +normal application under CP/M or Z-System. This will load the new +ROM on-the-fly. It is an excellent way to test a ROM Image before +actually burning it. Similarly, the .img files can be loaded using the UNA FAT loader for testing. -All of the standard ROM Images are configured with: +WARNING: In a few cases the .com file is too big to load. If you get +a message like "Full" or "BAD LOAD" when trying to load one of the +.com files, it is too big. In these cases, you will not be able to +test the ROM prior to programming it. + +All of the standard ROM Images are configured for: - 512KB ROM Disk - 512KB RAM Disk - - 38.4Kbps baud serial console (RC2014 is determined by hardware) + - 38.4Kbps baud serial console (*) - Auto-discovery of all serial ports -All hard disk type devices (IDE, PPIDE, CF Card, SD Card) will be -automatically assigned two drive letters per device. The drive -letters will refer to the first 2 slices of the device. The ASSIGN -command can be used to display and reassign drives to disk devices +* RC2014 and Stephen Cousins' kits run at 115,200Kbps baud + +All hard disk type devices (IDE, PPIDE, CF Card, SD Card) will be +automatically assigned two drive letters per device. The drive +letters will refer to the first 2 slices of the device. The ASSIGN +command can be used to display and reassign drives to disk devices and slices as desired. Standard ROM Image Notes ------------------------ -The standard ROM images will detect and install support for certain -devices and peripherals that are on-board or frequently used with -each platform as documented below. If the device or peripheral is -not detected at boot, the ROM will simply bypass support +The standard ROM images will detect and install support for certain +devices and peripherals that are on-board or frequently used with +each platform as documented below. If the device or peripheral is +not detected at boot, the ROM will simply bypass support appropriately. -SBC: - - Includes support for PPIDE/CF Card(s) connected to on-board +SBC (SBC_std.rom): + - CPU speed is detected at startup + - Console on onboard UART serial port at 38400 baud + - Includes support for PPIDE/CF Card(s) connected to on-board parallel port. - Includes support for CVDU and VGA3 boards. If detected at startup, support for video and keyboard is installed @@ -85,17 +99,39 @@ SBC: circuit which is likely to cause system instability. SBC V2 does not have this issue. -ZETA/ZETA2: +SBC (SBC_simh.rom): + - SBC variant customized to run under SimH + - Implments two emulated SimH hard disk images + - Uses SimH RTC + +ZETA (ZETA_std.rom): + - CPU speed is detected at startup + - Console on onboard UART serial port at 38400 baud + - Includes support for on-board floppy disk controller and + two attached floppy disks. + - Auto-detects ParPortProp and includes support for it if it + is attached. + - If ParPortProp is installed, initial console output is + determined by JP1. If JP1 is shorted, console will go to + on-board serial port, if JP1 is open, console will go to + ParPortProp video and keyboard ports. + +ZETA2 (ZETA2_std.rom): + - CPU speed is detected at startup + - Console on onboard UART serial port at 38400 baud - Includes support for on-board floppy disk controller and two attached floppy disks. - Auto-detects ParPortProp and includes support for it if it is attached. + - Uses CTC to generate periodic timer interrupts. - If ParPortProp is installed, initial console output is determined by JP1. If JP1 is shorted, console will go to on-board serial port, if JP1 is open, console will go to ParPortProp video and keyboard ports. -N8: +N8 (N8_std.rom): + - CPU speed is detected at startup + - Console on Z180 onboard primary ASCI serial port at 38400 baud - Includes support for on-board floppy disk controller and two attached floppy disks. - Includes support for on-board TMS9918 video and keyboard @@ -103,7 +139,9 @@ N8: - Includes support for on-board SD Card as hard disk and assumes a production level N8 board (date code >= 2312). -MK4: +MK4 (MK4_std.rom): + - CPU speed is detected at startup + - Console on Z180 onboard primary ASCI serial port at 38400 baud - Includes support for on-board IDE port (CF Card via adapter). - Includes support for on-board SD Card port. - Auto-detects PropIO or PropIO V2 and installs associated @@ -112,21 +150,69 @@ MK4: startup, support for video and keyboard is installed including VT-100/ANSI terminal emulation. -RC2014: +RCZ80 (RCZ80_std.rom): + - Assumes CPU oscillator of 7.3728 MHz - Requires 512K RAM/ROM module - Auto detects Serial I/O Module (ACIA) and Dual Serial Module (SIO/2). Either one may be used. - - Includes support for Compact Flash Module - - Support for PPIDE Module may be enabled in config + - Console on whichever serial module is installed, + but will use the SIO/2 if both are installed. Baud + rate is determined by hardware, but normally 115200. + - Includes support for RC2014 Compact Flash Module + - Support for RC2014 PPIDE Module may be enabled in config - Support for Scott Baker SIO board may be enabled in config - Support for Scott Baker floppy controllers (SMC & WDC) may be enabled in config -RC2014 w/ Z180: +RCZ80 w/ KIO (RCZ80_kio.rom): + - Assumes CPU oscillator of 7.3728 MHz - Requires 512K RAM/ROM module - - Console attached to Z180 onboard serial ports at 38400 baud - - Includes support for Compact Flash Module - - Support for PPIDE Module may be enabled in config + - Requires KIO module + - Console on KIO primary serial port at 115200 baud + - Includes support for RC2014 Compact Flash Module + - Includes support for RC2014 PPIDE Module + - Support for Scott Baker SIO board may be enabled in config + - Support for Scott Baker floppy controllers (SMC & WDC) may + be enabled in config + +RCZ180 (RCZ180_nat.rom & RCZ180_ext.rom): + - Assumes CPU oscillator of 18.432 MHz + - Console on Z180 onboard primary ASCI serial port at 115200 baud + - Includes support for RC2014 Compact Flash Module + - Includes support for RC2014 PPIDE Module + - Support for alternative serial modules may be enabled in config + - Support for Scott Baker floppy controllers (SMC & WDC) may + be enabled in config + - You must pick the _nat or _ext variant depending on which + memory module you are using: + - RCZ180_nat.rom uses the built-in Z180 memory manager + for use with memory modules allow direct physical + addressing of memory, such as the SC119 + - RCZ180_ext.rom uses external bank management to access + memory, such as the 512K RAM/ROM module. + +SCZ180 (SCZ180_126.rom, SCZ180_130.rom, SCZ180_131.rom): + - Assumes CPU oscillator of 18.432 MHz + - Console on Z180 onboard primary ASCI serial port at 115200 baud + - Includes support for RC2014 Compact Flash Module + - Includes support for RC2014 PPIDE Module - Support for alternative serial modules may be enabled in config - Support for Scott Baker floppy controllers (SMC & WDC) may be enabled in config + - The 3 different variants of SCZ180 are provided to match the + 3 corresponding systems (SC126, SC130, and SC131) designed by + Stephen Cousins. + +EZZ80 (EZZ80_std.rom): + - Assumes CPU oscillator of 10.000 MHz + - Console on primary SIO serial port at 115200 baud + - Includes support for on-board SIO + - Includes support for RC2014 Compact Flash Module + - Includes support for RC2014 PPIDE Module + +DYNO (DYNO_std.rom): + - Assumes CPU oscillator of 18.432 MHz + - Console on Z180 onboard serial ports at 38400 baud + - Includes support for BQ4842 RTC + - Includes support for onboard PPIDE + - Support for Dyno floppy controllers may be enabled in config diff --git a/Doc/CPM Manual.pdf b/Doc/CPM Manual.pdf index bdcf69ad..b7de4f24 100644 Binary files a/Doc/CPM Manual.pdf and b/Doc/CPM Manual.pdf differ diff --git a/Doc/CPM3 Command Summary.pdf b/Doc/CPM3 Command Summary.pdf new file mode 100644 index 00000000..b66d920f Binary files /dev/null and b/Doc/CPM3 Command Summary.pdf differ diff --git a/Doc/CPM3 Programmers Guide.pdf b/Doc/CPM3 Programmers Guide.pdf new file mode 100644 index 00000000..4914042f Binary files /dev/null and b/Doc/CPM3 Programmers Guide.pdf differ diff --git a/Doc/CPM3 System Guide.pdf b/Doc/CPM3 System Guide.pdf new file mode 100644 index 00000000..422a0c95 Binary files /dev/null and b/Doc/CPM3 System Guide.pdf differ diff --git a/Doc/CPM3 Users Guide.pdf b/Doc/CPM3 Users Guide.pdf new file mode 100644 index 00000000..df46c910 Binary files /dev/null and b/Doc/CPM3 Users Guide.pdf differ diff --git a/Doc/ChangeLog.txt b/Doc/ChangeLog.txt index c6ab07b7..8d28bd22 100644 --- a/Doc/ChangeLog.txt +++ b/Doc/ChangeLog.txt @@ -1,10 +1,87 @@ +Version 3.1 +----------- +- WBW: Refactored ROM Loader +- WBW: INTRTC periodic timer based clock +- WBW: FDISK80 updated to allow reserving up to 256 slices +- WBW: Added support dual 16C550 UART on RC2014 platform +- WBW: Made .com images smaller (contain only Z-System now) +- WBW: Support automatic clock hardware detection and fallback + +Version 3.0.1 +------------- +- WBW: Increase XModem timeout waiting for host to start sending +- WBW: Update TMS driver to dynamically increase Z180 I/O W/S inside driver +- MJS: Update CLRDIR for CP/M 3 compatibility +- WBW: Corrected cursor on/off esc sequence in pre-configured ZDE +- WBW: Fix automatic CRT console switching under CP/M 3 and ZPM3 +- WBW: DSRTC driver now correctly returns an error if there is no RTC present + +Version 3.0.0 +------------- +- WBW: v2.9.2 -> 3.0 version upgrade due to scope of changes + +Version 2.9.2 +------------- +- PMS: Fixed DS1210-related issue resulting in "Invalid BIOS" errors +- SCC: Support for SC126 motherboard +- WBW: Enable Auto-CTS/DCD in SIO driver for pacing output data +- WBW: Support missing pull-up resistors on SPI SD adapter boards (common) +- WBW: Support two SIO modules w/ auto-detection +- PMS: Support ECB USB-FIFO board +- WBW: Fixed ASSIGN issue with incorrect DPB selection +- WBW: Add RC2014 Z180 AY sound support to TUNE app +- WBW: Add RC2014 AY sound support to AY driver +- WBW: Add SC126 platform +- WBW: Config files cleanup +- WBW: Add interrupt support to ASCI driver +- WBW: Refactored XModem overlay, merged USB-FIFO support +- PMS: Added DS1210 low battery detection/message +- PMS: Added note playing ability to SPK driver +- WBW: Support disk I/O to any memory bank +- WBW: Fix floppy I/O error on slow CPUs w/ ints active (credit Jorge Rodrigues) +- WBW: Support for KIO chip (based on board by Tom Szolyga) +- N?B: Made ZCAL Y2K compliant +- WBW: Show disk activity on diagnostic LEDs +- WBW: DSRTC now detects DS-1302 presence dynamically +- WBW: SC126 platform renamed to SCZ180 w/ configs for SC126, SC130 +- WBW: Add status LED support +- WBW: Add support for secondry SPI (SD Card) on SC126 +- PMS: Add sound support to NASCOM BASIC +- WBW: Updated FAT to add MD and FORMAT commands +- WBW: Add CP/M 3 (experimental) +- M?T: Support Shift register SPI WIZNET for RC2014 +- PLS: Added seconds register in HBIOS +- WBW: More flexible table-driven config in TUNE.COM +- PMS: Added timer support for Zilog Peripherals ECB Board +- PLS: Enhanced Intel Hex Load in dbgmon +- WBW: Overhaul disk image creation +- WBW: Added support for Dyno platform (based on work by Steve Garcia) +- SLG: Added support for BQ4845 RTC (bqrtc.asm) +- PMS: Added 80x30 video mode to VGA3 driver +- PMS: Added 80x43 video mode +- PMS: Added font compression option +- PMS: Added a "safe mode" startup w/ minimal device support +- WBW: Switch RC/SC Z180 platforms to 115,200 default baud rate +- PMS: Enhanced PPIDE driver to handle multiple PPI interfaces +- PMS: Added a ROM based game +- WBW: Only assign drive letters to hard disk devices with media +- WBW: Enhanced IDE driver to handle multiple IDE interfaces +- D?R: Contributed SC126 How-To: Preparing a MicroSD Card to Transfer Files to/from a Linux System +- PMS: Updated romldr to handle more than 9 drives +- PMS: Added "user" rom module template +- PMS: Added CP/M 3 manuals +- WBW: Boot from any slice +- C?M: Added Unix build process +- PLS: FreeRTOS compatibility +- WWB: Removed OSLDR application (can't handle new OSes) + Version 2.9.1 ------------- - E?B: Added support for RC2014 RTC - WBW: Converted PTXPLAY to TUNE (now plays PT2/PT3/MYM sounds files) - WBW: Updated Win32DiskImager to v1.0 - WBW: Implemented character attributes on Propeller based consoles -- M?S: Added support for BEL function in Propeller based consoles +- MS: Added support for BEL function in Propeller based consoles - WBW: Support additional escape sequences in Propeller console ANSI emulation - WBW: Map LPT: to second serial port, UL1: to third serial port - WBW: Update default IOBYTE so that LST:=LPT: by default @@ -22,8 +99,15 @@ Version 2.9.1 - PMS: Added Forth, Nascom BASIC, and Tasty BASIC to ROM - PMS: Refactored ROM Loader to support more ROM images, now table driven - WBW: Refactored DSKY code -- S?K: Initial support for Easy Z80 +- SK: Initial support for Easy Z80 - PMS: Enhance VDU driver to support alternative screen dimensions +- WBW: DDT and DDTZ modified to use RST 30 instead of RST 38 to avoid conflicts with IM 1 interrupts +- WBW: Added timer interrupt support for CTC under Zeta 2 and Easy Z80 +- WBW: Support LBA style access in floppy driver +- WBW: Added beta version of FAT filesystem utility (copy, dir, del, ren) +- SCC: Added support for native memory addressing on Z180-based RC2014 +- PMS: Dynamically discover and display processor type at boot +- J?L: Added German keyboard support to PPK and KBD drivers Version 2.9.0 ------------- diff --git a/Doc/Contrib/FdTst.txt b/Doc/Contrib/FdTst.txt deleted file mode 100644 index fefec401..00000000 --- a/Doc/Contrib/FdTst.txt +++ /dev/null @@ -1,429 +0,0 @@ -================================================================ -FDTST v3.1 for N8VEM DISKIO / DISKIO V3 / ZETA / DIDE / N8 -================================================================ - -Updated JuLY 1, 2012 -by Wayne Warthen (wwarthen@gmail.com) - -Application to test the hardware functionality of the Floppy Disk -Controller (FDC) on the ECB DISK I/O, DISK I/O V3, ZETA SBC, -Dual IDE w/ Floppy, or N8 board. - -The intent is to provide a testbed that allows direct testing -of all possible media types and modes of access. The application -supports read, write, and format by sector, track, and disk as -well as a random read/write test. - -The application supports access modes of polling, interrupt, -INT/WAIT, and DRQ/WAIT. At present, it supports 3.5" media at DD (720KB) and -HD (1.44MB) capacities. It also now supports 5.25" media (720KB and 1.2MB) -and 8" media (1.11MB) as well. Additonal media will be added when I have -time and access to required hardware. Not all modes are supported -on all platforms and some modes are experimental in all cases. - -In many ways this application is merely reinventing the wheel and -performs functionality similer to existing applications, but I have -not seen any other applications for DISK I/O that provide this range -of functionality. - -While the application is now almost entirely new code, I would like to -acknowledge that much was derived from the previous work of Andrew Lynch -and Dan Werner. I also want to credit Sergio Gimenez with testing the 5.25" -drive support and Jim Harre with testing the 8" drive support. Thanks! - -General Usage -------------- - -In general, usage is self explanatory. The main menu allows you to set -the unit, media, and mode to test. These settings MUST match your -situation. Read, write, format, and verify functions are provided. A sub-menu -will allow you to choose sector, track, disk, or random tests. - -The verify function requires a little explanation. It will take the contents -of the buffer, save it, and compare it to the selected sectors. So, you -must ensure that the sectors to be verified already have been written -with the same pattern as the buffer contains. I typically init the buffer -to a pattern, write the pattern to the entire disk, then verify the entire -disk. - -Another submenu is provided for FDC commands. This sub-menu allows you to -send low-level commands directly to FDC. You MUST know what you are doing -to use this sub-menu. For example, in order to read a sector using this -sub-menu, you will need to perform specify, seek, sense int, and read -commands specifying correct values (nothing is value checked in this menu). - -Required Hardware/BIOS ----------------------- - -Of course, the starting point is one of the support hardware platforms. -You need to start with either an N8VEM SBC, backplane, and ECB DISK I/O -card or a Zeta SBC. Additionally, a floppy drive connected via an -appropriate cable: - -DISKIO - no twist in cable, drive unit 0/1 must be selected by jumper on drive -ZETA - cable with twist, unit 0 after twist, unit 1 before twist -DIDE/N8 - cable with twist, unit 0 before twist, unit 1 after twist - - -It is preferable that the BIOS you use does not have DISK I/O support -enabled since the application assumes it has complete control of the -DISK I/O hardware. - -The DISK I/O should be jumpered as follows: - -J1: depends on use of interrupt modes (see interrupt modes below) -J2: pins 1-2, & 3-4 jumpered -J3: hardware dependent timing for DMA mode (see DMA modes below) -J4: pins 2-3 jumpered -J5: off -J6: pins 2-3 jumpered -J7: pins 2-3 jumpered -J8: off -J9: off -J10: off -J11: off -J12: off - -Note that J1 can be left on even when not using interrupt modes. As -long as the BIOS is OK with it, that is fine. Note also that J3 is -only relevant for DMA modes, but also can be left in place when -using other modes. - -The DISK I/O V3 should be jumpered at the default settings: - -JP2: 3-4 -JP3: 1-2 for int mode support, otherwise no jumper -JP4: 1-2, 3-4 -JP5: 1-2 -JP6: 1-2 -JP7: 1-2, 3-4 - -Zeta does not have any relevant jumper settings. - -DIDE should be jumpered as follows: - -K3 (DT/R or /RD): /RD -P5 (bd ID): 1-2, 3-4 (for $20-$3F port range) - -There are no specific N8 jumper settings, but the default -I/O range starting at $80 is assumed in the published code. - - -Modes of Operation ------------------- - -You can select the following test modes. Please refer to the chart -that follows to determine which modes should work with combinations -of Z80 CPU speed and media format. - -Polling: Traditional polled input/output. Works well and very reliable -including timeouts and good error recovery. Also, the slowest performance -which precludes it from being used with 1.44MB floppy on a 4MHz Z80. -This is definitely the mode you want to get working before any others. -It does not require J1 (interrupt enable) on DISK I/O and does not care about -the setting of J3. - -Interrupt: Input/output is interrupt driven. Works pretty well, but -is not able to recover from some errors. For example, if there is -no disk in the drive, this mode will just hang until a disk is inserted. -This mode REQUIRES that interrupts be enabled on the DISK I/O via -jumper at J1. On Zeta it requires the INT/NMI jumper be set for -INT. Mode not supported on DIDE or N8. Some BIOS variants will not -handle interrupts during boot. - -Fast Interrupt: As above, but sacrifices additional reliability for -faster operation. This mode will allow a 1.44MB floppy to work -with a 4MHz Z80 CPU. However, if any errors occur (even a transient -read error which is not unusual), this mode will hang. As above -you MUST have the appropriate jumpers for DISKIO and Zeta. DIDE -does not support this mode. - -INT/WAIT: Same as Fast Interrupt, but uses CPU wait instead of -actual interrupt. Subject to all the same issues as Fast -Interrupt, but does not need J1 shorted. J3 is irrelevant. -This mode is available on only on DISKIO (and not DISKIO V3). - -DRQ/WAIT: Uses pseudo DMA to handle input/output. Does not require that -interrupts (J1) be enabled on the DISK I/O. However, it is subject to -all of the same reliability issues as "Fast Interrupt". This -mode is known to not work on N8VEM DISKIO!!! It is included -for testing only. It is dependent on setting of J3. This -mode is NOT available on Zeta, DIDE, N8, or DISKIO V3. - -The chart below attempts to describe the combinations that -work for me. By far, the most reliable mode is Polling, -but it requires 8MHz CPU for HD disks. - -DRQ/WAIT --------------------------------+ -INT/WAIT -----------------------------+ | -Fast Interrupt --------------------+ | | -Interrupt ----------------------+ | | | -Polling ---------------------+ | | | | - | | | | | -CPU Speed --------------+ | | | | | - | | | | | | - | | | | | | - -3.5" DD (720K) ------ 4MHz Y Y Y Y X - 8MHz+ Y Y Y Y X - -3.5" HD (1.44M) ----- 4MHz N N Y Y X - 8MHz+ Y Y Y Y X - -5.25" DD (360K) ----- 4MHz Y Y Y Y X - 8MHz+ Y Y Y Y X - -5.25" HD (1.2M) ----- 4MHz N N Y Y X - 8MHz+ Y Y Y Y X - -8" DD (1.11M) ------- 4MHz N N Y Y X - 8MHz+ Y Y Y Y X - -Y = Yes, works -N = No, does not work -X = Experimental, probably won't work - -Tracing -------- - -Command/result activity to/from the FDC will be written out -if the trace setting is changed from '00' to '01' in setup. -Additionally, if a command failure is detected on any -command, that specific comand and results are written -regardless of the trace setting. - -The format of the line written is: -: --> [] - -For example, this is the output of a normal read operation: -READ: 46 01 00 00 01 02 09 1B FF --> 01 00 00 00 00 02 02 [OK] - -Please refer to the i8272 data sheet for information on the -command and result bytes. - -Note that the sense interrupt command can return a non-OK -result. This is completely normal in some cases. It is -necessary to "poll" the drive for seek status using -sense interrupt. If there is nothing to report, then -the result will be INVALID COMMAND. Additionally, -during a recalibrate operation, it may be necessary to -issue the command twice because the command will only step -the drive 77 times looking for track 0, but the head may be -up to 80 tracks away. In this case, the first recalibrate -fails, but the second should succeed. Here is what this -would look like if trace is turned on: - -RECALIBRATE: 07 01 --> [OK] -SENSE INTERRUPT: 08 --> 80 [INVALID COMMAND] - ... - ... - ... -SENSE INTERRUPT: 08 --> 80 [INVALID COMMAND] -SENSE INTERRUPT: 08 --> 71 00 [ABNORMAL TERMINATION] -RECALIBRATE: 07 01 --> [OK] -SENSE INTERRUPT: 08 --> 21 00 [OK] - -Another example is when the FDC has just been reset. In -this case, you will see up to 4 disk change errors. Again -these are not a real problem and to be expected. - -When tracing is turned off, the application tries to be -intelligent about error reporting. The specific errors -from sense interrupt documented above will be suppressed -because they are not a real problem. All other -errors will be displayed. - -Error Handling --------------- - -There is no automated error retry logic. This is very -intentional since the point is to expose the controller -and drive activity. Any error detected will result in -a prompt to abort, retry, or continue. Note that some -number of errors is considered normal for this -technology. An occasional error would not necessarily -be considered a problem. - -CPU Speed ---------- - -I distribute the binary version of the application optimized for -20MHz CPUs. There is a configuration variable called CPUFREQ -at the top of the source file. Ideally, you should build -with that set appropriately. However, I have found that the -default build setting of 20MHz seems to work for 4-20MHz CPUs. - -Interleave ----------- - -The format command now allows the specification of a sector -interleave. It is almost always the case that the optimal -interleave will be 2 (meaning 2:1). - -360K Media ----------- - -The 360K media definition should work well for true 360K -drives. However, it will generally not work -with 1.2M drives. This is because these drives spin at 360RPM -instead of the 300RPM speed of true 360K drives. Additionally, -1.2M drives are 80 tracks and 360K drives are 40 tracks and, so -far, there is no mechanism in FDTST to "double step" as a way -to use 40 track media in 80 track drives. - -With this said, it is possible to configure some 1.2M 5.25" drives -to automatically spin down to 300RPM based on a density select -signal (DENSEL). This signal is asserted by FDTST for 360K -media, so IF you have configured your drive to react to this -signal correctly, you will be able to use the 360K media defintion. -Most 1.2M 5.25" drives are NOT configured this way by default. -TEAC drives are generally easy to modify and have been tested by -the author and do work in this manner. Note that this does not -address the issue of double stepping above; you will just be -using the first 40 of 80 tracks. - -Support -------- - -I am happy to answer questions as fast and well as I am able. -Best contact is wwarthen@gmail.com or post something on the -N8VEM Google Group https://groups.google.com/forum/#!forum/n8vem. - -Changes -------- - -WW 8/12/2011 - -Removed call to pulse TC in the FDC initialization -after determining that it periodically caused the FDC to write -bad sectors. I am mystified by this, but definitely found it -to be true. Will revisit at some point -- probably a timing -issue between puslsing TC and whatever happens next. - -Non-DMA mode was being set incorrectly for FAST-DMA mode. -It was set for non-DMA even though we were doing DMA. It is -interesting that it worked fine anyway. Fixed it anyway. - -DIO_SETMEDIA was not clearing DCD_DSKRDY as it should. Fixed. - -WW 8/26/2011: v1.1 - -Added support for Zeta. Note that INT/WAIT and DRQ/WAIT are -not available on Zeta. Note that Zeta provides the ability -to perform a reset of the FDC independent of a full CPU -reset. This is VERY useful and the FDC is reset anytime -a drive reset is required. - -Added INT/WAIT support. - -WW 8/28/2011: V1.2 - -All changes in this version are Zeta specific. Fixed FDC reset -logic and motor status display for Zeta (code from Sergey). - -Modified Zeta disk change display to include it in the -command output line. This makes more sense because a command -must be issued to select the desired drive first. You can -use the SENSE INT command id you want to check the disk -change value at any time. It will also be displayed with -any other command output display. - -WW 9/1/2011: V1.3 - -Added CPUFREQ configuration setting to tune -delays based on cpu speed. The build app -is set for 8MHz which also seems to work well -for 4MHz CPU's. Faster CPU speeds will -probably require tuning this setting. - -WW 9/5/2011: V1.4 - -Changed the polling execution routines to utilize CPUFREQ -variable to optimize timeout counter. Most importantly, -this should allow the use of faster CPUs (like 20MHz). - -WW 9/19/2011: V1.5 - -Zeta changes only. Added a call to FDC RESET after any -command failure. This solves an issue where the drive -remains selected if a command error occurs. Also -added FDC RESET to FDC CONTROL menu. - -WW 10/7/2011: V2.0 - -Added support for DIDE. Only supports polling IO and it -does not appear any other modes are possible given the -hardware constraints. - -WW 10/13/2011: V2.1 - -Modified to support N8. N8 is essentially identical to -Dual IDE. The only real change is the IO addresses. In -theory, I should be able to support true DMA on N8 and -will work on that. - -WW 10/20/2011: v2.2 - -I had some problems with the results being read were -sometimes missing a byte. Fixed this by taking a more -strict approach to watching the MSR for the exact -bits that are expected. - -WW 10/22/2011: V2.3 - -After spending a few days trying to track down an -intermittent data corruption issue with my Dual IDE -board, I added a verify function. This helped -me isolate the problem very nicely (turned out to -be interference from the bus monitor). - -WW 11/25/2011: V2.4 - -Preliminary support for DISKIO V3. Basically just -assumed that it operates just like the Zeta. Needs -to be verified with real hardware as soon as I can. - -WW 1/9/2012: V2.5 - -Modified program termination to use CP/M reset -call so that a warm start is done and all -drives are logged out. This is important -because media may have been formatted during -the program execution. - -WW 2/6/2012: v2.6 - -Added support for 5.25" drives as tested by -Sergio. - -WW 4/5/2012: v2.7 - -Added support for 8" drives as tested by -Jim Harre. - -WW 4/6/2012: v2.7a - -Fixed issue with media selection menu to remove -duplicate entries. - -WW 4/8/2012: v2.7b - -Corrected the handling of the density select -signal. - -WW 5/22/2012: v2.8 - -Added new media definitions (5.25", 320K) - -WW 6/1/2012: v2.9 - -Added interleave capability on format - -WW 6/5/2012: v3.0 - -Documentation cleanup - -WW 7/1/2012: v3.1 - -Modified head load time (HLT) for 8" media based on -YD-180 spec. Now set to 50ms. \ No newline at end of file diff --git a/Doc/Contrib/LinuxBuild.txt b/Doc/Contrib/LinuxBuild.txt deleted file mode 100644 index 5b480040..00000000 --- a/Doc/Contrib/LinuxBuild.txt +++ /dev/null @@ -1,45 +0,0 @@ -Assembling the RomWBW firmware under Linux. - -Note: Updated on 6/25/2013 to eliminate the need for the separate Linux -makefile. The standard makefile now has conditionals to allow it to be -used under Windows or Linux (I hope) --WW - -This method has been used under Ubuntu Linux and may have to be adapted for -other distributions. It is a bit more involved than the Windows procedure. - -What you need -You will need the TASM assembler, make, dos2unix and cpmtools. - -The TASM assembler is shareware and the Linux version is only available as -source code from the Author. I found one bug during compiling version 3.2 for -Ubuntu. In /src/tasm.c change the reference CLK_TIC to CLOCKS_PER_SEC. -After compiling install the tasm executable to /usr/local/bin and the table -files to /usr/local/lib. If you choose to place them somewhere else you will -have to edit the "makefile.linux" file to suit. - -The make, dos2unix and cpmtools packages are found in the Linux repository and -installed as for any other package. - -Before assembly -Some changes need to be made to cater for the differences between Linux and the -DOS/Windows environments. The examples below refer to the /RomWBW/current -directory, you'll have to allow for the stable or branches directories if used. -These are all done from a terminal. (: is end of the command prompt) - -1. Go to the RomWBW Source directory.e.g. -:cd /n8vem/RomWBW/current/Source - -2. The Linux version of TASM can't handle the CR-LF line endings. So from the -command prompt use dos2unix to convert all the source files. -:~/RomWBW/current/Source dos2unix -f *.asm *.inc *.z80 *.lib diskdefs - -3. You'll have to alter the disk definitions for the cpmtools package to cater -for the new roms. Easiest way is to copy the one given in the source over the -old. This must be done as superuser. -:~/RomWBW/current/Source sudo cp diskdefs /etc/cpmtools/diskdefs - -4. From now on it's the same as using the DOS/Windows instructions in Build.txt. -Make any last changes, go to the Source directory and make -:~/RomWBW/current/Source make clean ; make - -DGG diff --git a/Doc/Contrib/Microsoft NASCOM BASIC.docx b/Doc/Contrib/Microsoft NASCOM BASIC.docx new file mode 100644 index 00000000..68af9472 Binary files /dev/null and b/Doc/Contrib/Microsoft NASCOM BASIC.docx differ diff --git a/Doc/Contrib/SC126_How-To_No_1_Serial_Comms_Using_Minicom_v.1.1.pdf b/Doc/Contrib/SC126_How-To_No_1_Serial_Comms_Using_Minicom_v.1.1.pdf new file mode 100644 index 00000000..8a215aa1 Binary files /dev/null and b/Doc/Contrib/SC126_How-To_No_1_Serial_Comms_Using_Minicom_v.1.1.pdf differ diff --git a/Doc/Contrib/SC126_How-To_No_2_Preparing_an_SD_Card_for_Use_with_SC126_v1.5.3.pdf b/Doc/Contrib/SC126_How-To_No_2_Preparing_an_SD_Card_for_Use_with_SC126_v1.5.3.pdf new file mode 100644 index 00000000..94f75b63 Binary files /dev/null and b/Doc/Contrib/SC126_How-To_No_2_Preparing_an_SD_Card_for_Use_with_SC126_v1.5.3.pdf differ diff --git a/Doc/Contrib/SC126_How-To_No_3_Updating_RomWBW_Using_a_Hybrid_SD_Card_v1.1.pdf b/Doc/Contrib/SC126_How-To_No_3_Updating_RomWBW_Using_a_Hybrid_SD_Card_v1.1.pdf new file mode 100644 index 00000000..0a7d2ab1 Binary files /dev/null and b/Doc/Contrib/SC126_How-To_No_3_Updating_RomWBW_Using_a_Hybrid_SD_Card_v1.1.pdf differ diff --git a/Doc/Contrib/Z180 Clocking.txt b/Doc/Contrib/Z180 Clocking.txt deleted file mode 100644 index 846c73b0..00000000 --- a/Doc/Contrib/Z180 Clocking.txt +++ /dev/null @@ -1,19 +0,0 @@ -The table below can be used to determine the correct value for CLKDIV AND CNTLB -in an Z180 (N8) configuration file. OSC Freq refers to the hardware clock -oscillator frequency you are using. You can then choose a CLKDIV value which -will result in the CPU speed (frequency) shown below the oscillator frequency. - -Using your oscillator frequency (OSC) and chosen value for CLKDIV, you can -use the appropriate column to derive values to use for CNTLB for different -baud rates. - - ----- CLKDIV = 0 ----- ----- CLKDIV = 1 ----- -OSC Freq (MHz) 6.144 12.288 18.432 6.144 12.288 18.432 -CPU Freq (MHz) 3.072 6.144 9.216 6.144 12.288 18.432 - -1200 baud 04H 05H 24H 05H 06H 25H -2400 baud 03H 04H 23H 04H 05H 24H -4800 baud 02H 03H 22H 03H 04H 23H -9600 baud 01H 02H 21H 02H 03H 22H -19200 baud 00H 01H 20H 01H 02H 21H -38400 baud --- 00H --- 00H 01H 20H \ No newline at end of file diff --git a/Doc/Contrib/ZSystem.txt b/Doc/Contrib/ZSystem.txt index c3315fad..0dda8d31 100644 --- a/Doc/Contrib/ZSystem.txt +++ b/Doc/Contrib/ZSystem.txt @@ -32,6 +32,8 @@ The remainder of this document details the changes I made as I went along. In a 6. Updated FILEATTR to v1.6A. Original FILEATTR.CFG was invalid. FILEATTR.CFG replaced with FA16.CFG. Added associated files FA16.DOC, FA16A.FOR, FA16CFG.TXT. +7. ZCAL patched to be Y2K compliant. Contributed by Nick Brok. + Usage Notes ----------- diff --git a/Doc/DDTZ.doc b/Doc/DDTZ.doc new file mode 100644 index 00000000..46da2d3b --- /dev/null +++ b/Doc/DDTZ.doc @@ -0,0 +1,564 @@ + + DDTZ v2.7 + by C.B. Falconer + edited by George A. Havach + +Introduction: +============ +DDTZ v2.7 is a complete replacement for DDT, Digital Research's +famous Dynamic Debugging Tool, with improved functionality, bug +extermination, and full Z80 support. In general, DDTZ is fully +compatible with the original utility, but it has extra and +extended commands and many fewer quirks. All Z80-specific +instructions can be (dis)assembled, though in Intel rather then +Zilog format. Furthermore, DDTZ will correctly trace ('T' and 'U' +commands) both 8080 and Z80 instructions, depending on which CPU +is operating. On startup, the program announces which CPU it is +running on. + +DDTZ v2.7 now handles the 64180 added opcodes. It does NOT test +for a 64180 CPU, since this cannot be done without executing +illegal Z80 instructions, which in turn will crash some +simulators. However v2.7 does not execute any 64180 instructions +internally, only in the subject program. + +This issue supplies the "M" version assembled, to avoid errors +when switching between MSDOS and CPM systems. The command table +is updated accordingly. Most CPM users are also MSDOS users, but +not vice-versa. + +The program is invoked by typing + + ddtz +or + ddtz [d:]filespec + +In the second form, DDTZ will load the specified file into +memory starting at 0100H, unless it's a .HEX file that sets its +own load address. Besides reporting the NEXT free address and +the PC (program counter) after a successful load, DDTZ also shows +the number of memory pages needed for a SAVE. Instead of having +to write all this down, just use the 'X' command at any time to +redisplay these three values for the current application. + +NOTE: loading more code above the NEXT pointer revises these + values. + +As in DDT, when a program is loaded above the area holding the +'A' and 'U' (and now 'W') command code, these commands are +disabled, and the extra memory is released to the user. Thus, +DDTZ can occupy as little as 3K total memory space. Unlike DDT, +however, DDTZ will not overwrite itself or the system on program +loads (except .HEX files). + +At initialization, the stack pointer (SP) points to a return to +DDTZ, just like for the CCP. Thus, programs that normally return +to the CCP will be returned to DDTZ. The 'B' command +reinitializes this condition. + + +The intercept vector copies the BDOS version number, etc., so +an object program does not know that DDTZ is running (except +for BIOS-BDOS vector size). Thus, programs that check the version +number should execute correctly under DDTZ. + +All input parameters can now be entered in any of three formats: + + (1) hexadecimal (as in DDT), + (2) decimal, by adding a leading '#' character, + (3) ASCII, by enclosing between either single or double + quotes; either one or two characters are allowed. + +Leading blanks in command lines and parameters are absorbed. +Either a comma or a (single) space is a valid delimiter. +Either uppercase or lowercase input is accepted. + +The default command (for anything not otherwise recognizable) +is 'H'. This allows convenient calculation, along with the other +features described below. So, to convert a number, just enter +it! + +As in DDT, the prompt character is '-', and the only error +message is the query ('?'), which generally kicks you back to +command mode. + +New Commands (Over DDT): +======================= + +NOTE: letters in parenthesis, e.g. "(U)", show the equivalent + command for DDTZM version (compatible with MSDOS debug). + + @ Sets or shows (with no parameter) the internally stored + "base" value. Also used with the 'S' and 'D' commands as + an optional parameter (though without the '@') to display + memory from an arbitrary base marker (offset). When set to + zero (the default), it does not affect any screen displays. + + B B)egin: resets the USER stack pointer to its initial value, + such that any program that exits by an RET will return to + DDTZ. DDTZ provides a default stack space of + approximately 24 bytes for user programs. + + C C)ompare first_address,last_address,against_address: shows + all the byte differences between two memory areas, in the + format + + XXXX aa YYYY bb + + where XXXX and YYYY are the comparative memory addresses, + and aa and bb are the corresponding byte values. Can be + used to verify the identity of two files by first + loading them into different memory areas with the 'R' + command (see below). + + + W Write: stores the modified memory area to disk under the + (K) filename specified by the 'I' command, overwriting the + original file from which it was loaded (the user is queried + before doing so). By default, the image of memory from + 0100H through the "NEXT" value -1 is saved. "K first_addr, + last_address" overrides this and allows writing ANY memory + area to a file. Almost a necessity for CPM 3.0 (no SAVE!). + K)eep on DDTZ + + X eXamine: redisplays the "NEXT PC SAVE" report at any time. + (Q) Q)uery size on DDTZ. + + S S)earch first_address, last_addr, value: searches the + (W) specified memory area for the value (a 16-bit word, not a + byte) and shows the locations of all such. Very useful for + finding CALL's or JMP's to a particular address, etc. + W)here on DDTZ + + Y Y)our_option parm1,parm2,address: executes an arbitrary + routine at the specified address, with the BC and DE + registers set to parm1 and parm2, respectively. + + Z Displays (but does not alter) the Z80's alternate register + set, including the index registers (disabled if running on + an 8080). On Z80's, automatically included as the last + part of the display by the 'X' command. + + +Based (Offset) Displays: +======================= + +The 'D' and 'E' commands can use a stored base value (offset), +as set by the '@' command. The current @ value may be +overridden for a single execution of these commands by adding the +base as an extra parameter in the command line. The effect is +to add this value to the first/last address and display +accordingly. The address listing on the left becomes XXXX:YYYY, +where XXXX is the offset address and YYYY is the actual memory +address being displayed. For example, if you have a data area +located at 42B7H and wish to preserve easy access, just enter +"@42b7". Now, "d0,3f" will dump memory starting at 4237H. + + +Further Changes from DDT: +======================== + + A A)ssemble now accepts the full Z80 as well as 8080 + instruction set, although it expects them in Intel rather + than Zilog format (see notes below under the 'L' + command). When in doubt, see the mnemnonic list below. + + D D)isplay or D)ump will accept an optional third parameter + to set the base value for a single execution only. Format + has been cleaned up. + + H H)ex_arithmetic on two values also shows their + difference in decimal. With only one value, converts to + hexadecimal, decimal, and ASCII (low-order byte only). + + + N N)ame now allows drive specification (d:...) and sets up + (I) the complete command line, including both FCB's (at + addresses 005CH and 006CH). The tail (stored at 0081H up) + is NOT upshifted. + I)nput on DDTZ + + U U)nassemble now displays the raw hexcode, especially handy + (L) when examining non-code areas. Intel (8080 style) mnemonics + are used, so some disassembled instructions may look + strange. E.g., the Z80's 'IN B,(C)' and 'OUT (C),B' become + 'INP B' and 'OUTP B', respectively; 'LD (nnnn),BC' becomes + 'SBCD nnnn', 'ADD IX, BC' becomes 'DADX B', and 'JP (IX)' + becomes 'PCIX'. + L)ist on DDTZ + + L L)oad now permits loading a file into memory with an + (R) offset, which is added to the default load address of + 0100H. When reading in a .HEX file with a preset bias, + the 'R' command will not transfer control to an invalid + execution point. Another execution of the 'R' command will + reread the input file, e.g.: + + n blah + l + ...modify the code and generally mess about... + l + + The original file is reloaded, and the modifications are + removed. + R)ead on DDTZ + + E E)nter, like D)isplay, now accepts an optional second + (S) parameter to set the base value for a single execution + only. + S)ubstitute or S)et on DDTZ + + T T)rap/trace on termination now shows the complete CPU + state. Traps and traces no longer lock up when a user RST + 7 instruction is executed. Tracing of BDOS/BIOS calls is + heavily trun cated, avoiding clutter and preventing system + crashes. + +NOTE: Most of the UNDOCUMENTED Z80 op-codes are handled. Others + can crash the system. + + R R)egisters also shows what two-byte values the HL and SP + (X) registers are actually pointing to. On Z80's, displays the + alternate register set. + eX)amine on DDTZ + +NOTE: Any use of the 'W' or 'L' command resets the system DMA + transfer address to the standard default value of 0080H. + + +; This is the output of DDTZ when disassembling OPTYPE.TRY +NOP LDA 06A4 MOV M,H +LXI B,06A4 DCX SP MOV M,L +STAX B INR A HLT +INX B DCR A MOV M,A +INR B MVI A,20 MOV A,B +DCR B CMC MOV A,C +MVI B,20 MOV B,B MOV A,D +RLC MOV B,C MOV A,E +EXAF MOV B,D MOV A,H +DAD B MOV B,E MOV A,L +LDAX B MOV B,H MOV A,M +DCX B MOV B,L MOV A,A +INR C MOV B,M ADD B +DCR C MOV B,A ADD C +MVI C,20 MOV C,B ADD D +RRC MOV C,C ADD E +DJNZ 0134 MOV C,D ADD H +LXI D,06A4 MOV C,E ADD L +STAX D MOV C,H ADD M +INX D MOV C,L ADD A +INR D MOV C,M ADC B +DCR D MOV C,A ADC C +MVI D,20 MOV D,B ADC D +RAL MOV D,C ADC E +JR 0134 MOV D,D ADC H +DAD D MOV D,E ADC L +LDAX D MOV D,H ADC M +DCX D MOV D,L ADC A +INR E MOV D,M SUB B +DCR E MOV D,A SUB C +MVI E,20 MOV E,B SUB D +RAR MOV E,C SUB E +JRNZ 0134 MOV E,D SUB H +LXI H,06A4 MOV E,E SUB L +SHLD 06A4 MOV E,H SUB M +INX H MOV E,L SUB A +INR H MOV E,M SBB B +DCR H MOV E,A SBB C +MVI H,20 MOV H,B SBB D +DAA MOV H,C SBB E +JRZ 0134 MOV H,D SBB H +DAD H MOV H,E SBB L +LHLD 06A4 MOV H,H SBB M +DCX H MOV H,L SBB A +INR L MOV H,M ANA B +DCR L MOV H,A ANA C +MVI L,20 MOV L,B ANA D +CMA MOV L,C ANA E +JRNC 0134 MOV L,D ANA H +LXI SP,06A4 MOV L,E ANA L +STA 06A4 MOV L,H ANA M +INX SP MOV L,L ANA A +INR M MOV L,M XRA B +DCR M MOV L,A XRA C +MVI M,20 MOV M,B XRA D +STC MOV M,C XRA E +JRC 0134 MOV M,D XRA H +DAD SP MOV M,E XRA L + + +XRA M JPE 06A4 SLAR M +XRA A XCHG SLAR A +ORA B CPE 06A4 SRAR B +ORA C XRI 20 SRAR C +ORA D RST 5 SRAR D +ORA E RP SRAR E +ORA H POP PSW SRAR H +ORA L JP 06A4 SRAR L +ORA M DI SRAR M +ORA A CP 06A4 SRAR A +CMP B PUSH PSW SLLR B +CMP C ORI 20 SLLR C +CMP D RST 6 SLLR D +CMP E RM SLLR E +CMP H SPHL SLLR H +CMP L JM 06A4 SLLR L +CMP M EI SLLR M +CMP A CM 06A4 SLLR A +RNZ CPI 20 SRLR B +POP B RST 7 SRLR C +JNZ 06A4 RLCR B SRLR D +JMP 06A4 RLCR C SRLR E +CNZ 06A4 RLCR D SRLR H +PUSH B RLCR E SRLR L +ADI 20 RLCR H SRLR M +RST 0 RLCR L SRLR A +RZ RLCR M BIT 0,B +RET RLCR A BIT 0,C +JZ 06A4 RRCR B BIT 0,D +CZ 06A4 RRCR C BIT 0,E +CALL 06A4 RRCR D BIT 0,H +ACI 20 RRCR E BIT 0,L +RST 1 RRCR H BIT 0,M +RNC RRCR L BIT 0,A +POP D RRCR M BIT 1,B +JNC 06A4 RRCR A BIT 1,C +OUT 20 RALR B BIT 1,D +CNC 06A4 RALR C BIT 1,E +PUSH D RALR D BIT 1,H +SUI 20 RALR E BIT 1,L +RST 2 RALR H BIT 1,M +RC RALR L BIT 1,A +EXX RALR M BIT 2,B +JC 06A4 RALR A BIT 2,C +IN 20 RARR B BIT 2,D +CC 06A4 RARR C BIT 2,E +SBI 20 RARR D BIT 2,H +RST 3 RARR E BIT 2,L +RPO RARR H BIT 2,M +POP H RARR L BIT 2,A +JPO 06A4 RARR M BIT 3,B +XTHL RARR A BIT 3,C +CPO 06A4 SLAR B BIT 3,D +PUSH H SLAR C BIT 3,E +ANI 20 SLAR D BIT 3,H +RST 4 SLAR E BIT 3,L +RPE SLAR H BIT 3,M +PCHL SLAR L BIT 3,A + + +BIT 4,B RES 3,D SET 2,H +BIT 4,C RES 3,E SET 2,L +BIT 4,D RES 3,H SET 2,M +BIT 4,E RES 3,L SET 2,A +BIT 4,H RES 3,M SET 3,B +BIT 4,L RES 3,A SET 3,C +BIT 4,M RES 4,B SET 3,D +BIT 4,A RES 4,C SET 3,E +BIT 5,B RES 4,D SET 3,H +BIT 5,C RES 4,E SET 3,L +BIT 5,D RES 4,H SET 3,M +BIT 5,E RES 4,L SET 3,A +BIT 5,H RES 4,M SET 4,B +BIT 5,L RES 4,A SET 4,C +BIT 5,M RES 5,B SET 4,D +BIT 5,A RES 5,C SET 4,E +BIT 6,B RES 5,D SET 4,H +BIT 6,C RES 5,E SET 4,L +BIT 6,D RES 5,H SET 4,M +BIT 6,E RES 5,L SET 4,A +BIT 6,H RES 5,M SET 5,B +BIT 6,L RES 5,A SET 5,C +BIT 6,M RES 6,B SET 5,D +BIT 6,A RES 6,C SET 5,E +BIT 7,B RES 6,D SET 5,H +BIT 7,C RES 6,E SET 5,L +BIT 7,D RES 6,H SET 5,M +BIT 7,E RES 6,L SET 5,A +BIT 7,H RES 6,M SET 6,B +BIT 7,L RES 6,A SET 6,C +BIT 7,M RES 7,B SET 6,D +BIT 7,A RES 7,C SET 6,E +RES 0,B RES 7,D SET 6,H +RES 0,C RES 7,E SET 6,L +RES 0,D RES 7,H SET 6,M +RES 0,E RES 7,L SET 6,A +RES 0,H RES 7,M SET 7,B +RES 0,L RES 7,A SET 7,C +RES 0,M SET 0,B SET 7,D +RES 0,A SET 0,C SET 7,E +RES 1,B SET 0,D SET 7,H +RES 1,C SET 0,E SET 7,L +RES 1,D SET 0,H SET 7,M +RES 1,E SET 0,L SET 7,A +RES 1,H SET 0,M DADX B +RES 1,L SET 0,A DADX D +RES 1,M SET 1,B LXI X,06A4 +RES 1,A SET 1,C SIXD 06A4 +RES 2,B SET 1,D INX X +RES 2,C SET 1,E DADX X +RES 2,D SET 1,H LIXD 06A4 +RES 2,E SET 1,L DCX X +RES 2,H SET 1,M INR [X+05] +RES 2,L SET 1,A DCR [X+05] +RES 2,M SET 2,B MVI [X+05],20 +RES 2,A SET 2,C DADX SP +RES 3,B SET 2,D MOV B,[X+05] +RES 3,C SET 2,E MOV C,[X+05] + + +MOV D,[X+05] DSBC B DADY B +MOV E,[X+05] SBCD 06A4 DADY D +MOV H,[X+05] NEG LXI Y,06A4 +MOV L,[X+05] RETN SIYD 06A4 +MOV [X+05],B IM0 INX Y +MOV [X+05],C LDIA DADY Y +MOV [X+05],D INP C LIYD 06A4 +MOV [X+05],E OUTP C DCX Y +MOV [X+05],H DADC B INR [Y+05] +MOV [X+05],L LBCD 06A4 DCR [Y+05] +MOV [X+05],A RETI MVI [Y+05],2 +MOV A,[X+05] LDRA DADY SP +ADD [X+05] INP D MOV B,[Y+05] +ADC [X+05] OUTP D MOV C,[Y+05] +SUB [X+05] DSBC D MOV D,[Y+05] +SBB [X+05] SDED 06A4 MOV E,[Y+05] +ANA [X+05] IM1 MOV H,[Y+05] +XRA [X+05] LDAI MOV L,[Y+05] +ORA [X+05] INP E MOV [Y+05],B +CMP [X+05] OUTP E MOV [Y+05],C +POP X DADC D MOV [Y+05],D +XTIX LDED 06A4 MOV [Y+05],E +PUSH X IM2 MOV [Y+05],H +PCIX LDAR MOV [Y+05],L +SPIX INP H MOV [Y+05],A +RLCR [X+05] OUTP H MOV A,[Y+05] +RRCR [X+05] DSBC H ADD [Y+05] +RALR [X+05] shld 06A4 ADC [Y+05] +RARR [X+05] RRD SUB [Y+05] +SLAR [X+05] INP L SBB [Y+05] +SRAR [X+05] OUTP L ANA [Y+05] +SRLR [X+05] DADC H XRA [Y+05] +BIT 0,[X+05] lhld 06A4 ORA [Y+05] +BIT 1,[X+05] RLD CMP [Y+05] +BIT 2,[X+05] INP M POP Y +BIT 3,[X+05] OUTP M XTIY +BIT 4,[X+05] DSBC SP PUSH Y +BIT 5,[X+05] SSPD 06A4 PCIY +BIT 6,[X+05] INP A SPIY +BIT 7,[X+05] OUTP A RLCR [Y+05] +RES 0,[X+05] DADC SP RRCR [Y+05] +RES 1,[X+05] LSPD 06A4 RALR [Y+05] +RES 2,[X+05] LDI RARR [Y+05] +RES 3,[X+05] CCI SLAR [Y+05] +RES 4,[X+05] INI SRAR [Y+05] +RES 5,[X+05] OTI SRLR [Y+05] +RES 6,[X+05] LDD BIT 0,[Y+05] +RES 7,[X+05] CCD BIT 1,[Y+05] +SET 0,[X+05] IND BIT 2,[Y+05] +SET 1,[X+05] OTD BIT 3,[Y+05] +SET 2,[X+05] LDIR BIT 4,[Y+05] +SET 3,[X+05] CCIR BIT 5,[Y+05] +SET 4,[X+05] INIR BIT 6,[Y+05] +SET 5,[X+05] OTIR BIT 7,[Y+05] +SET 6,[X+05] LDDR RES 0,[Y+05] +SET 7,[X+05] CCDR RES 1,[Y+05] +INP B INDR RES 2,[Y+05] +OUTP B OTDR RES 3,[Y+05] + + +RES 4,[Y+05] SET 0,[Y+05] SET 4,[Y+05] +RES 5,[Y+05] SET 1,[Y+05] SET 5,[Y+05] +RES 6,[Y+05] SET 2,[Y+05] SET 6,[Y+05] +RES 7,[Y+05] SET 3,[Y+05] SET 7,[Y+05] + +; These are the result of disassembling 64180OPS.TRY +; These opcodes are available ONLY on the 64180 CPU +; DDTZ will both assemble and disassemble these. +IN0 B,20 TST E MLT B +OUT0 20,B IN0 H,20 MLT D +TST B OUT0 20,H TSTI 20 +IN0 C,20 TST H MLT H +OUT0 20,C IN0 L,20 TSIO 20 +TST C OUT0 20,L SLP +IN0 D,20 TST L MLT SP +OUT0 20,D TST M OTIM +TST D IN0 A,20 OTDM +IN0 E,20 OUT0 20,A OIMR +OUT0 20,E TST A ODMR + +; The following are UNDOCUMENTED z80 opcodes from XTDOPS.TRY. +; DDTZ will disassemble these, but will not assemble them. +; They use xh/xl (or yh/yl) as separate byte registers. +; Use these at your own risk. +INRX H ACXR H MOVY H,B +DCRX H ACXR L MOVY H,C +MVIX H,20 SUXR H MOVY H,D +INRX L SUXR L MOVY H,E +DCRX L SBXR H MOVY H,A +MVIX L,20 SBXR L MOVY L,B +MOVX B,H NDXR H MOVY L,C +MOVX B,L NDXR L MOVY L,D +MOVX C,H XRXR H MOVY L,E +MOVX C,L XRXR L MOVY L,A +MOVX D,H ORXR H MOVY A,H +MOVX D,L ORXR L MOVY A,L +MOVX E,H CPXR H ADYR H +MOVX E,L CPXR L ADYR L +MOVX H,B INRY H ACYR H +MOVX H,C DCRY H ACYR L +MOVX H,D MVIY H,20 SUYR H +MOVX H,E INRY L SUYR L +MOVX H,A DCRY L SBYR H +MOVX L,B MVIY L,20 SBYR L +MOVX L,C MOVY B,H NDYR H +MOVX L,D MOVY B,L NDYR L +MOVX L,E MOVY C,H XRYR H +MOVX L,A MOVY C,L XRYR L +MOVX A,H MOVY D,H ORYR H +MOVX A,L MOVY D,L ORYR L +ADXR H MOVY E,H CPYR H +ADXR L MOVY E,L CPYR L + + +Command Summary: +=============== + +DDTZM command DDTZ command +============= ============ +@ (base) +A)ssemble first_address A +B)egin {i.e., initialize stack and return} B +C)ompare first_address,last_address,against_address C +D)ump first_address[,last_address[,base]] D +E)nter_in_memory first_address[,base] S)ubstitute +F)ill first_address,last_address,value F +G)o_to [address][,trap1[,trap2]] G +H)ex_arithmetic value1(,value2) H +L)oad_file (offset) R)ead +M)ove first_address,last_address,destination M +N)nput FCBs_command_line I)nput +Q)uit (not avail) +R)egister examine/change [register|flag] X)amine +S)earch first_address,last_address,word W)hereis +T)race_execution [count] T + Untrace_execution [count] (i.e. do count instr) U)ntrace +U)nassemble_code first_address[,last_address] L)ist code +W)rite [first_address,last_address] K)eep +X)amine {i.e. display memory parameters for application} Q)uery +Y)our_option BC:=parm1,DE:=parm2,call_address Y +Z)80_register_display Z + + +If you find this program useful, contributions will be gratefully +accepted and will encourage further development and release of +useful CPM programs. My practice is to include source. + +C.B. Falconer +680 Hartford Turnpike, +Hamden, Conn. 06517 (203) 281-1438 + +DDTZ and its associated documentation and other files are +copyright (c) 1980-1988 by C.B. Falconer. They may be freely +copied and used for non-commercial purposes ONLY. +ôÙ \ No newline at end of file diff --git a/Doc/FDU.txt b/Doc/FDU.txt index b591b7c3..d67fe1fe 100644 --- a/Doc/FDU.txt +++ b/Doc/FDU.txt @@ -1,9 +1,9 @@ ================================================================ Floppy Disk Utility (FDU) v5.3 for RetroBrew Computers -Disk IO / Zeta / Dual-IDE / N8 / RC2014 / SmallZ80 +Disk IO / Zeta / Dual-IDE / N8 / RC2014 / SmallZ80 / Dyno ================================================================ -Updated September 5, 2018 +Updated January 5, 2020 by Wayne Warthen (wwarthen@gmail.com) Application to test the hardware functionality of the Floppy @@ -77,6 +77,7 @@ supported: - RC2014 w/ SMC - RC2014 w/ WDC - SmallZ80 + - Dyno You must be using either a RomWBW or UBA based OS version. @@ -95,7 +96,7 @@ Finally, you will need a floppy drive connected via an appropriate cable: Disk IO - no twist in cable, drive unit 0/1 must be selected by jumper on drive -DISK IO 3, Zeta, Zeta 2, RC2014 - cable with twist, unit 0 after twist, unit 1 before twist +DISK IO 3, Zeta, Zeta 2, RC2014, Dyno - cable with twist, unit 0 after twist, unit 1 before twist DIDE, N8, Mark IV, SmallZ80 - cable with twist, unit 0 before twist, unit 1 after twist Note that FDU does not utilize your systems ROM or OS to @@ -154,6 +155,9 @@ JP2 (TC): 2-3. SmallZ80 does not have any relevant jumper settings. The hardwired I/O ranges are assumed in the code. +Dyno does not have any relevant jumper settings. The +hardwired I/O ranges are assumed in the code. + Modes of Operation ------------------ @@ -505,3 +509,6 @@ WW 9/5/2018: v5.3 condition is no longer considered an error, but a successful end of operation. - Added support for SmallZ80 + +WW 5/1/2020: v5.4 + - Added support for Dyno (based on work by Steve Garcia) diff --git a/Doc/Hard Disk Anatomy.pdf b/Doc/Hard Disk Anatomy.pdf new file mode 100644 index 00000000..54bf0ad5 Binary files /dev/null and b/Doc/Hard Disk Anatomy.pdf differ diff --git a/Doc/NZCOM Users Manual.pdf b/Doc/NZCOM Users Manual.pdf new file mode 100644 index 00000000..59a62a08 Binary files /dev/null and b/Doc/NZCOM Users Manual.pdf differ diff --git a/Doc/ReadMe.txt b/Doc/ReadMe.txt index 3172dd2c..276003d6 100644 --- a/Doc/ReadMe.txt +++ b/Doc/ReadMe.txt @@ -10,31 +10,84 @@ This directory ("Doc") is part of the RomWBW System Software distribution archive. It contains documentation for components of the system. -CPM Manual: + +CPM Manual ("CPM Manual.pdf") +----------------------------- The original DRI CP/M 2.x Operating System Manual. This should be considered the primary reference for system operation. The section on CP/M 2 Alteration can be ignored since this work has already been completed as part of the RomWBW distribution. -FDisk Manual: + +CPM3 Command Summary ("CPM3 Command Summary.pdf") +CPM3 Programmer's Guide ("CPM3 Programmers Guide.pdf") +CPM3 System Guide ("CPM3 System Guide.pdf") +CPM3 User's Guide ("CPM3 Users Guide.pdf") +------------------------------------------------------ + +The original DRI CP/M 3.0 Operating System Documentation Set. This +should be considered the primary reference for CP/M 3 system operation. + + +DDTZ Manual ("DDTZ.doc") +------------------------ + +Manual for the DDTZ v2.7 debug tool included on the ROM drive. + + +FDisk Manual ("FDisk Manual.pdf") +--------------------------------- The operational manual for John Coffman's hard disk partitioning program. This program is included in RomWBW as FDISK80. -RomWBW Architecture: + +Floppy Disk Utility Documentation ("FDU.tst") +--------------------------------------------- + +Operational documentation for the RomWBW FDU application. + + +Hard Disk Anatomy ("Hard Disk Anatomy.pdf") +------------------------------------------- + +Diagram of a CP/M & MS-DOS (FAT) hybrid hard disk layout. + + +NZCOM User's Manual ("NZCOM Users Manual.pdf") +---------------------------------------------- + +NZCOM operating system operation manual. + + +RomWBW Architecture ("RomWBW Architecture.pdf") +----------------------------------------------- Document describing the architecture of the RomWBW HBIOS. It includes reference information for the HBIOS calls. -ZCPR Manual: + +Z180 ASCI Baud Rate Options ("Z180 ASCI Baud Rate Options.pdf") +--------------------------------------------------------------- + +The Z180 processor's ASCI serial ports have a limited set of +baud rate divisors. These divisors are relative to the CPU +clock rate. This document provides a list of the possible +baud rates for typical CPU clock rates. + + +ZCPR Manual ("ZCPR Manual.pdf") +------------------------------- ZCPR is the command proccessor portion of Z-System. This is the manual for ZCPR 1.x as included in RomWBW. The installation instructions can be ignored since that work has already been completed as part of the RomWBW distribution. -ZSDOS Manual: + +ZSDOS Manual ("ZSDOS Manual.pdf") +--------------------------------- ZSDOS is the DOS portion of Z-System. This is the manual fo ZSDOS 1.x as included in RomWBW. The installation instructions can be diff --git a/Doc/RomWBW Applications.pdf b/Doc/RomWBW Applications.pdf new file mode 100644 index 00000000..ed1cd38b Binary files /dev/null and b/Doc/RomWBW Applications.pdf differ diff --git a/Doc/RomWBW Architecture.pdf b/Doc/RomWBW Architecture.pdf index dae486de..10bbc123 100644 Binary files a/Doc/RomWBW Architecture.pdf and b/Doc/RomWBW Architecture.pdf differ diff --git a/Doc/RomWBW Getting Started.pdf b/Doc/RomWBW Getting Started.pdf new file mode 100644 index 00000000..dabf18a4 Binary files /dev/null and b/Doc/RomWBW Getting Started.pdf differ diff --git a/Doc/Z180 ASCI Baud Rate Options.pdf b/Doc/Z180 ASCI Baud Rate Options.pdf new file mode 100644 index 00000000..0786fcbe Binary files /dev/null and b/Doc/Z180 ASCI Baud Rate Options.pdf differ diff --git a/Doc/ZCPR Manual.pdf b/Doc/ZCPR Manual.pdf index ce5f80b8..50668527 100644 Binary files a/Doc/ZCPR Manual.pdf and b/Doc/ZCPR Manual.pdf differ diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..44d7847e --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +all: + cd Tools/unix ; make + cd Source ; make + cd Source/Images ; make + +clean: + cd Tools/unix ; make clean + cd Source ; make clean + cd Binary ; make clean + +clobber: + cd Tools/unix ; make clobber + cd Source ; make clobber + cd Binary ; make clobber + rm -f typescript + +diff: + cd Source ; make diff + diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 00000000..8c4346c4 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,1164 @@ +# RomWBW + +## Z80/Z180 System Software + +Version 3.1 Pre-release +Saturday 11 April 2020 + +Wayne Warthen + +### Download + + - [RomWBW Distribution + Package](https://github.com/wwarthen/RomWBW/releases) + +### Related Pages + + - [RomWBW Architecture + Document](https://www.retrobrewcomputers.org/lib/exe/fetch.php?media=software:firmwareos:romwbw:romwbw_architecture.pdf) + - [RomWBW + Applications](https://www.retrobrewcomputers.org/doku.php?id=software:firmwareos:romwbw:apps) + - [RomWBW + Errata](https://www.retrobrewcomputers.org/doku.php?id=software:firmwareos:romwbw:errata) + +# Overview + +RomWBW provides a complete software system for a wide variety of +hobbyist Z80/Z180 CPU-based systems produced by these developer +communities: + + - [RetroBrew Computers](https://www.retrobrewcomputers.org) + - [RC2014](https://rc2014.co.uk) + - [retro-comp](https://groups.google.com/forum/#!forum/retro-comp) + +General features include: + + - Banked memory services for several banking designs + - Disk drivers for RAM, ROM, Floppy, IDE, CF, and SD + - Serial drivers including UART (16550-like), ASCI, ACIA, SIO + - Video drivers including TMS9918, SY6545, MOS8563, HD6445 + - Real time clock drivers including DS1322, BQ4845 + - Multiple OS support including CP/M 2.2, ZSDOS, CP/M 3, ZPM3 + - Built-in VT-100 terminal emulation support + +RomWBW is distributed as both source code and pre-built ROM and disk +images. Some of the provided software can be launched directly from the +ROM firmware itself: + + - System Monitor + - Operating Systems (CP/M 2.2, ZSDOS) + - ROM BASIC (Nascom BASIC and Tasty BASIC) + - ROM Forth + +A dynamic disk drive letter assignment mechanism allows mapping +operating system drive letters to any available disk media. +Additionally, mass media devices (IDE Disk, CF Card, SD Card) support +the use of multiple slices (up to 256 per device). Each slice contains a +complete CP/M filesystem and can be mapped independently to any drive +letter. This overcomes the inherent size limitations in legacy OSes and +allows up to 2GB of accessible storage on a single device. + +The pre-built ROM firmware images are generally optimal for most users. +However, it is also very easy to modify and build custom ROM images that +fully tailor the firmware to your specific preferences. All tools +required to build custom ROM firmware are included – no need to install +assemblers, etc. Any modern computer running Windows, Linux, or MacOS +can be used. + +Multiple disk images are provided in the distribution. Most disk images +contain a complete, bootable, ready-to-run implementation of a specific +operating system. A “combo†disk image contains multiple slices, each +with a full operating system implementation. If you use this disk image, +you can easily pick whichever operating system you want to boot without +changing media. + +# Installation + +The latest RomWBW distribution downloads are maintained on GitHub in the +[RomWBW Repository](https://github.com/wwarthen/RomWBW). The fully-built +distributions are found on the [releases +page](https://github.com/wwarthen/RomWBW/releases) of the repository. On +this page, you will probably see both pre-releases as well as normal +releases. Unless you have a specific reason, I suggest you stick to the +most recent normal release (not pre-release). Expand the “Assets†+drop-down for the release you want to download, then select the asset +named RomWBW-vX.X.X-Package.zip. The Package asset includes all +pre-built ROM and Disk images as well as full source code. The other +assets are Source Code only and do not have the pre-built ROM or disk +images. + +The pre-built ROM images will automatically detect and support a +reasonable range of devices including serial ports, video adapters, +on-board disk interfaces, and PropIO/ParPortProp boards without building +a custom ROM. The distribution is a .zip archive. After downloading it +to a working directory on your modern computer (Windows/Linux/Mac) use +any zip tool to extract the contents of the archive. + +In general, you will just program your system’s ROM chip with the +appropriate ROM image from the RomWBW distribution. Depending on how you +got your system, you may have already been provided with a +pre-programmed ROM chip. If so, use that initially. Otherwise, you will +need to use a ROM programmer to initially program your ROM chip. Please +refer to the documentation that came with your ROM programmer for more +information. Once you have a running RomWBW system, you can generally +update your ROM to a newer version in-situ with an included ROM Flashing +tool (Will Sowerbutts’ FLASH application) as described in the Upgrading +section below. + +Looking at the extracted distribution archive, You will see that the +distribution is broken up into a few sub-directories. The Binary +directory contains the pre-built ROM and disk images. The ROM image +files all end in “.româ€. Based on the table below, **carefully** pick +the appropriate ROM image for your hardware. + +| Platform | ROM Image File | Baud | Description | +| --------- | --------------- | -----: | ----------------------------------------------- | +| SBC | SBC\_std.rom | 38400 | RetroBrew SBC v1 or v2 ECB Z80 | +| Zeta V1 | ZETA\_std.rom | 38400 | RetroBrew Zeta V1 Z80, ParPortProp (optional) | +| Zeta V2 | ZETA2\_std.rom | 38400 | RetroBrew Zeta V2 Z80, ParPortProp (optional) | +| N8 | N8\_std.rom | 38400 | RetroBrew N8 Z180, date code \>= 2312 | +| Mark IV | MK4\_std.rom | 38400 | RetroBrew Mark IV ECB Z180 | +| RC Z80 | RCZ80\_std.rom | 115200 | RC2014 w/ Z80 CPU, requires 512K RAM/ROM module | +| RC Z180\* | RCZ180\_ext.rom | 115200 | RC2014 w/ Z180 CPU & 512K banked RAM/ROM module | +| RC Z180\* | RCZ180\_nat.rom | 115200 | RC2014 w/ Z180 CPU & 512K native RAM/ROM module | +| Easy Z80 | EZZ80\_std.rom | 115200 | Sergey Kiselev’s Easy Z80 | +| SC126 | SCZ180\_126.rom | 115200 | Stephen Cousin’s SC126 Z180 | +| SC130 | SCZ180\_130.rom | 115200 | Stephen Cousin’s SC130 Z180 | +| SC131 | SCZ180\_131.rom | 115200 | Stephen Cousin’s SC131 Z180 | +| Dyno | DYNO\_std.rom | 38400 | Steve Garcia’s Z180 Dyno Computer | + +\*The RC2014 Z180 requires a separate RAM/ROM memory module. There are +two types of these modules and you must pick the ROM for your type of +memory module. The “ext†ROM supports Spencer’s official 512K RAM/ROM +banked memory module. The “nat†ROM supports any of the third-party Z180 +native memory modules. + +RomWBW will automatically attempt to detect and support typical add-on +components for each of the systems supported. More information on the +required system configuration and optional supported components for each +ROM is found in the file called “RomList.txt†in the Binary directory. +All pre-built ROM images are simple 512KB binary images. If your system +utilizes a larger ROM chip, you can just program the image into the +first 512KB of the ROM. + +Connect a serial terminal or computer with terminal emulation software +to the primary serial port of your CPU board. You may need to refer to +your hardware provider’s documentation for details. A null-modem +connection may be required. Set the baud rate as indicated in the table +above. Set the line characteristics to 8 data bits, 1 stop bit, no +parity, and no flow control. If possible, select VT-100 terminal +emulation. + +Upon power-up, your terminal should display a sign-on banner within 2 +seconds followed by hardware inventory and discovery information. When +hardware initialization is completed, a boot loader prompt allows you to +choose a ROM-based operating system, system monitor, application, or +boot from a disk device. + +Initially, you should try the ROM boot options. By selecting either CP/M +2.2 or Z-System, the selected operating system will be loaded from ROM +and you will see the a `B>` disk prompt. In this scenario, A: will be an +empty RAM disk and B: will refer to your ROM disk containing some common +applications. This provides a simple environment for learning to use +your system. Be aware that files saved to the RAM disk (A:) will +disappear at the next power on (RAM is generally not persistent). Also +note that attempts to save files to the ROM disk (B:) will fail because +ROM is not writable. + +# General Usage + +Each of the operating systems and ROM applications included with RomWBW +are sophisticated tools in their own right. It is not reasonable to +document their usage here. However, you will find complete manuals in +PDF format in the Doc directory of the distribution. The intention of +this section is to document the RomWBW specific enhancements to these +operating systems. + +## Inbuilt ROM Applications + +In addition to CP/M 2.2 and Z-System, there are several ROM applications +that can be launched directly from ROM. These applications are not +hosted by an operating system and so they are unable to save files to +disk devices. + +The following ROM applications are available at the boot loader prompt: + +| Application | | +| ----------- | ------------------------------------------------------ | +| Monitor | Z80 system debug monitor w/ Intel Hex loader | +| Forth | Brad Rodriguez’s ANSI compatible Forth language | +| Basic | Nascom 8K BASIC language | +| Tasty BASIC | Dimitri Theuling’s Tiny BASIC implementation | +| Play | A simple video game (requires ANSI terminal emulation) | + +In general, the command to exit these applications and restart the +system is `BYE`. The exceptions are the Monitor which uses `B` and Play +which uses `Q`. + +Space is available in the ROM image for the inclusion of other software. +Any inbuilt application can be set up to launch automatically at +startup. + +## Devices and Units + +In order to support a wide variety of hardware, RomWBW HBIOS uses a +modular approach to implementing device drivers and presenting devices +to the operating system. In general, all devices are classified as one +of the following: + + - Disk (Hard Disk, CF Card, SD Card, RAM/ROM Disk, etc.) + - Character (Serial Ports, Parallel Ports, etc.) + - Video (Video Display/Keyboard Interfaces) + - RTC/NVRAM (Real Time Clock, Non-volatile RAM) + +HBIOS uses the concept of unit numbers to present a complex set of +hardware devices to the operating system. As an example, a typical +system might have a ROM Disk, RAM Disk, Floppy Drives, and Disk Drives. +All of these are considered Disk devices and are presented to the +operating system as generic block devices. This means that the operating +system does not need to understand the difference between a floppy drive +and a ROM disk. + +As RomWBW boots, it assigns a unit number to each device. This unit +number is used by the operating system to refer to the device. It is, +therefore, important to know the unit number assigned to each device. +This information is displayed in the unit summary table at startup. Here +is an example: + + Unit Device Type Capacity/Mode + ---------- ---------- ---------------- -------------------- + Char 0 UART0: RS-232 38400,8,N,1 + Char 1 UART1: RS-232 38400,8,N,1 + Disk 0 MD1: RAM Disk 384KB,LBA + Disk 1 MD0: ROM Disk 384KB,LBA + Disk 2 FD0: Floppy Disk 3.5",DS/HD,CHS + Disk 3 FD1: Floppy Disk 3.5",DS/HD,CHS + Disk 4 IDE0: CompactFlash 3815MB,LBA + Disk 5 IDE1: Hard Disk -- + Disk 6 PRPSD0: SD Card 1886MB,LBA + Video 0 CVDU0: CRT Text,80x25 + +In this example, you can see that the system has a total of 7 Disk Units +numbered 0-6. There are also 2 Character Units and 1 Video Unit. The +table shows the unit numbers assigned to each of the devices. Notice how +the unit numbers are assigned sequentially regardless of the specific +device. + +There may or may not be media in the disk devices listed. For example, +the floppy disk devices (Disk Units 2 & 3) may not have a floppy in the +drive. Also note that Disk Unit 4 shows a disk capacity, but Disk Unit 5 +does not. This is because the PPIDE interface of the system supports up +to two drives, but there is only one actual drive attached. A unit +number is assigned to all possible devices regardless of whether they +have actual media installed at boot time. + +Note that Character Unit 0 is **always** the initial system console by +definition. + +If your system has an RTC/NVRAM device, it will not be listed in the +unit summary table. Since only a single RTC/NVRAM device can exist in +one system, unit numbers are not required nor used for this type of +device. + +## Drive Letter Assignment + +In legacy CP/M-type operating systems, drive letters were generally +mapped to disk drives in a completely fixed way. For example, drive A: +would **always** refer to the first floppy drive. Since RomWBW supports +a wide variety of hardware configurations, it implements a much more +flexible drive letter assignment mechanism so that any drive letter can +be assigned to any disk device. + +At boot, you will notice that RomWBW automatically assigns drive letters +to the available disk devices. These assignments are displayed during +the startup of the selected operating system. Additionally, you can +review the current drive assignments at any time using the `ASSIGN` +command. CP/M 3 and ZPM3 do not automatically display the assignments at +startup, but you can use `ASSIGN` do display them. + +The drive letter assignments **do not** change during an OS session +unless you use the `ASSIGN` command yourself to do it. Additionally, the +assignments at boot will stay the same on each boot as long as you do +not make changes to your hardware configuration. Note that the +assignments **are** dependent on the media currently inserted in hard +disk drives. So, notice that if you insert or remove an SD Card or CF +Card, the drive assignments will change. Since drive letter assignments +can change, you must be careful when doing destructive things like using +`CLRDIR` to make sure the drive letter you use is referring to the +desired media. + +When performing a ROM boot of an operating system, note that A: will be +your RAM disk and B: will be your ROM disk. When performing a disk boot, +the disk you are booting from will be assigned to A: and the rest of the +drive letters will be offset to accommodate this. This is done because +most legacy operating systems expect that A: will be the boot drive. + +## Slices + +The vintage operating systems included with RomWBW were produced at a +time when mass storage devices were quite small. CP/M 2.2 could only +handle filesystems up to 8MB. In order to achieve compatibility across +all of the operating systems supported by RomWBW, the hard disk +filesystem format used is 8MB. This ensures any filesystem will be +accessible to any of the operating systems. + +Since storage devices today are quite large, RomWBW implements a +mechanism called slicing to allow up to 256 8MB filesystems on a single +large storage device. This allows up to 2GB of usable space on a single +media. You can think of slices as a way to refer to any of the first 256 +8MB chunks of space on a single media. + +Of course, the problem is that CP/M-like operating systems have only 16 +drive letters (A:-P:) available. Under the covers, RomWBW allows you to +use any drive letter to refer to any slice of any media. The `ASSIGN` +command is allows you to view or change the drive letter mappings at any +time. At startup, the operating system will automatically allocate a +reasonable number of drive letters to the available storage devices. The +allocation will depend on the number of large storage devices available +at boot. For example, if you have only one hard disk type media, you +will see that 8 drive letters are assigned to the first 8 slices of that +media. If you have two large storage devices, you will see that each +device is allocated four drive letters. + +Referring to slices within a storage device is done by appending a : +** where ** is the device relative slice number from 0-255. For +example, if you have an IDE device, it will show up as IDE0: in the boot +messages meaning the first IDE device. To refer to the fourth slice of +IDE0, you would type “IDE0:3â€. Here are some examples: + +| | | +| -------- | ---------------------------- | +| `IDE0:0` | First slice of disk in IDE0 | +| `IDE0:` | First slice of disk in IDE0 | +| `IDE0:3` | Fourth slice of disk in IDE0 | + +So, if I wanted to use drive letter L: to refer to the fourth slice of +IDE0, I could use the command `ASSIGN L:=IDE0:3`. There are a couple of +rules to be aware of when assigning drive letters. First, you may only +refer to a specific device/slice with one drive letter. Said another +way, you cannot have multiple drive letters referring to a single +device/slice at the same time. Second, there must always be a drive +assigned to A:. Any attempt to violate these rules will be blocked by +the `ASSIGN` command. + +Unlike MS-DOS partitions, slices are not allocated – there is no +partitioning for slices. Think of every hard disk type device as having +a pre-allocated set of 256 8MB slices at the start of the media. You can +refer to any of them simply by assigning a drive letter. RomWBW will not +check to see if there is anything else on the hard disk in the slice you +are referring to, nor will it verify that the hard disk media is large +enough to have a slice at the location you refer to. If you attempt to +write past the end of your media, you will get an I/O error displayed, +so you will know if you make a mistake. There is no tracking of your use +of slices – you will need to keep track of your use of slices yourself. + +Nothing automatically initializes a slice as a file system. You must do +that yourself using `CLRDIR`. Since `CLRDIR` works on drive letters, +make absolutely sure you know what media and slice are assigned to that +drive letter before using `CLRDIR`. + +While it is probably obvious, you cannot use slices on any media less +than 8MB in size. Specifically, you cannot slice RAM disks, ROM disks, +floppy disks, etc. + +# RomWBW Custom Applications + +The operation of the RomWBW hosted operating systems is enhanced through +several custom applications. These applications are functional on all of +the OS variants included with RomWBW. + +The following custom applications are found on the ROM disk and are, +therefore, globally available. + +| Application | Description | +| ----------- | ---------------------------------------------------------------------------------------------------- | +| ASSIGN | Add, change, and delete drive letter assignments. Use ASSIGN /? for usage instructions. | +| SYSCOPY | Copy system image to a device to make it bootable. Use SYSCOPY with no parms for usage instructions. | +| MODE | Reconfigures serial ports dynamically. | +| FDU | Format and test floppy disks. Menu driven interface. | +| FORMAT | Will someday be a command line tool to format floppy disks. Currently does nothing\! | +| XM | XModem file transfer program adapted to hardware. Automatically uses primary serial port on system. | +| FLASH | Will Sowerbutts’ in-situ ROM programming utility. | +| FDISK80 | John Coffman’s Z80 hard disk partitioning tool. See documentation in Doc directory. | +| TALK | Direct console I/O to a specified character device. | +| RTC | Manage and test the Real Time Clock hardware. | +| TIMER | Display value of running periodic system timer. | +| INTTEST | Test interrupt vector hooking. | + +Some custom applications do not fit on the ROM disk. They are found on +the disk image files or the individual files can be found in the +Binary\\Apps directory of the distribution. + +| Application | Description | +| ----------- | ----------------------------------------------------------- | +| TUNE | Play .PT2, .PT3, .MYM audio files. | +| FAT | Access MS-DOS FAT filesystems from RomWBW (based on FatFs). | + +Additional documentation on all of these applications can be found in +“RomWBW Applications.pdf†in the Doc directory of the distribution. + +# Using Disks + +## ROM & RAM Disks + +RomWBW utilizes a portion of the ROM and RAM memory in your system to +implement small memory-based disks. + +The RAM disk provides a small CP/M filesystem that you can use for the +temporary storage of files. Unless your system has a battery backed +mechanism for persisting your RAM contents, the RAM disk contents will +be lost at each power-off. However, the RAM disk is an excellent choice +for storing temporary files because it is very fast. + +Like the RAM disk, the ROM disk also provides a small CP/M filesystem, +but it’s contents are static – they are part of the ROM. As such, you +cannot save files to the ROM disk. Any attempt to do this will result in +a disk I/O error. The contents of the ROM disk have been chosen to +provide a core set of tools and applications that are helpful for either +CP/M 2.2 or ZSDOS. Since ZSDOS is CP/M 2.2 compatible, this works fairly +well. However, you will find some files on the ROM disk that will work +with ZSDOS, but will not work on CP/M 2.2. For example, `LDDS`, which +loads the ZSDOS date/time stamper will only run on ZSDOS. + +## Disk Devices + +While the RAM/ROM disks provide a functional system, they are not useful +in the long term because you cannot save data across power cycles. They +are also constrained by limited space. + +The systems supported by RomWBW all have the ability to use persistent +disk media. A wide variety of disk devices are supported including +floppy drives, hard disks, CF Cards, and SD Cards. Some systems have +disk interfaces built-in, while others will require add-in cards. You +will need to refer to the documentation for your system for your +specific options. + +In the RomWBW boot messages, you will see hardware discovery messages. +If you have a disk drive interface, you should see messages listing +device types like FD:, IDE:, PPIDE:, SD:. Additionally, you will see +messages indicating the media that has been found on the interfaces. As +an example, here are the messages you might see if you have an IDE +interface in your system with a single CF Card inserted in the primary +side of the interface: + + IDE: IO=0x80 MODE=MK4 + IDE0: 8-BIT LBA BLOCKS=0x00773800 SIZE=3815MB + IDE1: NO MEDIA + +The messages you see will vary depending on your hardware and the media +you have installed. But, they will all have the same general format as +the example above. + +Once your your system has working disk devices, you can boot an +operating system and the operating system will have access to the media. +At the boot loader prompt, select either either CP/M 2.2 or Z-System to +boot from ROM. As the operating system starts up, you should see a list +of drive letters assigned to the disk media you have installed. Here is +an example of this: + + Configuring Drives... + + A:=MD1:0 + B:=MD0:0 + C:=IDE0:0 + D:=IDE0:1 + +You will probably see more drive letters than this. The drive letter +assignment process is described above in the Drive Letter Assignment +section. Be aware that RomWBW will only assign drive letters to disk +interfaces that actually have media in them. If you do not see drive +letters assigned as expected, refer to the prior system boot messages to +ensure media has been detected in the interface. Actually, there is one +exception to this rule: floppy drives will be assigned a drive letter +regardless of whether there is any media inserted at boot. + +Notice how each drive letter refers back to a specific disk hardware +interface like IDE0. This is important as it is telling you what each +drive letter refers to. Also notice that mass storage disks (like IDE) +will normally have multiple drive letters assigned. The extra drive +letters refer to additional “slices†on the disk. The concept of slices +is described above in the Slices section. + +Once you are seeing drive letters referring to your disk media, you can +follow the instructions below to begin using the disk media with the +operating system. Your disk media **must** be initialized prior to being +used. There are two ways to initialize your media for use. + +One option is to initialize the media in-place using your RomWBW system. +This process is described below under Disk Initialization. In this +scenario, you will need to subsequently copy any files you want to use +onto the newly initialized disk (see Transferring Files). + +Alternatively, you can use your modern Windows, Linux, or Mac computer +to copy a disk image onto the disk media. RomWBW comes with a variety of +disk images that are ready to use and have a much more complete set of +files than you will find on the ROM disk. This process is covered below +under Disk Images. + +## Disk Initialization + +To use a disk device, you will need to initialize the directory of the +filesystem. On RomWBW, the initialization is done using the CLRDIR +application. For example if your C: drive has been assigned to a storage +device, you would use `CLRDIR C:` to initialize C: and prepare it hold +files. Note that CLRDIR will prompt you for confirmation and you must +respond with a **capital** ‘Y’ to confirm. Once `CLDIR` has completed, +you can copy files onto the drive, for example `COPY *.* C:`. Be very +careful to pay attention to your drive letter assignments prior to +running `CLRDIR` to avoid accidentally wiping out a filesystem that has +data on it. + +Running `CLRDIR` on a disk device is roughly equivalent to running +FORMAT on MS-DOS. Note that unlike MS-DOS you do **not** partition your +mass storage device. CP/M knows nothing about disk partitions. You may +notice a partitioning application on your ROM disk (FDISK80), but this +is strictly for an advanced technique of adding an MS-DOS FAT filesystem +to your media in addition to the CP/M area. Do not use FDISK80 unless +you are specifically attempting to add an MS-DOS FAT filesystem to your +media. + +If you are using a floppy drive, you will need to physically format your +floppy disk prior to use. This is only required for floppy disks, not +hard disk, CF Cards, or SD Cards, etc. To format a floppy drive, you can +use the interactive application `FDU`. FDU is not terribly user +friendly, but is generally documented in the file “FDU.txt†found in the +Doc directory of the distribution. It is not necessary to run `CLRDIR` +on a floppy disk after physically formatting it – the directory is +cleared as part of the formatting. + +Once you have initialized a disk device and copied your desired files +onto it, you may want to make the disk bootable. On CP/M filesystems, +you must perform one additional step to make a disk bootable. +Specifically, you need to place a copy of the operating system on the +system tracks of the disk. This is done using the `SYSCOPY` command. +Let’s say you have prepared drive C: by initializing it with `CLRDIR` +and copied some files onto it. You can now make C: bootable by running +the following command: + +`B>SYSCOPY C:=B:ZSYS.SYS` + +This command means: copy the Z-System operating system onto the system +tracks of drive C:. In this example, it is assumed that you have booted +from ROM, so B: is the ROM disk drive. Additionally, this example +assumes you want the Z-System operating system to be booted from C:. If +you want CP/M 2.2 instead, you would replace `B:ZSYS.SYS` with +`B:CPM.SYS`. Here is a full example of this process. + + B>SYSCOPY C:=B:ZSYS.SYS + + SYSCOPY v2.0 for RomWBW CP/M, 17-Feb-2020 (CP/M 2 Mode) + Copyright 2020, Wayne Warthen, GNU GPL v3 + + Transfer system image from B:ZSYS.SYS to C: (Y/N)? Y + Reading image... Writing image... Done + +Once this process succeeds, you will be able to boot directly to the +disk from the boot loader prompt. See the instructions in Booting Disks +for details on this. + +## Disk Images + +As mentioned previously, RomWBW includes a variety of disk images that +contain a full set of applications for the operating systems supported. +It is generally easier to use these disk images instead of copying all +the files over using XModem. You use your modern computer (Windows, +Linux, MacOS) to place the disk image onto the disk media, then just +move the media over to your system. In this scenario you **do not** run +`CLRDIR` or `SYSCOPY` on the drive(s). The directory is prepared and the +disk is already bootable, if it is an operating system boot disk image. + +To copy the disk image files onto your actual media (floppy disk, CF +Card, SD Card, etc.), you need to use an image writing utility on your +modern computer. Your modern computer will need to have an appropriate +interface or slot that accepts the media. To actually copy the image, +you can use the `dd` command on Linux or MacOS. On Windows, in the +“Tools†directory of the distribution there are two tools you can use. +For floppy media, you can use RawWriteWin and for hard disk media, you +can use Win32DiskImager. In all cases, the image file should be written +to the media starting at the very first block or sector of the media. +This will **destroy** any other data on the media. + +The disk image files are found in the Binary directory of the +distribution. Floppy disk images are prefixed with “fd\_†and hard disk +images are prefixed with “hd\_â€. The floppy images are specifically for +1.44M floppy media only. Each disk image has the complete set of normal +applications and tools distributed with the associated operating system +or application suite. + +The following table shows the disk image files available. Note that the +images in the “Hard†column are fine for use on CF Cards, SD Cards, as +well as real spinning hard disks. + +| Floppy | Hard | Description | +| ------------- | ------------- | ---------------------------- | +| fd\_cpm22.img | hd\_cpm22.img | DRI CP/M 2.2 boot disk | +| fd\_zsdos.img | hd\_zsdos.img | ZSDOS 1.1 boot disk | +| fd\_nzcom.img | hd\_nzcom.img | NZCOM boot disk | +| fd\_cpm3 | hd\_cpm3.img | DRI CP/M 3 boot disk | +| fd\_zpm3 | hd\_zpm3.img | ZPM3 boot disk | +| fd\_ws4 | hd\_ws4.img | WordStar v4 application disk | + +In addition to the disk images above, there is also a special hard disk +image called hd\_combo.img. This image contains all of the images above, +but in a single image with 6 slices. At the boot loader prompt, you can +choose a disk with the combo image, then select the specific slice you +want. This allows a single disk to have all of the possible operating +system options. + +This is the layout of the hd\_combo disk image: + +| Slice | Description | +| ------- | ---------------------------- | +| Slice 0 | DRI CP/M 2.2 boot disk | +| Slice 1 | ZSDOS 1.1 boot disk | +| Slice 2 | NZCOM boot disk | +| Slice 3 | DRI CP/M 3 boot disk | +| Slice 4 | ZPM3 boot disk | +| Slice 5 | WordStar v4 application disk | + +Note that unlike the ROM firmware, you do **not** need to choose a disk +image specific to your hardware. Because the RomWBW firmware provides a +hardware abstraction layer, all hard disk images will work on all +hardware variations. Yes, this means you can remove an SD Card from one +system and put it in a different system. The only constraint is that the +applications on the disk media must be up to date with the firmware on +the system being used. + +All of the disk images that indicate they are bootable (boot disk) will +boot from disk as is. You do not need to run `SYSCOPY` on them to make +them bootable. However, if you upgrade your ROM, you should use +`SYSCOPY` to update the system tracks. + +## Booting Disks + +When starting your system, following the hardware initialization, you +will see the Boot Loader prompt. In addition, to the ROM boot options, +you will see another line listing the Disk boot options. This line lists +the disk devices that you can choose to boot directly. + +You will notice that you do not have an option to boot a drive letter +here (like C:). This is because the operating system is not yet loaded. +When you ran `SYSCOPY` previously, remember that C: was assigned to +IDE0:0 which means device IDE0, slice 0. So, to boot the disk that you +just setup with `SYSCOPY`, you would choose option 2. You will then be +prompted for the slice on IDE0 that you want to boot. For now, just +press enter to choose slice 0. Once you are familiar with slices, you +can `SYSCOPY` and boot alternate slices. Here is what you would see when +booting to a disk device: + + MARK IV Boot Loader + + ROM: (M)onitor (C)P/M (Z)-System (F)orth (B)ASIC (T)-BASIC (P)LAY (U)SER ROM + Disk: (0)MD1 (1)MD0 (2)IDE0 (3)IDE1 + + Boot Selection? 2 Slice(0-9)[0]? + + Booting Disk Unit 2, Slice 0... + + Reading disk information... + Loc=D000 End=FE00 Ent=E600 Label=Unlabeled Drive + + Loading... + +Following this, you would see the normal operating system startup +messages. However, your operating system prompt will be `A>` and when +you look at the drive letter assignments, you should see that A: has +been assigned to the disk you selected to boot. + +If you receive the error message “Disk not bootable\!â€, you have either +failed to properly run `SYSCOPY` on the target disk or you have selected +the wrong disk/slice. + +Note that although MD1 (RAM disk) and MD0 (ROM disk) drives are listed +in the Disk boot line, they are not “bootable†disks because they have +no system tracks on them. Attempting to boot to one of them, will fail +with a “Disk not bootable\!†error message and return to the loader +prompt. + +# Operating Systems + +One of the primary goals of RomWBW is to expose a set of generic +hardware functions that make it easy to adapt operating systems to any +hardware supported by RomWBW. As a result, there are now 5 operating +systems that have been adapted to run under RomWBW. The adaptations are +identical for all hardware supported by RomWBW because RomWBW hides all +hardware specifics from the operating system. + +Note that all of the operating systems included with RomWBW support the +same basic filesystem format. As a result, a formatted filesystem will +be accessible to any operating system. The only possible issue is that +if you turn on date/time stamping using the newer OSes, the older OSes +will not understand this. Files will not be corrupted, but the date/time +stamps may be lost. + +The following sections briefly describe the operating system options +currently available. + +## Digital Research CP/M 2.2 + +This is the most widely used variant of the Digital Research operating +system. It has the most basic feature set, but is essentially the +compatibility metric for all other CP/M-like operating systems including +all of those listed below. The Doc directory contains a manual for CP/M +usage (“CPM Manual.pdfâ€). If you are new to the CP/M world, I would +recommend using this CP/M variant to start with simply because it is the +most stable and you are less likely to encounter problems. + +### Notes + + - The original versions of DDT, DDTZ, and ZSID used the RST 38 vector + which conflicts with interrupt mode 1 use of this vector. The DDT, + DDTZ, and ZSID applications in RomWBW have been modified to use RTS + 30 to avoid this issue. + + - Z-System applications will not run under CP/M 2.2. For example, the + `LDDS` date stamper with not run. + +## ZSDOS 1.1 + +ZSDOS is the most popular non-DRI CP/M “clone†which is generally +referred to as Z-System. Z-System is intended to be an enhanced version +of CP/M and should run all CP/M 2.2 applications. It is optimized for +the Z80 CPU (as opposed to 8080 for CP/M) and has some significant +improvements such as date/time stamping of files. For further +information on the RomWBW implementation of Z-System, see the wiki page +[Z-System +Notes](https://www.retrobrewcomputers.org/doku.php?id=software:firmwareos:romwbw:zsystem). +Additionally, the official documentation for Z-System is included in the +RomWBW distribution Doc directory (“ZSDOS Manual.pdf†and “ZCPR +Manual.pdfâ€). + +### Notes + + - Although most CP/M 2.2 applications will run under Z-System, some + may not work as expected. The best example is PIP which is not aware + of the ZSDOS paths and will fail in some scenarios (use `COPY` + instead). + +## NZCOM Automatic Z-System + +NZCOM is a much further refined version of Z-System (ZCPR 3.4). NZCOM +was sold as an enhancement for existing users of CP/M 2.2 or ZSDOS. For +this reason, (by design) NZCOM does not provide a way to boot directly +from disk. Rather, it is loaded after the system boots into a host OS. +On the RomWBW NZCOM disk images, the boot OS is ZSDOS 1.1. After you +configure NZCOM, you can add a `PROFILE.SUB` file to automatically +launch NZCOM at boot. + +NZCOM is not pre-configured. You must run through a simple configuration +process before loading it. Run MKZCM to do this. + +NZCOM has substantially more functionality than CP/M or basic Z-System. +It is important to read the the “NZCOM Users Manual.pdf†file in the +RomWBW Doc directory. + +### Notes + + - There is no `DIR` command, you must use `SDZ` instead. If you don’t + like this, look into the ALIAS facility. + +## Digital Research CP/M 3 + +This is the Digital Research follow-up product to their very popular +CP/M 2.2 operating system. While highly compatible with CP/M 2.2, it +features many enhancements. It makes direct use of banked memory to +increase the user program space (TPA). It also has a new suite of +support tools and help system. + +Note that to make a CP/M 3 boot disk, you actually place CPMLDR.SYS on +the system tracks of the disk. You do not place CPM3.SYS on the system +tracks. `CPMLDR.SYS` chain loads `CPM3.SYS`. + +### Notes + + - The `DATE` command cannot yet be used to **set** the RTC. The RTC is + used to read the current date/time for file stamping, etc. You can + use the `RTC` app to set the RTC clock. + +## Simeon Cran’s ZPM3 + +ZPM3 is an interesting combination of the features of both CP/M 3 and +ZCPR 3. Essentially, it has the features of and compatibility with both. + +Like CP/M 3, to make ZPM3 boot disk, you put CPMLDR.SYS on the system +tracks of the disk. + +### Notes + + - `ZPMLDR` is included with ZPM3, but it is not working correctly. + + - The ZPM operating system is contained in the file called CPM3.SYS + which is confusing, but it is the author’s intended way of using + ZPM3. + +## FreeRTOS + +Phillip Stevens has ported FreeRTOS to run under RomWBW. FreeRTOS is not +provided in the RomWBW distribution. FreeRTOS is available under the +[MIT licence](https://www.freertos.org/a00114.html) and further general +information is available at +[FreeRTOS](https://www.freertos.org/RTOS.html). + +You can also contact Phillip for detailed information on the Z180 +implementation of FreeRTOS for RomWBW. +[feilipu](https://github.com/feilipu) + +# Transferring Files + +Transferring files between your modern computer and your RomWBW system +can be achieved in a variety of ways. The most common of these are +described below. All of these have a certain degree of complexity and I +encourage new users to use the available community forums to seek +assistance as needed. + +## Serial Port Transfers + +RomWBW provides an serial file transfer program called XModem that has +been adapted to run under RomWBW hardware. The program is called `XM` +and is on your ROM disk as well as all of the pre-built disk images. + +You can type `XM` by itself to get usage information. In general, you +will run `XM` with parameters to indicate you want to send or receive a +file on your RomWBW system. Then, you will use your modern computers +terminal program to complete the process. + +The `XM` application generally tries to detect the hardware you are +using and adapt to it. However, you must ensure that you have a reliable +serial connection. You must also ensure that the speed of the connection +is not too fast for XModem to service. Alternatively, you can ensure +that hardware flow control is working properly. + +There is an odd interaction between XModem and partner terminal programs +that can occur. Essentially, after launching `XM`, you must start the +protocol on your modern computer fairly quickly (usually in about 20 +seconds or so). So, if you do not pick a file on your modern computer +quickly enough, you will find that the transfer completes about 16K, +then hangs. The interaction that causes this is beyond the scope of this +document. + +## Disk Image Transfers + +It is possible to pass disk images between your RomWBW system and your +modern computer. This assumes you have an appropriate media slot on your +modern computer for the media you want to use (CF Card, SD Card, or +floppy drive). + +The general process to get files from your modern computer to a RomWBW +computer is: + +1. Use `cpmtools` on your modern computer to create a RomWBW CP/M + filesystem image. + +2. Insert your RomWBW media (CF Card, SD Card, or floppy disk) in your + modern computer. + +3. Use a disk imaging tool to copy the RomWBW filesystem image onto the + media. + +4. Move the media back to the RomWBW computer. + +This process is a little complicated, but it has the benefit of allowing +you to get a lot of files over to your RomWBW system quickly and with +little chance of corruption. + +The process can be run in reverse to get files from your RomWBW computer +to a modern computer. + +The exact use of these tools is a bit too much for this document, but +the tools are all included in the RomWBW distribution along with usage +documents. + +Note that the build scripts for RomWBW create the default disk images +supplied with RomWBW. It is relatively easy to customize the contents of +the disk images that are part of RomWBW. This is described in more +detail in the Source\\Images directory of the distribution. + +## FAT Filesystem Transfers + +RomWBW provides a mechanism that allows it to read and write files on a +FAT formatted disk. This means that you can generally use your modern +computer to make an SD Card or CF Card with a standard FAT32 filesystem +on it, then place that media in your RomWBW computer and access the +files. + +When formatting the media on your modern computer, be sure to pick the +FAT filesystem. NTFS and other filesystems will not work. + +On your RomWBW computer you can use the `FAT` application to access the +FAT media. The `FAT` application allows you to read files, write files, +list a directory, and erase files on the FAT media. It can handle +subdirectories as well. It will only see 8.3 character filenames +however. Longer filenames will show up as a truncated version. + +The `FAT` application is not on your ROM disk because it is too large to +fit. You will find it on all of the pre-built disk images as well as in +the Binary\\Apps directory of the distribution. + +For advanced users, it is possible to create a hybrid disk that contains +CP/M slices at the beginning and a FAT filesystem after. Such a hybrid +disk can be used to boot an operating system and still have access to +FAT files on the FAT portion of the disk. David Reese has prepared a +document describing how to do this. It is called +“SC126\_How-To\_No\_2\_Preparing\_an\_SD\_Card\_for\_Use\_with\_SC126\_Rev\_1-5.pdf†+and can be found in the Doc\\Contrib directory of the distribution. + +# Startup Command Processing + +Each of the operating systems supported by RomWBW provide a mechanism to +run commands at boot. This is similar to the AUTOEXEC.BAT files from +MS-DOS. + +With the exception of ZPM3, all operating systems will look for a file +called `PROFILE.SUB` on the system drive at boot. If it is found, it +will be processed as a standard CP/M submit file. You can read about the +use of the SUBMIT facility in the CP/M manuals included in the RomWBW +distribution. Note that the boot disk must also have a copy of +`SUBMIT.EXE`. + +In the case of ZPM3, the file called `STARTZPM.COM` will be run at boot. +To customize this file, you use the ZCPR ALIAS facility. You will need +to refer to ZCPR documentation for more information on the ALIAS +facility. + +Note that the automatic startup processing generally requires booting to +a disk drive. Since the ROM disk is not writable, there is no simple way +to add/edit a `PROFILE.SUB` file there. If you want to customize your +ROM and add a `PROFILE.SUB` file to the ROM Disk, it will work, but is a +lot harder than using a boot disk. + +# ROM Customization + +The pre-built ROM images are configured for the basic capabilities of +each platform. Additionally, some of the typical add-on hardware for +each platform will be automatically detected and used. If you want to go +beyond this, RomWBW provides a very flexible configuration mechanism +based on configuration files. Creating a customized ROM requires running +a build script, but it is quite easy to do. + +Essentially, the creation of a custom ROM is accomplished by updating a +small configuration file, then running a script to compile the software +and generate the custom ROM and disk images. There are build scripts for +Windows, Linux, and MacOS to accommodate virtually all users. All +required build tools (compilers, assemblers, etc.) are included in the +distribution, so it is not necessary to setup a build environment on +your computer. + +The process for building a custom ROM is documented in the ReadMe.txt +file in the Source directory of the distribution. + +For those who are interested in more than basic system customization, +note that all source code is provided (including the operating systems). +Modification of the source code is considered an expert level task and +is left to the reader to pursue. + +Note that the ROM customization process does not apply to UNA. All UNA +customization is performed within the ROM setup script. + +# UNA Hardware BIOS + +John Coffman has produced a new generation of hardware BIOS called UNA. +The standard RomWBW distribution includes it’s own hardware BIOS. +However, RomWBW can alternatively be constructed with UNA as the +hardware BIOS portion of the ROM. If you wish to use the UNA variant of +RomWBW, then just program your ROM with the ROM image called +“UNA\_std.rom†in the Binary directory. This one image is suitable on +**all** of the platforms and hardware UNA supports. + +UNA is customized dynamically using a ROM based setup routine and the +setup is persisted in the system NVRAM of the RTC chip. This means that +the single UNA-based ROM image can be used on most of the RetroBrew +platforms and is easily customized. UNA also supports FAT file system +access that can be used for in-situ ROM programming and loading system +images. + +While John is likely to enhance UNA over time, there are currently a few +things that UNA does not support: + + - Floppy Drives + - Terminal Emulation + - Zeta 1, N8, RC2014, Easy Z80, and Dyno Systems + - Some older support boards + +The UNA version embedded in RomWBW is the latest production release of +UNA. RomWBW will be updated with John’s upcoming UNA release with +support for VGA3 as soon as it reaches production status. + +Please refer to the [UNA BIOS Firmware +Page](https://www.retrobrewcomputers.org/doku.php?id=software:firmwareos:una:start) +for more information on UNA. + +# Upgrading + +Upgrading to a newer release of RomWBW is essentially just a matter of +updating the ROM chip in your system. If you have spare ROM chips for +your system and a ROM programmer, it is always safest to retain your +existing, working ROM chip and program a new one with the new firmware. +If the new one fails to boot, you can easily return to the known working +ROM. + +Prior to attempting to reprogram your actual ROM chip, you may wish to +“try†the upgrade. With RomWBW, you can upload a new system image +executable and load it from the command line. For each ROM image file +(.rom) in the Binary directory, you will also find a corresponding +application file (.com). For example, for SBC\_std.rom, there is also an +SBC\_std.com file. You can upload the .com file to your system using +XModem, then simply run the .com file. You will see your system go +through the normal startup process just like it was started from ROM. +However, your ROM has not been updated and the next time you boot your +system, it will revert to the system image contained in ROM. + +If you do not have easy access to a ROM programmer, it is usually +possible to reprogram your system ROM using the FLASH utility from Will +Sowerbutts. This application, called FLASH.COM, can be found on the ROM +drive of any running system. In this case, you would need to transfer +the new ROM image (.rom) over to your system using XModem (or one of the +other mechanisms described in the Transferring Files section). The ROM +image is too large to fit on your RAM drive, so you will need to +transfer it to a larger storage drive. Once the ROM image is on your +system, you can use the FLASH application to update your ROM. The +following is a typical example of transferring ROM image using XModem +and flashing the chip in-situ. + + E>xm r rom.img + + XMODEM v12.5 - 07/13/86 + RBC, 28-Aug-2019 [WBW], ASCI + + Receiving: E0:ROM.IMG + 7312k available for uploads + File open - ready to receive + To cancel: Ctrl-X, pause, Ctrl-X + + Thanks for the upload + + E>flash write rom.img + FLASH4 by Will Sowerbutts version 1.2.3 + + Using RomWBW (v2.6+) bank switching. + Flash memory chip ID is 0xBFB7: 39F040 + Flash memory has 128 sectors of 4096 bytes, total 512KB + Write complete: Reprogrammed 2/128 sectors. + Verify (128 sectors) complete: OK! + +Obviously, there is some risk to this approach since any issues with the +programming or ROM image could result in a non-functional system. + +To confirm your ROM chip has been successfully updated, restart your +system and boot an operating system from ROM. Do not boot from a disk +device yet. Review the boot messages to see if any issues have occurred. + +Once you are satisfied that the ROM is working well, you will need to +update the system images and RomWBW custom applications on your disk +drives. The system images and custom applications are matched to the +RomWBW ROM firmware in use. If you attempt to boot a disk or run +applications that have not been updated to match the current ROM +firmware, you are likely to have odd problems. + +The simplest way to update your disk media is to just use your modern +computer to overwrite the entire media with the latest disk image of +your choice. This process is described below in the Disk Images section. +If you wish to update existing disk media in your system, you need to +perform the following steps. + +If the disk is bootable, you need to update the system tracks of the +disk. This is done using a SYSCOPY command such as `SYSCOPY +C:=B:ZSYS.SYS`. For a ZSDOS boot disk, use ZSYS.SYS. For a CP/M 2.2 +disk, use CPM.SYS. For a CP/M 3 or ZPM3 disk, use CPMLDR.SYS. CPMLDR.SYS +is not provided on the ROM disk, so you will need to upload it from the +distribution. + +Finally, if you have copies of any of the RomWBW custom applications on +your hard disk, you need to update them with the latest copies. The +following applications are found on your ROM disk. Use COPY to copy them +over any older versions of the app on your disk: + + - ASSIGN.COM + - SYSCOPY.COM + - MODE.COM + - FDU.COM (was FDTST.COM) + - FORMAT.COM + - XM.COM + - FLASH.COM + - FDISK80.COM + - TALK.COM + - RTC.COM + - TIMER.COM + - INTTEST.COM + +For example: `B>COPY ASSIGN.COM C:` + +Some RomWBW custom applications are too large to fit on the ROM disk. If +you are using any of these you will need to transfer them to your system +and then update all copies. These applications are found in the +Binary\\Apps directory of the distribution and in all of the disk +images. + + - FAT.COM + - TUNE.COM + +# RomWBW Distribution + +All source code and distributions are maintained on GitHub. Code +contributions are very welcome. + +[RomWBW GitHub +Repository](https://github.com/wwarthen/RomWBW%7Chttps://github.com/wwarthen/RomWBW) + +## Distribution Directory Layout + +The RomWBW distribution is a compressed zip archive file organized in a +set of directories. Each of these directories has it’s own ReadMe.txt +file describing the contents in detail. In summary, these directories +are: + +| Application | Description | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| Binary | The final output files of the build process are placed here. Most importantly, are the ROM images with the file names ending in “.româ€. | +| Doc | Contains various detailed documentation including the operating systems, RomWBW architecture, etc. | +| Source | Contains the source code files used to build the software and ROM images. | +| Tools | Contains the MS Windows programs that are used by the build process or that may be useful in setting up your system. | + +# Acknowledgments + +While I have heavily modified much of the code, I want to acknowledge +that much of the work is derived from the work of others in the +RetroBrew Computers Community including Andrew Lynch, Dan Werner, Max +Scane, David Giles, John Coffman, and probably many others I am not +clearly aware of (let me know if I omitted someone\!). + +I especially want to credit Douglas Goodall for contributing code, time, +testing, and advice. He created an entire suite of application programs +to enhance the use of RomWBW. However, he is looking for someone to +continue the maintenance of these applications and they have become +unusable due to changes within RomWBW. As of RomWBW 2.6, these +applications are no longer provided. + + - David Giles contributed support for the CSIO support in the SD Card + driver. + - Ed Brindley contributed some of the code that supports the RC2014 + platform. + - Phil Summers contributed Forth and BASIC in ROM as well as a long + list of general code enhancements. + - Phillip Stevens contributed support for FreeRTOS. + - Curt Mayer contributed the Linux / MacOS build process. + - UNA BIOS and FDISK80 is a product of John Coffman. + - FLASH4 is a product of Will Sowerbutts. + +Contributions of all kinds to RomWBW are very welcome. + +# Getting Assistance + +The best way to get assistance with RomWBW or any aspect of the +RetroBrew Computers projects is via the community forums: + + - [RetroBrew Computers + Forum](https://www.retrobrewcomputers.org/forum/) + - [RC2014 Google + Group](https://groups.google.com/forum/#!forum/rc2014-z80) + - [retro-comp Google + Group](https://groups.google.com/forum/#!forum/retro-comp) + +Submission of issues and bugs are welcome at the [RomWBW GitHub +Repository](https://github.com/wwarthen/RomWBW). + +Also feel free to email Wayne Warthen at . diff --git a/ReadMe.txt b/ReadMe.txt index 83b9f503..f47dcf47 100644 --- a/ReadMe.txt +++ b/ReadMe.txt @@ -1,280 +1,1193 @@ -*********************************************************************** -*** *** -*** R o m W B W *** -*** *** -*** Z80/Z180 System Software *** -*** *** -*********************************************************************** - -Wayne Warthen (wwarthen@gmail.com) -Version 2.9.1-pre.11, 2019-03-05 -https://www.retrobrewcomputers.org/ - -RomWBW is a ROM-based implementation of CP/M-80 2.2 and Z-System for -all RetroBrew Computers Z80/Z180 hardware platforms including SBC -1/2, Zeta 1/2, N8, Mark IV, and RC2014. Virtually all RetroBrew -hardware is supported including floppy, hard disk (IDE, CF Card, SD -Card), Video, and keyboard. VT-100 terminal emulation is built-in. - -The RomWBW ROM loads and runs the built-in operating systems directly -from the ROM and includes a selection of standard/useful applications -accessed via a ROM disk drive. A RAM disk drive is also provided -to allow temporary file storage. - -Pre-built ROM images are included for all platforms. Detailed system -customization is achieved by making simple modifications to a -configuration file and running a build script to generate a custom -ROM image. All source and build tools are included in the -distribution. As distributed, the build scripts run under any modern -32 or 64 bit version of Microsoft Windows. - -John Coffman's UNA hardware BIOS is fully supported by RomWBW. In the -case of UNA, a single ROM image (pre-built) is used for all supported -platforms and is customized using a ROM-based setup program. See the -UNA section below for more information. - -Quick Start ------------ - -A pre-built ROM image is included for each of the hardware platforms -supported. These ROM images are found in the Binary directory of the -distribution and have a file extension of ".rom". Simply program the -ROM of your system with the appropriate ROM image. Please see the -RomList.txt file in the Binary directory for details on selecting the -correct ROM image for your system and platform specific information. - -Connect a serial terminal or computer with terminal emulation -software to the primary RS-232 port of your CPU board. A null-modem -connection is generally required. Set the line characteristics to -38400 baud, 8 data bits, 1 stop bit, no parity, and no flow control. -Select VT-100 terminal emulation. In the case of the RC2014, the -baud rate is determined by hardware, but is normally 115200 baud. - -Upon power-up, your terminal should display a sign-on banner within 2 -seconds followed by hardware inventory and discovery information. -When hardware initialization is completed, a boot loader prompt -allows you to choose a ROM-based operating system, system monitor, or +RomWBW + +Z80/Z180 System Software + +Version 3.1 Pre-release +Saturday 11 April 2020 + +Wayne Warthen wwarthen@gmail.com + +Download + +- RomWBW Distribution Package + +Related Pages + +- RomWBW Architecture Document +- RomWBW Applications +- RomWBW Errata + +Overview + +RomWBW provides a complete software system for a wide variety of +hobbyist Z80/Z180 CPU-based systems produced by these developer +communities: + +- RetroBrew Computers +- RC2014 +- retro-comp + +General features include: + +- Banked memory services for several banking designs +- Disk drivers for RAM, ROM, Floppy, IDE, CF, and SD +- Serial drivers including UART (16550-like), ASCI, ACIA, SIO +- Video drivers including TMS9918, SY6545, MOS8563, HD6445 +- Real time clock drivers including DS1322, BQ4845 +- Multiple OS support including CP/M 2.2, ZSDOS, CP/M 3, ZPM3 +- Built-in VT-100 terminal emulation support + +RomWBW is distributed as both source code and pre-built ROM and disk +images. Some of the provided software can be launched directly from the +ROM firmware itself: + +- System Monitor +- Operating Systems (CP/M 2.2, ZSDOS) +- ROM BASIC (Nascom BASIC and Tasty BASIC) +- ROM Forth + +A dynamic disk drive letter assignment mechanism allows mapping +operating system drive letters to any available disk media. +Additionally, mass media devices (IDE Disk, CF Card, SD Card) support +the use of multiple slices (up to 256 per device). Each slice contains a +complete CP/M filesystem and can be mapped independently to any drive +letter. This overcomes the inherent size limitations in legacy OSes and +allows up to 2GB of accessible storage on a single device. + +The pre-built ROM firmware images are generally optimal for most users. +However, it is also very easy to modify and build custom ROM images that +fully tailor the firmware to your specific preferences. All tools +required to build custom ROM firmware are included – no need to install +assemblers, etc. Any modern computer running Windows, Linux, or MacOS +can be used. + +Multiple disk images are provided in the distribution. Most disk images +contain a complete, bootable, ready-to-run implementation of a specific +operating system. A “combo†disk image contains multiple slices, each +with a full operating system implementation. If you use this disk image, +you can easily pick whichever operating system you want to boot without +changing media. + +Installation + +The latest RomWBW distribution downloads are maintained on GitHub in the +RomWBW Repository. The fully-built distributions are found on the +releases page of the repository. On this page, you will probably see +both pre-releases as well as normal releases. Unless you have a specific +reason, I suggest you stick to the most recent normal release (not +pre-release). Expand the “Assets†drop-down for the release you want to +download, then select the asset named RomWBW-vX.X.X-Package.zip. The +Package asset includes all pre-built ROM and Disk images as well as full +source code. The other assets are Source Code only and do not have the +pre-built ROM or disk images. + +The pre-built ROM images will automatically detect and support a +reasonable range of devices including serial ports, video adapters, +on-board disk interfaces, and PropIO/ParPortProp boards without building +a custom ROM. The distribution is a .zip archive. After downloading it +to a working directory on your modern computer (Windows/Linux/Mac) use +any zip tool to extract the contents of the archive. + +In general, you will just program your system’s ROM chip with the +appropriate ROM image from the RomWBW distribution. Depending on how you +got your system, you may have already been provided with a +pre-programmed ROM chip. If so, use that initially. Otherwise, you will +need to use a ROM programmer to initially program your ROM chip. Please +refer to the documentation that came with your ROM programmer for more +information. Once you have a running RomWBW system, you can generally +update your ROM to a newer version in-situ with an included ROM Flashing +tool (Will Sowerbutts’ FLASH application) as described in the Upgrading +section below. + +Looking at the extracted distribution archive, You will see that the +distribution is broken up into a few sub-directories. The Binary +directory contains the pre-built ROM and disk images. The ROM image +files all end in “.româ€. Based on the table below, carefully pick the +appropriate ROM image for your hardware. + + ------------------------------------------------------------------------- + Platform ROM Image File Baud Description + ---------- ----------------- -------- ----------------------------------- + SBC SBC_std.rom 38400 RetroBrew SBC v1 or v2 ECB Z80 + + Zeta V1 ZETA_std.rom 38400 RetroBrew Zeta V1 Z80, ParPortProp + (optional) + + Zeta V2 ZETA2_std.rom 38400 RetroBrew Zeta V2 Z80, ParPortProp + (optional) + + N8 N8_std.rom 38400 RetroBrew N8 Z180, date code >= + 2312 + + Mark IV MK4_std.rom 38400 RetroBrew Mark IV ECB Z180 + + RC Z80 RCZ80_std.rom 115200 RC2014 w/ Z80 CPU, requires 512K + RAM/ROM module + + RC Z180* RCZ180_ext.rom 115200 RC2014 w/ Z180 CPU & 512K banked + RAM/ROM module + + RC Z180* RCZ180_nat.rom 115200 RC2014 w/ Z180 CPU & 512K native + RAM/ROM module + + Easy Z80 EZZ80_std.rom 115200 Sergey Kiselev’s Easy Z80 + + SC126 SCZ180_126.rom 115200 Stephen Cousin’s SC126 Z180 + + SC130 SCZ180_130.rom 115200 Stephen Cousin’s SC130 Z180 + + SC131 SCZ180_131.rom 115200 Stephen Cousin’s SC131 Z180 + + Dyno DYNO_std.rom 38400 Steve Garcia’s Z180 Dyno Computer + ------------------------------------------------------------------------- + +*The RC2014 Z180 requires a separate RAM/ROM memory module. There are +two types of these modules and you must pick the ROM for your type of +memory module. The “ext†ROM supports Spencer’s official 512K RAM/ROM +banked memory module. The “nat†ROM supports any of the third-party Z180 +native memory modules. + +RomWBW will automatically attempt to detect and support typical add-on +components for each of the systems supported. More information on the +required system configuration and optional supported components for each +ROM is found in the file called “RomList.txt†in the Binary directory. +All pre-built ROM images are simple 512KB binary images. If your system +utilizes a larger ROM chip, you can just program the image into the +first 512KB of the ROM. + +Connect a serial terminal or computer with terminal emulation software +to the primary serial port of your CPU board. You may need to refer to +your hardware provider’s documentation for details. A null-modem +connection may be required. Set the baud rate as indicated in the table +above. Set the line characteristics to 8 data bits, 1 stop bit, no +parity, and no flow control. If possible, select VT-100 terminal +emulation. + +Upon power-up, your terminal should display a sign-on banner within 2 +seconds followed by hardware inventory and discovery information. When +hardware initialization is completed, a boot loader prompt allows you to +choose a ROM-based operating system, system monitor, application, or boot from a disk device. -CPU Speed ---------- +Initially, you should try the ROM boot options. By selecting either CP/M +2.2 or Z-System, the selected operating system will be loaded from ROM +and you will see the a B> disk prompt. In this scenario, A: will be an +empty RAM disk and B: will refer to your ROM disk containing some common +applications. This provides a simple environment for learning to use +your system. Be aware that files saved to the RAM disk (A:) will +disappear at the next power on (RAM is generally not persistent). Also +note that attempts to save files to the ROM disk (B:) will fail because +ROM is not writable. + +General Usage + +Each of the operating systems and ROM applications included with RomWBW +are sophisticated tools in their own right. It is not reasonable to +document their usage here. However, you will find complete manuals in +PDF format in the Doc directory of the distribution. The intention of +this section is to document the RomWBW specific enhancements to these +operating systems. + +Inbuilt ROM Applications + +In addition to CP/M 2.2 and Z-System, there are several ROM applications +that can be launched directly from ROM. These applications are not +hosted by an operating system and so they are unable to save files to +disk devices. + +The following ROM applications are available at the boot loader prompt: + + Application + ------------- -------------------------------------------------------- + Monitor Z80 system debug monitor w/ Intel Hex loader + Forth Brad Rodriguez’s ANSI compatible Forth language + Basic Nascom 8K BASIC language + Tasty BASIC Dimitri Theuling’s Tiny BASIC implementation + Play A simple video game (requires ANSI terminal emulation) + +In general, the command to exit these applications and restart the +system is BYE. The exceptions are the Monitor which uses B and Play +which uses Q. + +Space is available in the ROM image for the inclusion of other software. +Any inbuilt application can be set up to launch automatically at +startup. + +Devices and Units + +In order to support a wide variety of hardware, RomWBW HBIOS uses a +modular approach to implementing device drivers and presenting devices +to the operating system. In general, all devices are classified as one +of the following: + +- Disk (Hard Disk, CF Card, SD Card, RAM/ROM Disk, etc.) +- Character (Serial Ports, Parallel Ports, etc.) +- Video (Video Display/Keyboard Interfaces) +- RTC/NVRAM (Real Time Clock, Non-volatile RAM) + +HBIOS uses the concept of unit numbers to present a complex set of +hardware devices to the operating system. As an example, a typical +system might have a ROM Disk, RAM Disk, Floppy Drives, and Disk Drives. +All of these are considered Disk devices and are presented to the +operating system as generic block devices. This means that the operating +system does not need to understand the difference between a floppy drive +and a ROM disk. + +As RomWBW boots, it assigns a unit number to each device. This unit +number is used by the operating system to refer to the device. It is, +therefore, important to know the unit number assigned to each device. +This information is displayed in the unit summary table at startup. Here +is an example: + + Unit Device Type Capacity/Mode + ---------- ---------- ---------------- -------------------- + Char 0 UART0: RS-232 38400,8,N,1 + Char 1 UART1: RS-232 38400,8,N,1 + Disk 0 MD1: RAM Disk 384KB,LBA + Disk 1 MD0: ROM Disk 384KB,LBA + Disk 2 FD0: Floppy Disk 3.5",DS/HD,CHS + Disk 3 FD1: Floppy Disk 3.5",DS/HD,CHS + Disk 4 IDE0: CompactFlash 3815MB,LBA + Disk 5 IDE1: Hard Disk -- + Disk 6 PRPSD0: SD Card 1886MB,LBA + Video 0 CVDU0: CRT Text,80x25 + +In this example, you can see that the system has a total of 7 Disk Units +numbered 0-6. There are also 2 Character Units and 1 Video Unit. The +table shows the unit numbers assigned to each of the devices. Notice how +the unit numbers are assigned sequentially regardless of the specific +device. + +There may or may not be media in the disk devices listed. For example, +the floppy disk devices (Disk Units 2 & 3) may not have a floppy in the +drive. Also note that Disk Unit 4 shows a disk capacity, but Disk Unit 5 +does not. This is because the PPIDE interface of the system supports up +to two drives, but there is only one actual drive attached. A unit +number is assigned to all possible devices regardless of whether they +have actual media installed at boot time. -RomWBW ROM images support virtually any CPU speed your system is -running. However, there are some hardware-oriented caveats to be -aware of. +Note that Character Unit 0 is always the initial system console by +definition. -The use of high density floppy disks requires a CPU speed of 8 MHz or -greater. +If your system has an RTC/NVRAM device, it will not be listed in the +unit summary table. Since only a single RTC/NVRAM device can exist in +one system, unit numbers are not required nor used for this type of +device. -Upgrading from Previous Versions --------------------------------- +Drive Letter Assignment -Program a new ROM chip from an image in the new distribution. Install -the new ROM chip and boot your system. At the boot loader "Boot:" -prompt, select either CP/M or Z-System to load the OS from ROM. +In legacy CP/M-type operating systems, drive letters were generally +mapped to disk drives in a completely fixed way. For example, drive A: +would always refer to the first floppy drive. Since RomWBW supports a +wide variety of hardware configurations, it implements a much more +flexible drive letter assignment mechanism so that any drive letter can +be assigned to any disk device. -If you have spare rom chips for your system, it is always safest to -keep your existing, working ROM chip and program a new one so that you -can return to the old one if the new one does not work properly. +At boot, you will notice that RomWBW automatically assigns drive letters +to the available disk devices. These assignments are displayed during +the startup of the selected operating system. Additionally, you can +review the current drive assignments at any time using the ASSIGN +command. CP/M 3 and ZPM3 do not automatically display the assignments at +startup, but you can use ASSIGN do display them. -If you use a customized ROM image, it is recommended that you first -try the pre-built ROM image first and then move on to generating a -custom image. +The drive letter assignments do not change during an OS session unless +you use the ASSIGN command yourself to do it. Additionally, the +assignments at boot will stay the same on each boot as long as you do +not make changes to your hardware configuration. Note that the +assignments are dependent on the media currently inserted in hard disk +drives. So, notice that if you insert or remove an SD Card or CF Card, +the drive assignments will change. Since drive letter assignments can +change, you must be careful when doing destructive things like using +CLRDIR to make sure the drive letter you use is referring to the desired +media. -It is entirely possible to reprogram your system ROM using the FLASH -utility from Will Sowerbutts on your ROM drive (B:). In this case, -you would need to transfer the new ROM image to your system using -X-Modem. Obviously, there is some risk to this approach since any -issues with the programming or ROM image could result in a -non-functional system. +When performing a ROM boot of an operating system, note that A: will be +your RAM disk and B: will be your ROM disk. When performing a disk boot, +the disk you are booting from will be assigned to A: and the rest of the +drive letters will be offset to accommodate this. This is done because +most legacy operating systems expect that A: will be the boot drive. -If your system has any bootable drives, then update the OS image on -each drive using SYSCOPY. For example, if C: is a bootable drive -with the Z-System OS, you would update the OS image on this drive -with the command: +Slices + +The vintage operating systems included with RomWBW were produced at a +time when mass storage devices were quite small. CP/M 2.2 could only +handle filesystems up to 8MB. In order to achieve compatibility across +all of the operating systems supported by RomWBW, the hard disk +filesystem format used is 8MB. This ensures any filesystem will be +accessible to any of the operating systems. + +Since storage devices today are quite large, RomWBW implements a +mechanism called slicing to allow up to 256 8MB filesystems on a single +large storage device. This allows up to 2GB of usable space on a single +media. You can think of slices as a way to refer to any of the first 256 +8MB chunks of space on a single media. + +Of course, the problem is that CP/M-like operating systems have only 16 +drive letters (A:-P:) available. Under the covers, RomWBW allows you to +use any drive letter to refer to any slice of any media. The ASSIGN +command is allows you to view or change the drive letter mappings at any +time. At startup, the operating system will automatically allocate a +reasonable number of drive letters to the available storage devices. The +allocation will depend on the number of large storage devices available +at boot. For example, if you have only one hard disk type media, you +will see that 8 drive letters are assigned to the first 8 slices of that +media. If you have two large storage devices, you will see that each +device is allocated four drive letters. + +Referring to slices within a storage device is done by appending a : +where is the device relative slice number from 0-255. For example, if +you have an IDE device, it will show up as IDE0: in the boot messages +meaning the first IDE device. To refer to the fourth slice of IDE0, you +would type “IDE0:3â€. Here are some examples: + + -------- ------------------------------ + IDE0:0 First slice of disk in IDE0 + IDE0: First slice of disk in IDE0 + IDE0:3 Fourth slice of disk in IDE0 + -------- ------------------------------ + +So, if I wanted to use drive letter L: to refer to the fourth slice of +IDE0, I could use the command ASSIGN L:=IDE0:3. There are a couple of +rules to be aware of when assigning drive letters. First, you may only +refer to a specific device/slice with one drive letter. Said another +way, you cannot have multiple drive letters referring to a single +device/slice at the same time. Second, there must always be a drive +assigned to A:. Any attempt to violate these rules will be blocked by +the ASSIGN command. + +Unlike MS-DOS partitions, slices are not allocated – there is no +partitioning for slices. Think of every hard disk type device as having +a pre-allocated set of 256 8MB slices at the start of the media. You can +refer to any of them simply by assigning a drive letter. RomWBW will not +check to see if there is anything else on the hard disk in the slice you +are referring to, nor will it verify that the hard disk media is large +enough to have a slice at the location you refer to. If you attempt to +write past the end of your media, you will get an I/O error displayed, +so you will know if you make a mistake. There is no tracking of your use +of slices – you will need to keep track of your use of slices yourself. + +Nothing automatically initializes a slice as a file system. You must do +that yourself using CLRDIR. Since CLRDIR works on drive letters, make +absolutely sure you know what media and slice are assigned to that drive +letter before using CLRDIR. + +While it is probably obvious, you cannot use slices on any media less +than 8MB in size. Specifically, you cannot slice RAM disks, ROM disks, +floppy disks, etc. + +RomWBW Custom Applications + +The operation of the RomWBW hosted operating systems is enhanced through +several custom applications. These applications are functional on all of +the OS variants included with RomWBW. + +The following custom applications are found on the ROM disk and are, +therefore, globally available. + + ----------------------------------------------------------------------------- + Application Description + ------------- --------------------------------------------------------------- + ASSIGN Add, change, and delete drive letter assignments. Use ASSIGN /? + for usage instructions. + + SYSCOPY Copy system image to a device to make it bootable. Use SYSCOPY + with no parms for usage instructions. + + MODE Reconfigures serial ports dynamically. + + FDU Format and test floppy disks. Menu driven interface. + + FORMAT Will someday be a command line tool to format floppy disks. + Currently does nothing! + + XM XModem file transfer program adapted to hardware. Automatically + uses primary serial port on system. + + FLASH Will Sowerbutts’ in-situ ROM programming utility. + + FDISK80 John Coffman’s Z80 hard disk partitioning tool. See + documentation in Doc directory. + + TALK Direct console I/O to a specified character device. + + RTC Manage and test the Real Time Clock hardware. + + TIMER Display value of running periodic system timer. + + INTTEST Test interrupt vector hooking. + ----------------------------------------------------------------------------- + +Some custom applications do not fit on the ROM disk. They are found on +the disk image files or the individual files can be found in the +Binary\Apps directory of the distribution. + + Application Description + ------------- ------------------------------------------------------------- + TUNE Play .PT2, .PT3, .MYM audio files. + FAT Access MS-DOS FAT filesystems from RomWBW (based on FatFs). + +Additional documentation on all of these applications can be found in +“RomWBW Applications.pdf†in the Doc directory of the distribution. + +Using Disks + +ROM & RAM Disks + +RomWBW utilizes a portion of the ROM and RAM memory in your system to +implement small memory-based disks. + +The RAM disk provides a small CP/M filesystem that you can use for the +temporary storage of files. Unless your system has a battery backed +mechanism for persisting your RAM contents, the RAM disk contents will +be lost at each power-off. However, the RAM disk is an excellent choice +for storing temporary files because it is very fast. + +Like the RAM disk, the ROM disk also provides a small CP/M filesystem, +but it’s contents are static – they are part of the ROM. As such, you +cannot save files to the ROM disk. Any attempt to do this will result in +a disk I/O error. The contents of the ROM disk have been chosen to +provide a core set of tools and applications that are helpful for either +CP/M 2.2 or ZSDOS. Since ZSDOS is CP/M 2.2 compatible, this works fairly +well. However, you will find some files on the ROM disk that will work +with ZSDOS, but will not work on CP/M 2.2. For example, LDDS, which +loads the ZSDOS date/time stamper will only run on ZSDOS. + +Disk Devices + +While the RAM/ROM disks provide a functional system, they are not useful +in the long term because you cannot save data across power cycles. They +are also constrained by limited space. + +The systems supported by RomWBW all have the ability to use persistent +disk media. A wide variety of disk devices are supported including +floppy drives, hard disks, CF Cards, and SD Cards. Some systems have +disk interfaces built-in, while others will require add-in cards. You +will need to refer to the documentation for your system for your +specific options. + +In the RomWBW boot messages, you will see hardware discovery messages. +If you have a disk drive interface, you should see messages listing +device types like FD:, IDE:, PPIDE:, SD:. Additionally, you will see +messages indicating the media that has been found on the interfaces. As +an example, here are the messages you might see if you have an IDE +interface in your system with a single CF Card inserted in the primary +side of the interface: + + IDE: IO=0x80 MODE=MK4 + IDE0: 8-BIT LBA BLOCKS=0x00773800 SIZE=3815MB + IDE1: NO MEDIA + +The messages you see will vary depending on your hardware and the media +you have installed. But, they will all have the same general format as +the example above. + +Once your your system has working disk devices, you can boot an +operating system and the operating system will have access to the media. +At the boot loader prompt, select either either CP/M 2.2 or Z-System to +boot from ROM. As the operating system starts up, you should see a list +of drive letters assigned to the disk media you have installed. Here is +an example of this: + + Configuring Drives... + + A:=MD1:0 + B:=MD0:0 + C:=IDE0:0 + D:=IDE0:1 + +You will probably see more drive letters than this. The drive letter +assignment process is described above in the Drive Letter Assignment +section. Be aware that RomWBW will only assign drive letters to disk +interfaces that actually have media in them. If you do not see drive +letters assigned as expected, refer to the prior system boot messages to +ensure media has been detected in the interface. Actually, there is one +exception to this rule: floppy drives will be assigned a drive letter +regardless of whether there is any media inserted at boot. + +Notice how each drive letter refers back to a specific disk hardware +interface like IDE0. This is important as it is telling you what each +drive letter refers to. Also notice that mass storage disks (like IDE) +will normally have multiple drive letters assigned. The extra drive +letters refer to additional “slices†on the disk. The concept of slices +is described above in the Slices section. + +Once you are seeing drive letters referring to your disk media, you can +follow the instructions below to begin using the disk media with the +operating system. Your disk media must be initialized prior to being +used. There are two ways to initialize your media for use. + +One option is to initialize the media in-place using your RomWBW system. +This process is described below under Disk Initialization. In this +scenario, you will need to subsequently copy any files you want to use +onto the newly initialized disk (see Transferring Files). + +Alternatively, you can use your modern Windows, Linux, or Mac computer +to copy a disk image onto the disk media. RomWBW comes with a variety of +disk images that are ready to use and have a much more complete set of +files than you will find on the ROM disk. This process is covered below +under Disk Images. + +Disk Initialization + +To use a disk device, you will need to initialize the directory of the +filesystem. On RomWBW, the initialization is done using the CLRDIR +application. For example if your C: drive has been assigned to a storage +device, you would use CLRDIR C: to initialize C: and prepare it hold +files. Note that CLRDIR will prompt you for confirmation and you must +respond with a capital ‘Y’ to confirm. Once CLDIR has completed, you can +copy files onto the drive, for example COPY *.* C:. Be very careful to +pay attention to your drive letter assignments prior to running CLRDIR +to avoid accidentally wiping out a filesystem that has data on it. + +Running CLRDIR on a disk device is roughly equivalent to running FORMAT +on MS-DOS. Note that unlike MS-DOS you do not partition your mass +storage device. CP/M knows nothing about disk partitions. You may notice +a partitioning application on your ROM disk (FDISK80), but this is +strictly for an advanced technique of adding an MS-DOS FAT filesystem to +your media in addition to the CP/M area. Do not use FDISK80 unless you +are specifically attempting to add an MS-DOS FAT filesystem to your +media. + +If you are using a floppy drive, you will need to physically format your +floppy disk prior to use. This is only required for floppy disks, not +hard disk, CF Cards, or SD Cards, etc. To format a floppy drive, you can +use the interactive application FDU. FDU is not terribly user friendly, +but is generally documented in the file “FDU.txt†found in the Doc +directory of the distribution. It is not necessary to run CLRDIR on a +floppy disk after physically formatting it – the directory is cleared as +part of the formatting. + +Once you have initialized a disk device and copied your desired files +onto it, you may want to make the disk bootable. On CP/M filesystems, +you must perform one additional step to make a disk bootable. +Specifically, you need to place a copy of the operating system on the +system tracks of the disk. This is done using the SYSCOPY command. Let’s +say you have prepared drive C: by initializing it with CLRDIR and copied +some files onto it. You can now make C: bootable by running the +following command: + +B>SYSCOPY C:=B:ZSYS.SYS + +This command means: copy the Z-System operating system onto the system +tracks of drive C:. In this example, it is assumed that you have booted +from ROM, so B: is the ROM disk drive. Additionally, this example +assumes you want the Z-System operating system to be booted from C:. If +you want CP/M 2.2 instead, you would replace B:ZSYS.SYS with B:CPM.SYS. +Here is a full example of this process. B>SYSCOPY C:=B:ZSYS.SYS -If you have copies of any of the system utilities on drives other -than the ROM disk drive, you need to copy the latest version of the -programs from the ROM drive (B:) to any drives containing these -programs. For example, if you have a copy of the ASSIGN.COM program -on C:, you would update it from the new ROM using the COPY command: + SYSCOPY v2.0 for RomWBW CP/M, 17-Feb-2020 (CP/M 2 Mode) + Copyright 2020, Wayne Warthen, GNU GPL v3 - B>COPY B:ASSIGN.COM C: + Transfer system image from B:ZSYS.SYS to C: (Y/N)? Y + Reading image... Writing image... Done -The following programs are maintained with the ROM images and all -copies of these programs should be updated when upgrading to a new -ROM version: +Once this process succeeds, you will be able to boot directly to the +disk from the boot loader prompt. See the instructions in Booting Disks +for details on this. - - ASSIGN.COM - - FORMAT.COM - - OSLDR.COM - - SYSCOPY.COM - - TALK.COM - - FDU.COM - - XM.COM - - RTC.COM +Disk Images -UNA Hardware BIOS ------------------ +As mentioned previously, RomWBW includes a variety of disk images that +contain a full set of applications for the operating systems supported. +It is generally easier to use these disk images instead of copying all +the files over using XModem. You use your modern computer (Windows, +Linux, MacOS) to place the disk image onto the disk media, then just +move the media over to your system. In this scenario you do not run +CLRDIR or SYSCOPY on the drive(s). The directory is prepared and the +disk is already bootable, if it is an operating system boot disk image. -John Coffman has produced a new generation of hardware BIOS called -UNA. In addition to the classic ROM images, RomWBW comes with a -UNA-based image that combines the UNA BIOS with the RomWBW OS -implementations and applications. +To copy the disk image files onto your actual media (floppy disk, CF +Card, SD Card, etc.), you need to use an image writing utility on your +modern computer. Your modern computer will need to have an appropriate +interface or slot that accepts the media. To actually copy the image, +you can use the dd command on Linux or MacOS. On Windows, in the “Tools†+directory of the distribution there are two tools you can use. For +floppy media, you can use RawWriteWin and for hard disk media, you can +use Win32DiskImager. In all cases, the image file should be written to +the media starting at the very first block or sector of the media. This +will destroy any other data on the media. -UNA is customized dynamically using a ROM based setup routine and the -setup is persisted in the system NVRAM of the RTC chip. This means -that a single UNA-based ROM image can be used on most of the -RetroBrew platforms and is easily customized. UNA also supports FAT -file system access that can be used for in-situ ROM programming and -loading system images. - -While John is likely to enhance UNA over time, there are currently a -few things that UNA does not support: - - - Floppy Drives - - Video/Keyboard/Terminal Emulation - - Zeta 1 and N8 Systems - - Some older support boards - -If you wish to try the UNA variant of RomWBW, then just program your -ROM with the ROM image called "UNA_std.rom" in the Binary directory. -This one image is suitable on all of the platforms and hardware UNA -supports. - -Please refer to the RetroBrew Computers Wiki for more information on -UNA. - -CP/M vs. Z-System ------------------ - -There are two OS variants included in this distribution and you may -choose which one you prefer to use. Both variants are now included -in the pre-built ROM images. You will be given the choice to boot -either CP/M or Z-System at startup. - -The traditional Digital Research (DRI) CP/M OS is the first choice. -The Doc directory contains a manual for CP/M usage ("CPM -Manual.pdf"). If you are new to the RetroBrew Computer systems, I -would currently recommend using the CP/M variant to start with simply -because it has gone through more testing and you are less likely to -encounter problems. - -The other choice is to use the most popular non-DRI CP/M "clone" -which is generally referred to as Z-System. It is intended to be -functionally equivalent to CP/M and should run all CP/M 2.2 code. It -is optimized for the Z80 CPU (as opposed to 8080 for CP/M) and has -some potentially useful improvements. Please refer to "ZSDOS -Manual.pdf" and "ZCPR Manual.pdf" in the Doc directory for more -information on Z-System usage. +The disk image files are found in the Binary directory of the +distribution. Floppy disk images are prefixed with “fd_†and hard disk +images are prefixed with “hd_â€. The floppy images are specifically for +1.44M floppy media only. Each disk image has the complete set of normal +applications and tools distributed with the associated operating system +or application suite. + +The following table shows the disk image files available. Note that the +images in the “Hard†column are fine for use on CF Cards, SD Cards, as +well as real spinning hard disks. + + Floppy Hard Description + -------------- -------------- ------------------------------ + fd_cpm22.img hd_cpm22.img DRI CP/M 2.2 boot disk + fd_zsdos.img hd_zsdos.img ZSDOS 1.1 boot disk + fd_nzcom.img hd_nzcom.img NZCOM boot disk + fd_cpm3 hd_cpm3.img DRI CP/M 3 boot disk + fd_zpm3 hd_zpm3.img ZPM3 boot disk + fd_ws4 hd_ws4.img WordStar v4 application disk + +In addition to the disk images above, there is also a special hard disk +image called hd_combo.img. This image contains all of the images above, +but in a single image with 6 slices. At the boot loader prompt, you can +choose a disk with the combo image, then select the specific slice you +want. This allows a single disk to have all of the possible operating +system options. + +This is the layout of the hd_combo disk image: + + Slice Description + --------- ------------------------------ + Slice 0 DRI CP/M 2.2 boot disk + Slice 1 ZSDOS 1.1 boot disk + Slice 2 NZCOM boot disk + Slice 3 DRI CP/M 3 boot disk + Slice 4 ZPM3 boot disk + Slice 5 WordStar v4 application disk + +Note that unlike the ROM firmware, you do not need to choose a disk +image specific to your hardware. Because the RomWBW firmware provides a +hardware abstraction layer, all hard disk images will work on all +hardware variations. Yes, this means you can remove an SD Card from one +system and put it in a different system. The only constraint is that the +applications on the disk media must be up to date with the firmware on +the system being used. + +All of the disk images that indicate they are bootable (boot disk) will +boot from disk as is. You do not need to run SYSCOPY on them to make +them bootable. However, if you upgrade your ROM, you should use SYSCOPY +to update the system tracks. + +Booting Disks + +When starting your system, following the hardware initialization, you +will see the Boot Loader prompt. In addition, to the ROM boot options, +you will see another line listing the Disk boot options. This line lists +the disk devices that you can choose to boot directly. + +You will notice that you do not have an option to boot a drive letter +here (like C:). This is because the operating system is not yet loaded. +When you ran SYSCOPY previously, remember that C: was assigned to IDE0:0 +which means device IDE0, slice 0. So, to boot the disk that you just +setup with SYSCOPY, you would choose option 2. You will then be prompted +for the slice on IDE0 that you want to boot. For now, just press enter +to choose slice 0. Once you are familiar with slices, you can SYSCOPY +and boot alternate slices. Here is what you would see when booting to a +disk device: + + MARK IV Boot Loader + + ROM: (M)onitor (C)P/M (Z)-System (F)orth (B)ASIC (T)-BASIC (P)LAY (U)SER ROM + Disk: (0)MD1 (1)MD0 (2)IDE0 (3)IDE1 + + Boot Selection? 2 Slice(0-9)[0]? + + Booting Disk Unit 2, Slice 0... + + Reading disk information... + Loc=D000 End=FE00 Ent=E600 Label=Unlabeled Drive + + Loading... + +Following this, you would see the normal operating system startup +messages. However, your operating system prompt will be A> and when you +look at the drive letter assignments, you should see that A: has been +assigned to the disk you selected to boot. + +If you receive the error message “Disk not bootable!â€, you have either +failed to properly run SYSCOPY on the target disk or you have selected +the wrong disk/slice. + +Note that although MD1 (RAM disk) and MD0 (ROM disk) drives are listed +in the Disk boot line, they are not “bootable†disks because they have +no system tracks on them. Attempting to boot to one of them, will fail +with a “Disk not bootable!†error message and return to the loader +prompt. + +Operating Systems + +One of the primary goals of RomWBW is to expose a set of generic +hardware functions that make it easy to adapt operating systems to any +hardware supported by RomWBW. As a result, there are now 5 operating +systems that have been adapted to run under RomWBW. The adaptations are +identical for all hardware supported by RomWBW because RomWBW hides all +hardware specifics from the operating system. + +Note that all of the operating systems included with RomWBW support the +same basic filesystem format. As a result, a formatted filesystem will +be accessible to any operating system. The only possible issue is that +if you turn on date/time stamping using the newer OSes, the older OSes +will not understand this. Files will not be corrupted, but the date/time +stamps may be lost. + +The following sections briefly describe the operating system options +currently available. + +Digital Research CP/M 2.2 + +This is the most widely used variant of the Digital Research operating +system. It has the most basic feature set, but is essentially the +compatibility metric for all other CP/M-like operating systems including +all of those listed below. The Doc directory contains a manual for CP/M +usage (“CPM Manual.pdfâ€). If you are new to the CP/M world, I would +recommend using this CP/M variant to start with simply because it is the +most stable and you are less likely to encounter problems. + +Notes + +- The original versions of DDT, DDTZ, and ZSID used the RST 38 vector + which conflicts with interrupt mode 1 use of this vector. The DDT, + DDTZ, and ZSID applications in RomWBW have been modified to use RTS + 30 to avoid this issue. + +- Z-System applications will not run under CP/M 2.2. For example, the + LDDS date stamper with not run. + +ZSDOS 1.1 + +ZSDOS is the most popular non-DRI CP/M “clone†which is generally +referred to as Z-System. Z-System is intended to be an enhanced version +of CP/M and should run all CP/M 2.2 applications. It is optimized for +the Z80 CPU (as opposed to 8080 for CP/M) and has some significant +improvements such as date/time stamping of files. For further +information on the RomWBW implementation of Z-System, see the wiki page +Z-System Notes. Additionally, the official documentation for Z-System is +included in the RomWBW distribution Doc directory (“ZSDOS Manual.pdf†+and “ZCPR Manual.pdfâ€). + +Notes + +- Although most CP/M 2.2 applications will run under Z-System, some + may not work as expected. The best example is PIP which is not aware + of the ZSDOS paths and will fail in some scenarios (use COPY + instead). + +NZCOM Automatic Z-System + +NZCOM is a much further refined version of Z-System (ZCPR 3.4). NZCOM +was sold as an enhancement for existing users of CP/M 2.2 or ZSDOS. For +this reason, (by design) NZCOM does not provide a way to boot directly +from disk. Rather, it is loaded after the system boots into a host OS. +On the RomWBW NZCOM disk images, the boot OS is ZSDOS 1.1. After you +configure NZCOM, you can add a PROFILE.SUB file to automatically launch +NZCOM at boot. + +NZCOM is not pre-configured. You must run through a simple configuration +process before loading it. Run MKZCM to do this. + +NZCOM has substantially more functionality than CP/M or basic Z-System. +It is important to read the the “NZCOM Users Manual.pdf†file in the +RomWBW Doc directory. + +Notes + +- There is no DIR command, you must use SDZ instead. If you don’t like + this, look into the ALIAS facility. + +Digital Research CP/M 3 + +This is the Digital Research follow-up product to their very popular +CP/M 2.2 operating system. While highly compatible with CP/M 2.2, it +features many enhancements. It makes direct use of banked memory to +increase the user program space (TPA). It also has a new suite of +support tools and help system. + +Note that to make a CP/M 3 boot disk, you actually place CPMLDR.SYS on +the system tracks of the disk. You do not place CPM3.SYS on the system +tracks. CPMLDR.SYS chain loads CPM3.SYS. + +Notes + +- The DATE command cannot yet be used to set the RTC. The RTC is used + to read the current date/time for file stamping, etc. You can use + the RTC app to set the RTC clock. + +Simeon Cran’s ZPM3 + +ZPM3 is an interesting combination of the features of both CP/M 3 and +ZCPR 3. Essentially, it has the features of and compatibility with both. + +Like CP/M 3, to make ZPM3 boot disk, you put CPMLDR.SYS on the system +tracks of the disk. + +Notes + +- ZPMLDR is included with ZPM3, but it is not working correctly. + +- The ZPM operating system is contained in the file called CPM3.SYS + which is confusing, but it is the author’s intended way of using + ZPM3. + +FreeRTOS + +Phillip Stevens has ported FreeRTOS to run under RomWBW. FreeRTOS is not +provided in the RomWBW distribution. FreeRTOS is available under the MIT +licence and further general information is available at FreeRTOS. + +You can also contact Phillip for detailed information on the Z180 +implementation of FreeRTOS for RomWBW. feilipu + +Transferring Files + +Transferring files between your modern computer and your RomWBW system +can be achieved in a variety of ways. The most common of these are +described below. All of these have a certain degree of complexity and I +encourage new users to use the available community forums to seek +assistance as needed. + +Serial Port Transfers + +RomWBW provides an serial file transfer program called XModem that has +been adapted to run under RomWBW hardware. The program is called XM and +is on your ROM disk as well as all of the pre-built disk images. + +You can type XM by itself to get usage information. In general, you will +run XM with parameters to indicate you want to send or receive a file on +your RomWBW system. Then, you will use your modern computers terminal +program to complete the process. + +The XM application generally tries to detect the hardware you are using +and adapt to it. However, you must ensure that you have a reliable +serial connection. You must also ensure that the speed of the connection +is not too fast for XModem to service. Alternatively, you can ensure +that hardware flow control is working properly. + +There is an odd interaction between XModem and partner terminal programs +that can occur. Essentially, after launching XM, you must start the +protocol on your modern computer fairly quickly (usually in about 20 +seconds or so). So, if you do not pick a file on your modern computer +quickly enough, you will find that the transfer completes about 16K, +then hangs. The interaction that causes this is beyond the scope of this +document. + +Disk Image Transfers + +It is possible to pass disk images between your RomWBW system and your +modern computer. This assumes you have an appropriate media slot on your +modern computer for the media you want to use (CF Card, SD Card, or +floppy drive). + +The general process to get files from your modern computer to a RomWBW +computer is: + +1. Use cpmtools on your modern computer to create a RomWBW CP/M + filesystem image. + +2. Insert your RomWBW media (CF Card, SD Card, or floppy disk) in your + modern computer. + +3. Use a disk imaging tool to copy the RomWBW filesystem image onto the + media. + +4. Move the media back to the RomWBW computer. + +This process is a little complicated, but it has the benefit of allowing +you to get a lot of files over to your RomWBW system quickly and with +little chance of corruption. + +The process can be run in reverse to get files from your RomWBW computer +to a modern computer. + +The exact use of these tools is a bit too much for this document, but +the tools are all included in the RomWBW distribution along with usage +documents. + +Note that the build scripts for RomWBW create the default disk images +supplied with RomWBW. It is relatively easy to customize the contents of +the disk images that are part of RomWBW. This is described in more +detail in the Source\Images directory of the distribution. + +FAT Filesystem Transfers + +RomWBW provides a mechanism that allows it to read and write files on a +FAT formatted disk. This means that you can generally use your modern +computer to make an SD Card or CF Card with a standard FAT32 filesystem +on it, then place that media in your RomWBW computer and access the +files. + +When formatting the media on your modern computer, be sure to pick the +FAT filesystem. NTFS and other filesystems will not work. + +On your RomWBW computer you can use the FAT application to access the +FAT media. The FAT application allows you to read files, write files, +list a directory, and erase files on the FAT media. It can handle +subdirectories as well. It will only see 8.3 character filenames +however. Longer filenames will show up as a truncated version. + +The FAT application is not on your ROM disk because it is too large to +fit. You will find it on all of the pre-built disk images as well as in +the Binary\Apps directory of the distribution. + +For advanced users, it is possible to create a hybrid disk that contains +CP/M slices at the beginning and a FAT filesystem after. Such a hybrid +disk can be used to boot an operating system and still have access to +FAT files on the FAT portion of the disk. David Reese has prepared a +document describing how to do this. It is called +“SC126_How-To_No_2_Preparing_an_SD_Card_for_Use_with_SC126_Rev_1-5.pdf†+and can be found in the Doc\Contrib directory of the distribution. + +Startup Command Processing + +Each of the operating systems supported by RomWBW provide a mechanism to +run commands at boot. This is similar to the AUTOEXEC.BAT files from +MS-DOS. + +With the exception of ZPM3, all operating systems will look for a file +called PROFILE.SUB on the system drive at boot. If it is found, it will +be processed as a standard CP/M submit file. You can read about the use +of the SUBMIT facility in the CP/M manuals included in the RomWBW +distribution. Note that the boot disk must also have a copy of +SUBMIT.EXE. + +In the case of ZPM3, the file called STARTZPM.COM will be run at boot. +To customize this file, you use the ZCPR ALIAS facility. You will need +to refer to ZCPR documentation for more information on the ALIAS +facility. + +Note that the automatic startup processing generally requires booting to +a disk drive. Since the ROM disk is not writable, there is no simple way +to add/edit a PROFILE.SUB file there. If you want to customize your ROM +and add a PROFILE.SUB file to the ROM Disk, it will work, but is a lot +harder than using a boot disk. ROM Customization ------------------ The pre-built ROM images are configured for the basic capabilities of -each platform. If you add board(s) to your system, you will need to -customize your ROM image to include support for the added board(s). +each platform. Additionally, some of the typical add-on hardware for +each platform will be automatically detected and used. If you want to go +beyond this, RomWBW provides a very flexible configuration mechanism +based on configuration files. Creating a customized ROM requires running +a build script, but it is quite easy to do. -Essentially, the creation of a custom ROM is accomplished by updating -a small configuration file, then running a script to compile the -software and generate the custom ROM image. At this time, the build -process runs on Windows 32 or 64 bit versions. All tools (compilers, -assemblers, etc.) are included in the distribution, so it is not -necessary to setup a build environment on your computer. +Essentially, the creation of a custom ROM is accomplished by updating a +small configuration file, then running a script to compile the software +and generate the custom ROM and disk images. There are build scripts for +Windows, Linux, and MacOS to accommodate virtually all users. All +required build tools (compilers, assemblers, etc.) are included in the +distribution, so it is not necessary to setup a build environment on +your computer. -For those who are interested in more than basic system customization, -note that all source code is included (including the operating -systems). +The process for building a custom ROM is documented in the ReadMe.txt +file in the Source directory of the distribution. -Note that the ROM customization process does not apply to UNA. All -UNA customization is performed within the ROM setup script. +For those who are interested in more than basic system customization, +note that all source code is provided (including the operating systems). +Modification of the source code is considered an expert level task and +is left to the reader to pursue. -Complete documentation of the customization process is found in the -ReadMe.txt file in the Source directory. +Note that the ROM customization process does not apply to UNA. All UNA +customization is performed within the ROM setup script. -Inbuild ROM Applications ------------------------- +UNA Hardware BIOS -Additonal software other than the CP/M and Z-System application can -be included in the ROM image for execution from the ROM loader. +John Coffman has produced a new generation of hardware BIOS called UNA. +The standard RomWBW distribution includes it’s own hardware BIOS. +However, RomWBW can alternatively be constructed with UNA as the +hardware BIOS portion of the ROM. If you wish to use the UNA variant of +RomWBW, then just program your ROM with the ROM image called +“UNA_std.rom†in the Binary directory. This one image is suitable on all +of the platforms and hardware UNA supports. -Current inclusions are: +UNA is customized dynamically using a ROM based setup routine and the +setup is persisted in the system NVRAM of the RTC chip. This means that +the single UNA-based ROM image can be used on most of the RetroBrew +platforms and is easily customized. UNA also supports FAT file system +access that can be used for in-situ ROM programming and loading system +images. - Monitor - Z80 debug monitor with hexload capability. - Forth - Brad Rodriguez's ANS compatible Forth. - Basic - Nascom 8K BASIC. - Tasty BASIC - Dimitri Theulings Tiny BASIC implementation. - - Note: To exit type B in Monitor and BYE in other applications. - -Space is available in the ROM image for the inclusion of other software. -Any inbuild application can be set up to launch automatically at startup. +While John is likely to enhance UNA over time, there are currently a few +things that UNA does not support: -Source Code Respository ------------------------ +- Floppy Drives +- Terminal Emulation +- Zeta 1, N8, RC2014, Easy Z80, and Dyno Systems +- Some older support boards -All source code and distributions are maintained on GitHub at -"https://github.com/wwarthen/RomWBW". Code contributions are very -welcome. +The UNA version embedded in RomWBW is the latest production release of +UNA. RomWBW will be updated with John’s upcoming UNA release with +support for VGA3 as soon as it reaches production status. + +Please refer to the UNA BIOS Firmware Page for more information on UNA. + +Upgrading + +Upgrading to a newer release of RomWBW is essentially just a matter of +updating the ROM chip in your system. If you have spare ROM chips for +your system and a ROM programmer, it is always safest to retain your +existing, working ROM chip and program a new one with the new firmware. +If the new one fails to boot, you can easily return to the known working +ROM. + +Prior to attempting to reprogram your actual ROM chip, you may wish to +“try†the upgrade. With RomWBW, you can upload a new system image +executable and load it from the command line. For each ROM image file +(.rom) in the Binary directory, you will also find a corresponding +application file (.com). For example, for SBC_std.rom, there is also an +SBC_std.com file. You can upload the .com file to your system using +XModem, then simply run the .com file. You will see your system go +through the normal startup process just like it was started from ROM. +However, your ROM has not been updated and the next time you boot your +system, it will revert to the system image contained in ROM. + +If you do not have easy access to a ROM programmer, it is usually +possible to reprogram your system ROM using the FLASH utility from Will +Sowerbutts. This application, called FLASH.COM, can be found on the ROM +drive of any running system. In this case, you would need to transfer +the new ROM image (.rom) over to your system using XModem (or one of the +other mechanisms described in the Transferring Files section). The ROM +image is too large to fit on your RAM drive, so you will need to +transfer it to a larger storage drive. Once the ROM image is on your +system, you can use the FLASH application to update your ROM. The +following is a typical example of transferring ROM image using XModem +and flashing the chip in-situ. + + E>xm r rom.img + + XMODEM v12.5 - 07/13/86 + RBC, 28-Aug-2019 [WBW], ASCI + + Receiving: E0:ROM.IMG + 7312k available for uploads + File open - ready to receive + To cancel: Ctrl-X, pause, Ctrl-X + + Thanks for the upload + + E>flash write rom.img + FLASH4 by Will Sowerbutts version 1.2.3 + + Using RomWBW (v2.6+) bank switching. + Flash memory chip ID is 0xBFB7: 39F040 + Flash memory has 128 sectors of 4096 bytes, total 512KB + Write complete: Reprogrammed 2/128 sectors. + Verify (128 sectors) complete: OK! + +Obviously, there is some risk to this approach since any issues with the +programming or ROM image could result in a non-functional system. + +To confirm your ROM chip has been successfully updated, restart your +system and boot an operating system from ROM. Do not boot from a disk +device yet. Review the boot messages to see if any issues have occurred. + +Once you are satisfied that the ROM is working well, you will need to +update the system images and RomWBW custom applications on your disk +drives. The system images and custom applications are matched to the +RomWBW ROM firmware in use. If you attempt to boot a disk or run +applications that have not been updated to match the current ROM +firmware, you are likely to have odd problems. + +The simplest way to update your disk media is to just use your modern +computer to overwrite the entire media with the latest disk image of +your choice. This process is described below in the Disk Images section. +If you wish to update existing disk media in your system, you need to +perform the following steps. + +If the disk is bootable, you need to update the system tracks of the +disk. This is done using a SYSCOPY command such as +SYSCOPY C:=B:ZSYS.SYS. For a ZSDOS boot disk, use ZSYS.SYS. For a CP/M +2.2 disk, use CPM.SYS. For a CP/M 3 or ZPM3 disk, use CPMLDR.SYS. +CPMLDR.SYS is not provided on the ROM disk, so you will need to upload +it from the distribution. + +Finally, if you have copies of any of the RomWBW custom applications on +your hard disk, you need to update them with the latest copies. The +following applications are found on your ROM disk. Use COPY to copy them +over any older versions of the app on your disk: + +- ASSIGN.COM +- SYSCOPY.COM +- MODE.COM +- FDU.COM (was FDTST.COM) +- FORMAT.COM +- XM.COM +- FLASH.COM +- FDISK80.COM +- TALK.COM +- RTC.COM +- TIMER.COM +- INTTEST.COM + +For example: B>COPY ASSIGN.COM C: + +Some RomWBW custom applications are too large to fit on the ROM disk. If +you are using any of these you will need to transfer them to your system +and then update all copies. These applications are found in the +Binary\Apps directory of the distribution and in all of the disk images. + +- FAT.COM +- TUNE.COM + +RomWBW Distribution + +All source code and distributions are maintained on GitHub. Code +contributions are very welcome. + +RomWBW GitHub Repository Distribution Directory Layout ------------------------------ -The RomWBW distribution is a compressed zip archive file organized in -a set of directories. Each of these directories has it's own -ReadMe.txt file describing the contents in detail. In summary, these -directories are: +The RomWBW distribution is a compressed zip archive file organized in a +set of directories. Each of these directories has it’s own ReadMe.txt +file describing the contents in detail. In summary, these directories +are: - Binary: The final output files of the build process are placed - here. Most importantly, are the ROM images with the - file names ending in ".rom". + -------------------------------------------------------------------------- + Application Description + ------------- ------------------------------------------------------------ + Binary The final output files of the build process are placed here. + Most importantly, are the ROM images with the file names + ending in “.româ€. - Doc: Contains various detailed documentation including the - operating systems, RomWBW architecture, etc. + Doc Contains various detailed documentation including the + operating systems, RomWBW architecture, etc. - Source: Contains the source code files used to build the software - and ROM images. - - Tools: Contains the MS Windows programs that are used by the - build process or that may be useful in setting up your - system. + Source Contains the source code files used to build the software + and ROM images. -Acknowledgements ----------------- + Tools Contains the MS Windows programs that are used by the build + process or that may be useful in setting up your system. + -------------------------------------------------------------------------- + +Acknowledgments While I have heavily modified much of the code, I want to acknowledge -that much of the work is derived or copied from the work of others in -the RetroBrew Computers project including Andrew Lynch, Dan Werner, -Max Scane, David Giles, John Coffman, and probably many others I am -not clearly aware of (let me know if I omitted someone!). +that much of the work is derived from the work of others in the +RetroBrew Computers Community including Andrew Lynch, Dan Werner, Max +Scane, David Giles, John Coffman, and probably many others I am not +clearly aware of (let me know if I omitted someone!). -I especially want to credit Douglas Goodall for contributing code, -time, testing, and advice. He created an entire suite of application -programs to enhance the use of RomWBW. However, he is looking for -someone to continue the maintenance of these applications and they -have become unusable due to changes within RomWBW. As of RomWBW 2.6, -these applications are no longer provided. +I especially want to credit Douglas Goodall for contributing code, time, +testing, and advice. He created an entire suite of application programs +to enhance the use of RomWBW. However, he is looking for someone to +continue the maintenance of these applications and they have become +unusable due to changes within RomWBW. As of RomWBW 2.6, these +applications are no longer provided. -David Giles has contributed support for the CSIO support in the SD -Card driver. +- David Giles contributed support for the CSIO support in the SD Card + driver. +- Ed Brindley contributed some of the code that supports the RC2014 + platform. +- Phil Summers contributed Forth and BASIC in ROM as well as a long + list of general code enhancements. +- Phillip Stevens contributed support for FreeRTOS. +- Curt Mayer contributed the Linux / MacOS build process. +- UNA BIOS and FDISK80 is a product of John Coffman. +- FLASH4 is a product of Will Sowerbutts. -The UNA BIOS is a product of John Coffman. +Contributions of all kinds to RomWBW are very welcome. Getting Assistance ------------------- The best way to get assistance with RomWBW or any aspect of the -RetroBrew Computers projects is via the community forum at -"https://www.retrobrewcomputers.org/forum/". +RetroBrew Computers projects is via the community forums: -Also feel free to email Wayne Warthen at wwarthen@gmail.com. +- RetroBrew Computers Forum +- RC2014 Google Group +- retro-comp Google Group -To Do ------ +Submission of issues and bugs are welcome at the RomWBW GitHub +Repository. - - Formatting Media - - Making a Disk Bootable - - Assigning disks/slices to drives - - Managing the Console +Also feel free to email Wayne Warthen at wwarthen@gmail.com. diff --git a/Readme.unix b/Readme.unix new file mode 100644 index 00000000..710487c6 --- /dev/null +++ b/Readme.unix @@ -0,0 +1,49 @@ +this tree now contains makefiles and tools to build on Linux and MacosX +Linux is rather more thoroughly tested compared to os-x. + +to get here, TASM and the propeller generation tools needed to be replaced, +and since the unix filesystem is usually case-sensitive, and CP/M and windows +are not, the cpm tools were made case-insensitive. + +TASM was replaced with uz80as, which implements a subset of TASM and fixes some +bugs. however, I needed to add some functionality to make it build the sources +as they exist in this tree. in particular, one thing to be very careful of is +that TASM is not entirely consistent with respect to the .DS directive. +it's usually a bad idea to mix .DS, .FILL, .DB with .ORG. + .DS n is best thought of as .ORG $ + n + .ORG changes the memory pointer, but does not change the file output point. + it works a lot more like M80, SLR* .phase + +it assumes that you have some standard system tools and libraries installed +specifically: gcc, gnu make, libncurses + +to build: + cd to the top directory and type make. + +heavy use is made of make's include facility and pattern rules. +the master rule set is in Tools/Makefile.inc. changes here will affect +almost every Makefile, and where exceptions are needed, the overrides are +applied in the lower Makefiles. + +these tools can run a windows-linux regression test, where all the binaries are +compared to a baseline windows build. + +Credit: + + uz80as was written by Jorge Giner Cordero, jorge.giner@hotmail.com, + and the original source can be found at https://github.com/jorgicor/uz80as + + the propeller tools use bstc and openspin, parallax tools from + http://www.fnarfbargle.com/bst.html https://github.com/parallaxinc/OpenSpin + note that bst is not open source or even currently maintained, so I could + not generate a version for 64 bit osx. + + cpmtools were the most current I could find, and it has been hacked to do + case-insensitivity. these are not marked, and are not extensive. + + zx is from distributed version, and also has local hacks for case insensitivity. + both zx and cpmtools ship with an overly complicated makefile generation system + and this is ignored. + + this whole linux build framework is the work of Curt Mayer, curt@zen-room.org. + use it for whatever you like; this is not my day job. diff --git a/Source/Apps/Assign.asm b/Source/Apps/Assign.asm index adb53a13..0bbf038e 100644 --- a/Source/Apps/Assign.asm +++ b/Source/Apps/Assign.asm @@ -20,6 +20,9 @@ ; Change Log: ; 2016-03-21 [WBW] Updated for HBIOS 2.8 ; 2016-04-08 [WBW] Determine key memory addresses dynamically +; 2019-08-07 [WBW] Fixed DPB selection error +; 2019-11-17 [WBW] Added preliminary CP/M 3 support +; 2019-12-24 [WBW] Fixed location of BIOS save area ;_______________________________________________________________________________ ; ; ToDo: @@ -38,14 +41,27 @@ bdos .equ $0005 ; BDOS invocation vector ; stamp .equ $40 ; loc of RomWBW CBIOS zero page stamp ; -rmj .equ 2 ; CBIOS version - major -rmn .equ 9 ; CBIOS version - minor +rmj .equ 3 ; CBIOS version - major +rmn .equ 1 ; CBIOS version - minor ; ;=============================================================================== ; Code Section ;=============================================================================== ; .org $100 +; + ; relocate to high memory + ld hl,image + ld de,$8000 + ld bc,modsize + ldir + jp start +; +image .equ $ +; + .org $8000 +; +start: ; ; setup stack (save old value) ld (stksav),sp ; save stack @@ -83,6 +99,13 @@ init: ld de,-3 ; adjustment for start of table add hl,de ; HL now has start of table ld (bioloc),hl ; save it +; + ; get CP/M version and save it + ld c,$0C ; function number + call bdos ; do it, HL := version + ld (cpmver),hl ; save it + ld a,l ; low byte + cp $30 ; CP/M 3.0? ; ; get location of config data and verify integrity ld hl,stamp ; HL := adr or RomWBW zero page stamp @@ -122,6 +145,11 @@ init: inc hl ; ... into DE to get ld d,(hl) ; ... DPB map pointer ld (dpbloc),de ; and save it +; + ; test for CP/M 3 and branch if so + ld a,(cpmver) ; low byte of cpm version + cp $30 ; CP/M 3.0? + jp nc,initcpm3 ; handle CP/M 3.0 or greater ; ; make a local working copy of the drive map ld hl,(maploc) ; copy from CBIOS drive map @@ -192,6 +220,66 @@ initx: xor a ; signal success ret ; return ; +; CP/M 3 initialization +; +initcpm3: + ld hl,(bioloc) + ld de,22*3 ; offset of DRVTBL func + add hl,de ; HL := DRVTBL func + call jphl ; do it, HL := DRVTBL adr + ld (drvtbl),hl ; save it +; + ; switch to sysbnk + ld hl,(bioloc) + ld de,27*3 ; offset of SELMEM func + add hl,de ; HL := SELMEM func + ld a,0 ; bank 0 is system bank + call jphl +; + ; copy CP/M 3 drvtbl to drvmap working copy + ld hl,(drvtbl) ; get drive table in HL + ld de,mapwrk ; DE := working drive map + ld b,16 +initc2: + push hl ; save drvtbl entry adr + ld a,(hl) ; deref HL to get DPH adr + inc hl ; ... + ld h,(hl) ; ... + ld l,a ; ... + ld a,l ; check for + or h ; ... zero + jr nz,initc3 ; if not zero, copy entry + inc de ; ... else bump past unit field + jr initc4 ; ... and continue without copying +initc3: + dec hl ; back up to + dec hl ; ... unit + ld a,(hl) ; get unit from drvtbl + ld (de),a ; save unit to drvmap + inc hl ; bump to slice + inc de ; bump to slice + ld a,(hl) ; get slice from drvtbl + ld (de),a ; save slice to drvmap +initc4: + inc de ; bump past slice + inc de ; skip + inc de ; ... dph + pop hl ; back to drvtbl entry + inc hl ; bump to + inc hl ; ... next drvtbl entry + djnz initc2 +; + ; switch back to tpabnk + ld hl,(bioloc) + ld de,27*3 ; offset of SELMEM func + add hl,de ; HL := SELMEM func + ld a,1 ; bank 1 is tpa bank + call jphl +; + ; return success + xor a ; signal success + ret ; return +; ; Process command line ; process: @@ -373,9 +461,13 @@ devlstu1: ; Install the new drive map into CBIOS ; install: + ld a,(cpmver) ; low byte of CP/M version + cp $30 ; CP/M 3.0? + jp nc,instcpm3 ; handle CP/M 3.0 or greater +; ; capture CBIOS snapshot and stack frame for error recovery ld hl,(bioloc) ; start of CBIOS - ld de,$8000 ; save it here + ld de,$1000 ; save it here ld bc,(biosiz) ; size of CBIOS ldir ; save it ld (xstksav),sp ; save stack frame @@ -529,15 +621,21 @@ makdphuna1: ; handle ram/rom ld e,2 ; otherwise, must be ram drive jr makdph0 ; continue ; -makdphwbw: ; determine appropriate dpb (WBW mode) +makdphwbw: ; determine appropriate dpb (WBW mode, unit number in A) ; + ld c,a ; unit number to C + ld b,$17 ; HBIOS: Report Device Info + rst 08 ; call HBIOS, return w/ device type in D, physical unit in E + ld a,d ; device type to A + cp $00 ; ram/rom? + jr nz,makdph00 ; if not, skip ahead to other types + ld a,e ; physical unit number to A ld e,1 ; assume rom - cp $00+0 ; rom? - jr z,makdph0 ; yes, jump ahead - ld e,2 ; assume ram - cp $00+1 ; ram? + cp $00 ; rom? jr z,makdph0 ; yes, jump ahead - and $F0 ; ignore unit nibble now + ld e,2 ; otherwise ram + jr makdph0 ; jump ahead +makdph00: ld e,6 ; assume floppy cp $10 ; floppy? jr z,makdph0 ; yes, jump ahead @@ -580,27 +678,7 @@ makdph1: dec de ; ... prefix data (cks & als buf sizes) call makdph2 ; handle cks buf, then fall thru for als buf ret nz ; bail out on error - -;makdph2: -; ex de,hl ; point hl to cks/als size adr -; ld c,(hl) ; bc := cks/als size -; inc hl ; ... and bump -; ld b,(hl) ; ... past -; inc hl ; ... cks/als size -; ex de,hl ; bc and hl roles restored -; ld a,b ; check to see -; or c ; ... if bc is zero -; jr z,makdph3 ; if zero, bypass alloc, use zero for address -; call alloc ; alloc bc bytes, address returned in bc -; jp nz,instovf ; handle overflow error -;makdph3: -; ld (hl),c ; save cks/als buf -; inc hl ; ... address in -; ld (hl),b ; ... dph and bump -; inc hl ; ... to next dph entry -; xor a ; signal success -; ret - +; makdph2: ; DE = address of CKS or ALS buf to allocate ; HL = address of field in DPH to get allocated address @@ -638,12 +716,96 @@ makdph3: xor a ; signal success ret ; +; +; +instcpm3: +; + ; switch to sysbnk + ld hl,(bioloc) + ld de,27*3 ; offset of SELMEM func + add hl,de ; HL := SELMEM func + ld a,0 ; bank 0 is system bank + call jphl +; + ; copy drvmap working copy to CP/M 3 drvtbl + ld hl,(drvtbl) ; get drvtbl address + ld a,(hl) ; deref HL to get DPH0 adr + inc hl ; ... + ld h,(hl) ; ... + ld l,a ; ... + ld (dphadr),hl ; save starting dphadr + + + ld hl,(drvtbl) ; get drive table in HL + ld de,mapwrk ; DE := working drive map + ld b,16 +instc1: + ld a,(de) ; get unit field of mapwrk + inc a ; test for $FF + jr nz,instc2 ; if used, do copy + xor a ; zero accum + ld (hl),a ; zero lsb of drvtbl entry adr + inc hl ; move to msb + ld (hl),a ; zero msb of drvtbl entry adr + inc hl ; bump to start of next drvtbl entry + inc de ; bump to next mapwrk entry + inc de ; ... + inc de ; ... + inc de ; ... + jr instc3 ; resume loop without copy +; +instc2: + push hl ; save drvtbl entry adr + push de ; save mapwrk entry adr + ld de,(dphadr) ; get cur dph adr + ld (hl),e ; save dph adr to drvtbl + inc hl ; ... + ld (hl),d ; ... + ex de,hl ; dph adr to HL + pop de ; restore mapwrk entry adr + dec hl ; backup to unit + dec hl ; ... + ld a,(de) ; get unit from mapwrk + ld (hl),a ; put unit into DPH field + inc de ; bump to slice field of mapwrk + inc hl ; bump to slice field of DPH field + ld a,(de) ; get slice from mapwrk + ld (hl),a ; put slice into DPH field + inc de ; bump to next mapwrk entry + inc de ; ... + inc de ; ... + pop hl ; back to drvtbl entry + inc hl ; bump to + inc hl ; ... next drvtbl entry +instc3: + push hl ; save drvtbl entry adr + push de ; save mapwrk entry adr + ld hl,(dphadr) ; get cur dph address + ld de,$23 ; size of xdph + add hl,de ; bump to next dph + ld (dphadr),hl ; save it + pop de ; recover mapwrk entry adr + pop hl ; recover drvtbl entry adr + djnz instc1 +; + ; switch back to tpabnk + ld hl,(bioloc) + ld de,27*3 ; offset of SELMEM func + add hl,de ; HL := SELMEM func + ld a,1 ; bank 1 is tpa bank + call jphl +; + call drvrst ; perform BDOS drive reset +; + xor a ; signal success + ret +; ; Handle overflow error in installation ; instovf: ; restore stack frame and CBIOS image ld sp,(xstksav) ; restore stack frame - ld hl,$8000 ; start of CBIOS image buffer + ld hl,$1000 ; start of CBIOS image buffer ld de,(bioloc) ; start of CBIOS ld bc,(biosiz) ; size of CBIOS ldir ; restore it @@ -1636,6 +1798,9 @@ bioend .dw 0 ; CBIOS ending address biosiz .dw 0 ; CBIOS size (in bytes) maploc .dw 0 ; location of CBIOS drive map table dpbloc .dw 0 ; location of CBIOS DPB map table +cpmver .dw 0 ; CP/M version +drvtbl .dw 0 ; CP/M 3 drive table address +dphadr .dw 0 ; CP/M 3 working value for DPH ; drives: dstdrv .db 0 ; destination drive @@ -1701,10 +1866,10 @@ stack .equ $ ; stack top ; Messages ; indent .db " ",0 -msgban1 .db "ASSIGN v1.0c for RomWBW CP/M 2.2, 21-Apr-2016",0 +msgban1 .db "ASSIGN v1.1a for RomWBW CP/M, 24-Dec-2019",0 msghb .db " (HBIOS Mode)",0 msgub .db " (UBIOS Mode)",0 -msgban2 .db "Copyright 2016, Wayne Warthen, GNU GPL v3",0 +msgban2 .db "Copyright 2019, Wayne Warthen, GNU GPL v3",0 msguse .db "Usage: ASSIGN D:[=[{D:|[]:[]}]][,...]",13,10 .db " ex. ASSIGN (display all active assignments)",13,10 .db " ASSIGN /? (display version and usage)",13,10 @@ -1728,5 +1893,7 @@ msgint .db "Multiple drive letters reference one filesystem, aborting!",0 msgnoa .db "Drive A: is unassigned, aborting!",0 msgdos .db "DOS error, return code=0x",0 msgmem .db " Disk Buffer Bytes Free",0 +; +modsize .equ $ - start ; .end diff --git a/Source/Apps/Build.cmd b/Source/Apps/Build.cmd index 67bffb0a..794aee0d 100644 --- a/Source/Apps/Build.cmd +++ b/Source/Apps/Build.cmd @@ -16,7 +16,6 @@ call :asm SysCopy || goto :eof call :asm Assign || goto :eof call :asm Format || goto :eof call :asm Talk || goto :eof -call :asm OSLdr || goto :eof call :asm Mode || goto :eof call :asm RTC || goto :eof call :asm Timer || goto :eof @@ -30,6 +29,7 @@ zx MLOAD25 -SURVEY.COM=SURVEY.HEX setlocal & cd XM && call Build || exit /b 1 & endlocal setlocal & cd FDU && call Build || exit /b 1 & endlocal setlocal & cd Tune && call Build || exit /b 1 & endlocal +setlocal & cd FAT && call Build || exit /b 1 & endlocal copy *.com %APPBIN%\ diff --git a/Source/Apps/Clean.cmd b/Source/Apps/Clean.cmd index 66bb9407..70b6dcee 100644 --- a/Source/Apps/Clean.cmd +++ b/Source/Apps/Clean.cmd @@ -10,3 +10,4 @@ if exist *.prn del *.prn setlocal & cd XM && call Clean || exit /b 1 & endlocal setlocal & cd FDU && call Clean || exit /b 1 & endlocal setlocal & cd Tune && call Clean || exit /b 1 & endlocal +setlocal & cd FAT && call Clean || exit /b 1 & endlocal diff --git a/Source/Apps/FAT/Build.cmd b/Source/Apps/FAT/Build.cmd new file mode 100644 index 00000000..34e25577 --- /dev/null +++ b/Source/Apps/FAT/Build.cmd @@ -0,0 +1,7 @@ +@echo off +setlocal + +REM FAT.com is currently distributed as a binary application, so +REM it is not built here. + +copy /Y FAT.com ..\..\..\Binary\Apps\ diff --git a/Source/Apps/FAT/Clean.cmd b/Source/Apps/FAT/Clean.cmd new file mode 100644 index 00000000..01cc66c3 --- /dev/null +++ b/Source/Apps/FAT/Clean.cmd @@ -0,0 +1,5 @@ +@echo off +setlocal + +REM FAT.com is currently distributed as a binary application, so +REM we do not delete the .COM file. \ No newline at end of file diff --git a/Source/Apps/FAT/FAT.com b/Source/Apps/FAT/FAT.com new file mode 100644 index 00000000..1dd7dcca Binary files /dev/null and b/Source/Apps/FAT/FAT.com differ diff --git a/Source/Apps/FAT/LICENSE.txt b/Source/Apps/FAT/LICENSE.txt new file mode 100644 index 00000000..818433ec --- /dev/null +++ b/Source/Apps/FAT/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Source/Apps/FAT/Makefile b/Source/Apps/FAT/Makefile new file mode 100644 index 00000000..245385f2 --- /dev/null +++ b/Source/Apps/FAT/Makefile @@ -0,0 +1,6 @@ +OBJECTS = FAT.com +NODELETE = FAT.com +DEST = ../../../Binary/Apps + +TOOLS=../../../Tools +include $(TOOLS)/Makefile.inc diff --git a/Source/Apps/FAT/ReadMe.txt b/Source/Apps/FAT/ReadMe.txt new file mode 100644 index 00000000..ad06db72 --- /dev/null +++ b/Source/Apps/FAT/ReadMe.txt @@ -0,0 +1,101 @@ +RomWBW HBIOS CP/M FAT Utility ("FAT.COM") + +Author: Wayne Warthen +Updated: 11-Oct-2019 + +Application to manipulate and exchange files with a FAT (DOS) +filesystem. Runs on any HBIOS hosted CP/M implementation. + +USAGE: + FAT DIR + FAT COPY + FAT REN + FAT DEL [|] + FAT MD + FAT FORMAT + + CP/M filespec: :FILENAME.EXT ( is CP/M drive letter A-P) + FAT filespec: :/DIR/FILENAME.EXT ( is disk unit #) + +LICENSE: + GNU GPLv3 (see file LICENSE.txt) + +NOTES: + - Partitioned or non-partitioned media is handled automatically. + A floppy drive is a good example of a non-partitioned FAT + filesystem and will be recognized. Larger media will typically + have a partition table which will be recognized by the + application to find the FAT filesystem. + + - Although RomWBW-style CP/M media does not know anything about + partition tables, it is entirely possible to have media that + has both CP/M and FAT file systems on it. This is accomplished + by creating a FAT filesystem on the media that starts on a track + beyond the last track used by CP/M. Each CP/M slice on a + media will occupy a little over 8MB. So, make sure to start + your FAT partition beyond (slice count) * 8MB. + + - The application infers whether you are attempting to reference + a FAT or CP/M filesystem via the drive specifier (char before ':'). + A numeric drive character specifies the HBIOS disk unit number + for FAT access. An alpha (A-P) character indicates a CP/M + file system access targeting the specified drive letter. If there + is no drive character specified, the current CP/M filesystem and + current CP/M drive is assumed. For example: + + "2:README.TXT" refers to FAT file README.TXT on disk unit #2 + "C:README.TXT" refers to CP/M file README.TXT on CP/M drive C + "README.TXT" refers to CP/M file README.TXT on current CP/M drive + + - Files with SYS, HIDDEN, or R/O only attributes are not given + any special treatment. Such files are found and processed + like any other file. However, any attempt to write to a + read-only file will fail and the application will abort. + + - It is not currently possible to reference CP/M user areas other + than the current user. To copy files to alternate user areas, + you must switch to the desired user number first or use an + additional step to copy the file to the desired user area. + + - Accessing FAT filesystems on a floppy requires the use of + RomWBW HBIOS v2.9.1-pre.13 or greater. + + - Files written are not verified. + + - Wildcard matching in FAT filesystems is a bit unusual as + implemented by FatFs. See FatFs documentation. + +BUILD NOTES: + - Source is maintained on GitHub at https://github.com/wwarthen/FAT + + - Application is based on FatFs. FatFs source is included. + + - SDCC compiler is required to build (v3.9.0 known working). + + - ZX CP/M emulator is required to build (from RomWBW distribution). + + - See Build.cmd for sample build script under Windows. References + to SDCC and ZX must be updated for your environment. + + - Note that ff.c (core FatFs code) generates quite a few compiler + warnings (all appear to be benign). + +TO DO: + - Allow ^C to abort any operation in progress. + + - Handle wildcards in destination, e.g.: + "FAT REN 2:/*.TXT 2:/*.BAK" + + - Do something intelligent with R/O and SYS files on FAT + +HISTORY: + 2-May-2019: v0.9 (beta) initial release + 7-May-2019: v0.9.1 (beta) added REN and DEL + 8-May-2019: v0.9.2 (beta) handle file collisions w/ user prompt + 8-Oct-2019: v0.9.3 (beta) fixed incorrect filename buffer size (MAX_FN) + 10-Oct-2019: v0.9.4 (beta) upgraded to FatFs R0.13c + 10-Oct-2019: v0.9.5 (beta) added MD (make directory) + 10-Oct-2019: v0.9.6 (beta) added FORMAT + 11-Oct-2019: v0.9.7 (beta) fix FORMAT to use existing partition table entries + add attributes to directory listing + diff --git a/Source/Apps/FDU/FDU.asm b/Source/Apps/FDU/FDU.asm index 2fda22db..da176f06 100644 --- a/Source/Apps/FDU/FDU.asm +++ b/Source/Apps/FDU/FDU.asm @@ -44,6 +44,7 @@ ; 2018-09-05: v5.3 ADDED SUPPORT FOR SMALLZ80 ; - USE EOT=R TO END R/W AFTER ONE SECTOR INSTEAD ; OF USING PULSE TC +; 2020-01-05: V5.4 ADDED SUPPORT FOR DYNO FDC ; ;_______________________________________________________________________________ ; @@ -77,6 +78,7 @@ FDC_N8 .EQU 5 FDC_RCSMC .EQU 6 FDC_RCWDC .EQU 7 FDC_SMZ80 .EQU 8 +FDC_DYNO .EQU 9 ; ; FDC MODE ; @@ -211,8 +213,8 @@ INIT5: XOR A RET -STR_BANNER .DB "Floppy Disk Utility (FDU) v5.3, 28-Sep-2018$" -STR_BANNER2 .DB "Copyright (C) 2018, Wayne Warthen, GNU GPL v3","$" +STR_BANNER .DB "Floppy Disk Utility (FDU) v5.4, 05-jAN-2020$" +STR_BANNER2 .DB "Copyright (C) 2020, Wayne Warthen, GNU GPL v3","$" STR_HBIOS .DB " [HBIOS]$" STR_UBIOS .DB " [UBIOS]$" ; @@ -232,19 +234,18 @@ FDCSEL: CALL WRITESTR ; FDCSEL1: - CALL GETKEY - SUB '0' ; ASCII -> BINARY - CP FDCCNT + 1 ; TOO HIGH? - JR NC,FDCSEL1 ; IF SO, TRY AGAIN -; - OR A ; SET FLAGS - JR NZ,FDCSEL2 ; NOT ZERO, KEEP GOING - OR 0FFH ; SET NZ FOR EXIT REQUEST + CALL GETKEYUC + CP 'X' ; EXIT? + JR NZ,FDCSEL2 ; IF NOT, CONTINUE + OR 0FFH ; ELSE SET NZ FOR EXIT REQUEST RET ; AND RETURN -; +; FDCSEL2: + SUB 'A' ; ASCII -> BINARY + CP FDCCNT ; TOO HIGH? + JR NC,FDCSEL1 ; IF SO, TRY AGAIN +; ; SAVE SELECTED FDC IDENTIFIER - DEC A ; CONVERT TO ZERO-BASED FDC ID LD (FDCID),A ; RECORD THE FDC ID RLCA ; TIMES 4 RLCA ; ... FOR 4 BYTE ENTRIES @@ -282,6 +283,7 @@ FDCTBL: ; LABEL CONFIG DATA .DW STR_RCSMC, CFG_RCSMC .DW STR_RCWDC, CFG_RCWDC .DW STR_SMZ80, CFG_SMZ80 + .DW STR_DYNO, CFG_DYNO FDCCNT .EQU ($-FDCTBL)/4 ; FD CONTROLLER COUNT ; ; FDC LABEL STRINGS @@ -295,6 +297,7 @@ STR_N8 .TEXT "N8$" STR_RCSMC .TEXT "RC-SMC$" STR_RCWDC .TEXT "RC-WDC$" STR_SMZ80 .TEXT "SMZ80$" +STR_DYNO .TEXT "DYNO$" ; ; FDC CONFIGURATION BLOCKS ; @@ -405,6 +408,17 @@ CFG_SMZ80: .DB 0FFH ; PSEUDO DMA DATA PORT .DB _PCAT ; MODE= ; +CFG_DYNO: + .DB 084H ; FDC MAIN STATUS REGISTER + .DB 085H ; FDC DATA PORT + .DB 0FFH ; DATA INPUT REGISTER + .DB 086H ; DIGITAL OUTPUT REGISTER (LATCH) + .DB 087H ; DCR + .DB 0FFH ; DACK + .DB 086H ; TERMINAL COUNT (W/ DACK) + .DB 0FFH ; PSEUDO DMA DATA PORT + .DB _PCAT ; MODE= +; FDCID .DB 0 ; FDC IDENTIFIER (0 INDEXED) FDCBM .DB 0 ; FDC ID BITMAP FDCLBL .DW 0 ; POINTER TO ACTIVE FDC LABEL STRING @@ -413,16 +427,17 @@ FDCCFG .DW 0 ; POINTER TO ACTIVE CFG DATA FSS_MENU: .TEXT "\r\n" .TEXT "SELECT FLOPPY DISK CONTROLLER:\r\n" - .TEXT " (0) Exit\r\n" - .TEXT " (1) Disk IO ECB Board\r\n" - .TEXT " (2) Disk IO 3 ECB Board\r\n" - .TEXT " (3) Zeta SBC Onboard FDC\r\n" - .TEXT " (4) Zeta 2 SBC Onboard FDC\r\n" - .TEXT " (5) Dual IDE ECB Board\r\n" - .TEXT " (6) N8 Onboard FDC\r\n" - .TEXT " (7) RC2014 SMC (SMB)\r\n" - .TEXT " (8) RC2014 WDC (SMB)\r\n" - .TEXT " (9) SmallZ80 Expansion\r\n" + .TEXT " (A) Disk IO ECB Board\r\n" + .TEXT " (B) Disk IO 3 ECB Board\r\n" + .TEXT " (C) Zeta SBC Onboard FDC\r\n" + .TEXT " (D) Zeta 2 SBC Onboard FDC\r\n" + .TEXT " (E) Dual IDE ECB Board\r\n" + .TEXT " (F) N8 Onboard FDC\r\n" + .TEXT " (G) RC2014 SMC (SMB)\r\n" + .TEXT " (H) RC2014 WDC (SMB)\r\n" + .TEXT " (I) SmallZ80 Expansion\r\n" + .TEXT " (J) Dyno-Card FDC, D1030\r\n" + .TEXT " (X) Exit\r\n" .TEXT "=== OPTION ===> $\r\n" ; ;=============================================================================== @@ -1499,8 +1514,9 @@ MD_MAP: .DB %00000001 ; DIDE POLL .DB %00000001 ; N8 POLL .DB %00000001 ; RCSMC POLL -; .DB %00000001 ; RCWDC POLL + .DB %00000001 ; RCWDC POLL .DB %00000001 ; SMZ80 POLL + .DB %00000001 ; DYNO POLL ; ; MEDIA DESCRIPTION BLOCK ; @@ -1861,7 +1877,7 @@ FM_DRAW0B: ; ZETA, DIO3 LD A,(FST_DOR) AND 00000010B JR FM_DRAW1 -FM_DRAW0C: ; DIDE, N8, ZETA2, RCWDC, SMZ80 +FM_DRAW0C: ; DIDE, N8, ZETA2, RCWDC, SMZ80, DYNO LD A,(FST_DOR) AND 11110000B JR FM_DRAW1 @@ -2014,7 +2030,7 @@ FM_MOTOR0B: ; ZETA, DIO3 LD A,(FST_DOR) AND 00000010B JR FM_MOTOR1 -FM_MOTOR0C: ; DIDE, N8, ZETA2, RCWDC, SMZ80 +FM_MOTOR0C: ; DIDE, N8, ZETA2, RCWDC, SMZ80, DYNO LD A,(FST_DOR) AND 11110000B JR FM_MOTOR1 @@ -2152,7 +2168,7 @@ FSST_COUNT .EQU (($ - FSST) / FSST_ENTSIZ) ; # ENTRIES IN TABLE ; FCP_CMD .DB 000H ; INPUT: COMMAND CODE FCP_BUFLEN .DB 00H -FCP_BUF .DS 10H +FCP_BUF .FILL 10H FCP_BUFSIZ .EQU $-FCP_BUF FCP_XFRCNT .DW 00H ; BYTES TRANSFERRED DURING COMMAND PHASE ; @@ -2166,7 +2182,7 @@ FXP_BC .DW 00H ; LAST VALUE OF REG BC RECORDED DURING EXECUTION FXP_DE .DW 00H ; LAST VALUE OF REG DE RECORDED DURING EXECUTION FXP_HL .DW 00H ; LAST VALUE OF REG HL RECORDED DURING EXECUTION FXP_BUFLEN .DB 00H -FXP_BUF .DS 50H ; USED FOR CERTAIN EXEC ROUTINES (FORMAT TRACK) +FXP_BUF .FILL 50H ; USED FOR CERTAIN EXEC ROUTINES (FORMAT TRACK) FXP_BUFSIZ .EQU $-FXP_BUF ; ; FDC STATUS @@ -2753,7 +2769,7 @@ FC_INIT1: ; DIO FC_INIT2: ; ZETA, DIO3 LD A,(FCD_DORB) JR FC_INIT5 -FC_INIT3: ; DIDE, N8, ZETA2, RCWDC, SMZ80 +FC_INIT3: ; DIDE, N8, ZETA2, RCWDC, SMZ80, DYNO LD A,(FCD_DORC) JR FC_INIT5 FC_INIT4: ; WDSMC @@ -2797,7 +2813,7 @@ FC_RESETFDC1: ; ZETA, DIO3, RCSMC POP AF OUT (C),A JR FC_RESETFDC3 -FC_RESETFDC2: ; DIDE, N8, ZETA2, RCWDC, SMZ80 +FC_RESETFDC2: ; DIDE, N8, ZETA2, RCWDC, SMZ80, DYNO LD A,0 OUT (C),A LD A,(FST_DOR) @@ -2824,7 +2840,7 @@ FC_PULSETC: ;RES 0,A ;OUT (C),A ;JR FC_PULSETC2 -;FC_PULSETC1: ; DIDE, N8, ZETA2, RCWDC, SMZ80 +;FC_PULSETC1: ; DIDE, N8, ZETA2, RCWDC, SMZ80, DYNO ;LD C,(IY+CFG_TC) ;IN A,(C) ;JR FC_PULSETC2 @@ -2856,7 +2872,7 @@ FC_MOTORON2: ; ZETA, DIO3 LD HL,FST_DOR ; POINT TO FDC_DOR SET 1,(HL) JR FC_MOTORON5 -FC_MOTORON3: ; DIDE, N8, ZETA2, RCWDC, SMZ80 +FC_MOTORON3: ; DIDE, N8, ZETA2, RCWDC, SMZ80, DYNO LD HL,FST_DOR ; POINT TO FDC_DOR LD A,(HL) ; START WITH CURRENT DOR AND 11111100B ; GET RID OF ANY ACTIVE DS BITS @@ -2920,7 +2936,7 @@ FC_MOTOROFF2: ; ZETA, DIO3 LD HL,FST_DOR ; POINT TO FDC_DOR RES 1,(HL) JR FC_MOTOROFF5 -FC_MOTOROFF3: ; DIDE, N8, ZETA2, RCWDC, SMZ80 +FC_MOTOROFF3: ; DIDE, N8, ZETA2, RCWDC, SMZ80, DYNO LD HL,FST_DOR ; POINT TO FDC_DOR LD A,DORC_INIT LD (HL),A @@ -3330,7 +3346,8 @@ FXRR5: INC C ; [04] POINT TO DATA PORT ; ; AVOID RETURN FROM HALT IN PROBLEMATIC ADDRESS RANGE XX30-XX3F!!! .IF ((($ & 0F0H) == 20H) | (($ & 0F0H) == 30H)) - .ORG (($ & 0FF00H) + 40H) + .FILL (($ & 0FF00H) + 40H) - $ + ; .ORG (($ & 0FF00H) + 40H) .ENDIF ; IFXRR: @@ -3358,7 +3375,8 @@ IFXRRX .EQU $ - IFXRR ; ; AVOID RETURN FROM HALT IN PROBLEMATIC ADDRESS RANGE XX30-XX3F!!! .IF ((($ & 0F0H) == 20H) | (($ & 0F0H) == 30H)) - .ORG (($ & 0FF00H) + 40H) + .FILL (($ & 0FF00H) + 40H) - $ + ; .ORG (($ & 0FF00H) + 40H) .ENDIF ; FFXRR: @@ -3436,7 +3454,8 @@ FXRW5: INC C ; [04] POINT TO DATA PORT ; ; AVOID RETURN FROM HALT IN PROBLEMATIC ADDRESS RANGE XX30-XX3F!!! .IF ((($ & 0F0H) == 20H) | (($ & 0F0H) == 30H)) - .ORG (($ & 0FF00H) + 40H) + .FILL (($ & 0FF00H) + 40H) - $ + ; .ORG (($ & 0FF00H) + 40H) .ENDIF ; IFXRW: @@ -3462,7 +3481,8 @@ IFXRW2: EI ; ; AVOID RETURN FROM HALT IN PROBLEMATIC ADDRESS RANGE XX30-XX3F!!! .IF ((($ & 0F0H) == 20H) | (($ & 0F0H) == 30H)) - .ORG (($ & 0FF00H) + 40H) + .FILL (($ & 0FF00H) + 40H) - $ + ; .ORG (($ & 0FF00H) + 40H) .ENDIF ; FFXRW: @@ -3786,7 +3806,7 @@ DORB_BR500 .EQU 10100000B ; 500KBPS ; DORB_INIT .EQU DORB_BR250 ; -; *** DIDE/N8/ZETA2/RCWDC/SMZ80 *** +; *** DIDE/N8/ZETA2/RCWDC/SMZ80/DYNO *** ; DORC_INIT .EQU 00001100B ; SOFT RESET INACTIVE, DMA ENABLED ; diff --git a/Source/Apps/FDU/FDU.txt b/Source/Apps/FDU/FDU.txt index b591b7c3..d67fe1fe 100644 --- a/Source/Apps/FDU/FDU.txt +++ b/Source/Apps/FDU/FDU.txt @@ -1,9 +1,9 @@ ================================================================ Floppy Disk Utility (FDU) v5.3 for RetroBrew Computers -Disk IO / Zeta / Dual-IDE / N8 / RC2014 / SmallZ80 +Disk IO / Zeta / Dual-IDE / N8 / RC2014 / SmallZ80 / Dyno ================================================================ -Updated September 5, 2018 +Updated January 5, 2020 by Wayne Warthen (wwarthen@gmail.com) Application to test the hardware functionality of the Floppy @@ -77,6 +77,7 @@ supported: - RC2014 w/ SMC - RC2014 w/ WDC - SmallZ80 + - Dyno You must be using either a RomWBW or UBA based OS version. @@ -95,7 +96,7 @@ Finally, you will need a floppy drive connected via an appropriate cable: Disk IO - no twist in cable, drive unit 0/1 must be selected by jumper on drive -DISK IO 3, Zeta, Zeta 2, RC2014 - cable with twist, unit 0 after twist, unit 1 before twist +DISK IO 3, Zeta, Zeta 2, RC2014, Dyno - cable with twist, unit 0 after twist, unit 1 before twist DIDE, N8, Mark IV, SmallZ80 - cable with twist, unit 0 before twist, unit 1 after twist Note that FDU does not utilize your systems ROM or OS to @@ -154,6 +155,9 @@ JP2 (TC): 2-3. SmallZ80 does not have any relevant jumper settings. The hardwired I/O ranges are assumed in the code. +Dyno does not have any relevant jumper settings. The +hardwired I/O ranges are assumed in the code. + Modes of Operation ------------------ @@ -505,3 +509,6 @@ WW 9/5/2018: v5.3 condition is no longer considered an error, but a successful end of operation. - Added support for SmallZ80 + +WW 5/1/2020: v5.4 + - Added support for Dyno (based on work by Steve Garcia) diff --git a/Source/Apps/FDU/Makefile b/Source/Apps/FDU/Makefile new file mode 100644 index 00000000..ade7444a --- /dev/null +++ b/Source/Apps/FDU/Makefile @@ -0,0 +1,8 @@ +OBJECTS = FDU.com +DOCS = FDU.txt +DEST = ../../../Binary/Apps +DOCDEST = ../../../Doc +TOOLS = ../../../Tools +include $(TOOLS)/Makefile.inc + +%.com: USETASM=1 diff --git a/Source/Apps/Format.asm b/Source/Apps/Format.asm index ecb048c4..084be76f 100644 --- a/Source/Apps/Format.asm +++ b/Source/Apps/Format.asm @@ -30,8 +30,8 @@ bdos .equ $0005 ; BDOS invocation vector ;; ;stamp .equ $40 ; loc of RomWBW CBIOS zero page stamp ; -rmj .equ 2 ; CBIOS version - major -rmn .equ 9 ; CBIOS version - minor +rmj .equ 3 ; CBIOS version - major +rmn .equ 0 ; CBIOS version - minor ; ;=============================================================================== ; Code Section diff --git a/Source/Apps/IntTest.asm b/Source/Apps/IntTest.asm index 06bfa5ed..d751a700 100644 --- a/Source/Apps/IntTest.asm +++ b/Source/Apps/IntTest.asm @@ -1,4 +1,5 @@ ;=============================================================================== +; ; INTTEST - Test HBIOS interrupt API functions ; ;=============================================================================== @@ -24,11 +25,6 @@ bf_sysint .equ $FC ; INT function bf_sysintinfo .equ $00 ; INT INFO subfunction bf_sysintget .equ $10 ; INT GET subfunction bf_sysintset .equ $20 ; INT SET subfunction -; -z180_base .equ $40 ; i/o base address for internal z180 registers -z180_tcr .equ z180_base + $10 ; timer control -z180_tmdr0l .equ z180_base + $0C ; timer 0 data lo - ; ;=============================================================================== ; Code Section @@ -146,38 +142,46 @@ lstlp: ; Establish interrupt vector index to hook ; estidx: - ld a,(intmod) - ld c,0 - cp 1 - jr z,setidx - ld c,2 ; assume timer in entry 2 if im2 - cp 2 - jr z,setidx - ret ; neither im1 or im2, bail out -setidx: - ld a,c + call crlf2 + ld de,msgvec + call prtstr + call getbuf + ld hl,lnbuf+1 + ld a,(hl) ; get line length + or a ; set flags + jr z,estidx ; nothing entered + ld b,a ; set b to line length + inc hl + call hexbyt + jr c,estidx ld (vecidx),a + ld hl,veccnt ; check index against valid range + cp (hl) + jr nc,estidx ; vector index out of bounds ; ; Hook vector ; call crlf2 - ld de,msghook + ld de,msghk1 + call prtstr + ld a,(vecidx) + call prthex + ld de,msghk2 call prtstr - call crlf2 ld a,$ff ld (count),a ; set counter to max value ; ld a,(intmod) cp 1 - jr z,hkim1 + jr z,hkim cp 2 - jr z,hkim2 + jr z,hkim ret ; -; IM1 specific code +; Setup interrupt handler ; -hkim1: - ld hl,m1int ; pointer to my interrupt handler +hkim: + ld hl,int ; pointer to my interrupt handler ld b,bf_sysint ld c,bf_sysintset ; set new vector ld a,(vecidx) ; get vector idx @@ -188,45 +192,38 @@ hkim1: ei ; interrupts back on jr start ; -; IM2 specific code -; -hkim2: - ld hl,m2stub ; pointer to my interrupt stub - ld b,bf_sysint - ld c,bf_sysintset ; set new vector - ld a,(vecidx) ; get vector idx - ld e,a ; put in E - di - rst 08 ; do it - ld (chain),hl ; save the chain address - ld (engadr),de ; insert the int routing engine address - ei ; interrupts back on - jr start -; ; Wait for counter to countdown to zero ; start: + call crlf2 + ld de,msgrun ; message + call prtstr + call crlf2 ld a,(count) ld e,a call prthex ; print it ld a,13 call prtchr loop: + call getchr ; check console + cp $1B ; to exit + jr z,unhook ; if so, bail out ld a,(count) ; get current count value cp e jr z,loop + ld e,a push af call prthex ; print it ld a,13 call prtchr pop af or a ; set flags - jr z,loop1 ; done + jr z,unhook ; done jr loop ; and loop -loop1: ; ; Unhook ; +unhook: call crlf2 ld de,msgunhk call prtstr @@ -242,6 +239,35 @@ loop1: xor a ; signal success ret ; done ; +; Get a character from console, return in A +; returs zero if nothing available +; +getchr: + push bc ; save registers + push de + push hl + ld e,$FF ; read a character + ld c,$06 ; BDOS direct i/o function + call bdos ; do it + pop hl ; restore registers + pop de + pop bc + ret +; +; Get console buffer +; +getbuf: + push bc + push de + push hl + ld de,lnbuf + ld c,$0A + call bdos + pop hl + pop de + pop bc + ret +; ; Print character in A without destroying any registers ; prtchr: @@ -450,6 +476,59 @@ addhl: jphl: jp (hl) ; +; Convert hex chars to a byte value. +; Enter with HL pointing to buffer with chars to convert +; and B containing number of chars. Returns value in A. +; CF set to indicate error (overflow or invalid char). +; +hexbyt: + ld c,0 ; working value starts at zero +hexbyt1: + sla c ; rotate low nibble to high + ret c + sla c + ret c + sla c + ret c + sla c + ret c + ld a,(hl) ; load next char + call hexnibl ; convert to binary + ret c ; abort on error + or c ; combine w/ working value + ld c,a ; and put back in c + inc hl ; next position + djnz hexbyt1 ; next character + ld a,c ; ret val to a + or a ; clear carry + ret +; +; Convert hex character at (HL) to binary value +; Set CF to indicate invalid character +; +hexnibl: + ; handle '0'-'9' + cp '0' + jr c,hexnibl1 ; if < '0', out of range + cp '9'+1 + jr nc,hexnibl1 ; if > '9', out of range + and $0F ; convert to binary value + ret ; and done +hexnibl1: + ; handle 'A'-'F' + call ucase ; force upper case + cp 'A' + jr c,hexnibl2 ; if < 'A', out of range + cp 'F'+1 + jr nc,hexnibl2 ; if > 'F', out of range + and $0F ; convert to binary value + add a,9 ; + ret ; and done +hexnibl2: + ; invalid character + scf ; signal error + ret +; ;=============================================================================== ; Storage Section ;=============================================================================== @@ -457,6 +536,8 @@ jphl: intmod .db 0 ; active interrupt mode veccnt .db 0 ; count of ingterrupt vectors vecidx .db 0 ; vector index to hook +lnbuf .db 10,0 ; 10 char BDOS line input buffer + .fill 10 ; stksav .dw 0 ; stack pointer saved at start .fill stksiz,0 ; stack @@ -464,14 +545,17 @@ stack .equ $ ; stack top ; ; Messages ; -msgban .db "INTTEST v1.0, 27-Aug-2018",13,10 - .db "Copyright (C) 2018, Wayne Warthen, GNU GPL v3",0 +msgban .db "INTTEST v1.2, 15-May-2019",13,10 + .db "Copyright (C) 2019, Wayne Warthen, GNU GPL v3",0 msginfo .db "Interrupt information request...",0 msgmode .db " Active interrupt mode: ",0 msgcnt .db " Vector entries in use: ",0 msglst .db "Interrupt vector address list:",0 -msghook .db "Hooking vector...",0 +msghk1 .db "Hooking vector ",0 +msghk2 .db "...",0 msgunhk .db "Unhooking vector...",0 +msgrun .db "Interrupt countdown (press to exit)...",0 +msgvec .db "Enter vector to hook (hex): ",0 ; ;=============================================================================== ; Interrupt Handler @@ -481,39 +565,22 @@ reladr .equ $ ; relocation start adr ; .org $8000 ; code will run here ; -m1int: +int: ; count down to zero - ld a,(count) - or a - jr z,m1int1 - dec a - ld (count),a -m1int1: + ld a,(count) ; get current count value + or a ; test for zero + jr z,int1 ; if zero, leave it alone + dec a ; decrement + ld (count),a ; and save +int1: + xor a ; signal int has NOT been handled ; follow the chain... - ld hl,(chain) - jp (hl) -; -m2stub: - push hl - ld hl,m2int - jp $0000 -engadr .equ $ - 2 -; -m2int: - ; count down to zero - ld a,(count) - or a - jr z,m2int1 - dec a - ld (count),a -m2int1: - ; ack/reset z180 timer interrupt - in0 a,(z180_tcr) - in0 a,(z180_tmdr0l) - ret + ld hl,(chain) ; get chain adr + jp (hl) ; go there +; ret ; chain .dw $0000 ; chain address count .db 0 ; counter - +; hsiz .equ $ - $8000 ; size of handler to relocate .end diff --git a/Source/Apps/Makefile b/Source/Apps/Makefile new file mode 100644 index 00000000..49db6949 --- /dev/null +++ b/Source/Apps/Makefile @@ -0,0 +1,14 @@ +OBJECTS = SysGen.com Survey.com \ + SysCopy.com Assign.com Format.com Talk.com Mode.com RTC.com \ + Timer.com IntTest.com +OTHERS = *.hex *.com +SUBDIRS = XM FDU FAT Tune +DEST = ../../Binary/Apps +TOOLS =../../Tools + +include $(TOOLS)/Makefile.inc + +USETASM = 1 + +Survey.com: USETASM=0 + diff --git a/Source/Apps/Mode.asm b/Source/Apps/Mode.asm index 4358bab8..08f00ba7 100644 --- a/Source/Apps/Mode.asm +++ b/Source/Apps/Mode.asm @@ -48,8 +48,8 @@ bdos .equ $0005 ; BDOS invocation vector ; ident .equ $FFFE ; loc of RomWBW HBIOS ident ptr ; -rmj .equ 2 ; intended CBIOS version - major -rmn .equ 9 ; intended CBIOS version - minor +rmj .equ 3 ; intended CBIOS version - major +rmn .equ 1 ; intended CBIOS version - minor ; bf_cioinit .equ $04 ; HBIOS: CIOINIT function bf_cioquery .equ $05 ; HBIOS: CIOQUERY function diff --git a/Source/Apps/OSLdr.asm b/Source/Apps/OSLdr.asm deleted file mode 100644 index 4ca2973f..00000000 --- a/Source/Apps/OSLdr.asm +++ /dev/null @@ -1,1014 +0,0 @@ -;=============================================================================== -; OSLdr - Load a new OS image from filesystem on running system. -; Optionally, load a new HBIOS image at the same time. -;=============================================================================== -; -; Author: Wayne Warthen (wwarthen@gmail.com) -;_______________________________________________________________________________ -; -; Usage: -; OSLDR /F [] -; /F (force) overrides all compatibility checking -; ex. OSLDR CPM.SYS -; OSLDR CPM.SYS HBIOS.BIO -; -; is an os image file such as cpm.sys or zsys.sys -; is an optional bios image such as hbios.bio -;_______________________________________________________________________________ -; -; Operation: -; This application reads an OS image (and optionally HBIOS image) -; into TPA memory from the filesystem. It then copies the images to -; their appropriate locations and restarts the system. -; Note that the application itself is relocated to upper memory -; after starting so that it can manipulate the lower memory bank. -; -; The application uses the following memory layout: -; -; Loc Size Usage -; ----- ----- ----------------------------- -; $0400-$3FFF $3C00 OS Image (max of 15K possible) -; $4000-$BFFF $8000 HBIOS Image (32K fixed size) -; $C000-$CFFF $1000 Application (after relocation) -; -; Notes: -; 1) Drive assignments are not retained. Drive assignments are -; reset during the OS boot. -; 2) The OS boot drive is not explicitly set by this app. If a new -; HBIOS image is not loaded, the boot drive passed to the OS will -; be the same as it was at the last boot. If a new HBIOS image -; is being loaded, the boot drive will be the default imbedded in -; the HBIOS image. -; 3) It is not possible to load a new UNA BIOS. However, when the -; app is run under UNA, it can load a new OS image and optionally -; load an HBIOS image. -;_______________________________________________________________________________ -; -; Change Log: -;_______________________________________________________________________________ -; -; ToDo: -;_______________________________________________________________________________ -; -; Known Issues: -; 1) App will fail badly if OS image exceeds 15K -; 2) No attempt is made to match the BIOS image version against -; the running BIOS version. This is intended behavior and is -; to allow a different BIOS version to be tested. A failure -; could occur if the BIOS image does not conform to the -; expected structure (size, meta data location, entry point -; location, etc.) -;_______________________________________________________________________________ -; -;=============================================================================== -; Definitions -;=============================================================================== -; -stksiz .equ $40 ; we are a stack pig -; -restart .equ $0000 ; CP/M restart vector -bdos .equ $0005 ; BDOS invocation vector -; -; Memory layout (see Operation description above) -; -osimg .equ $0400 ; OS image load location (max 15K) -hbimg .equ $4000 ; HBIOS image load location (32K fixed) -runloc .equ $C000 ; running location (after relocation) -; -; Below are offsets in OS image of specific data fields -; The first 1.5K of the OS image is a header -; -hdrsiz .equ $600 ; Len of OS image header (3 sectors) -ossig .equ osimg + $580 ; Signature ($A55A) -osplt .equ osimg + $582 ; Platform ID -osver .equ osimg + $5E3 ; Version (4 bytes, maj, min, up, pat) -osloc .equ osimg + $5FA ; Intended address to load OS image -osend .equ osimg + $5FC ; Ending load address of OS image -osent .equ osimg + $5FE ; Entry point for OS image -osbin .equ osimg + hdrsiz ; Start of actual OS binary (after header) -; -; HBIOS internal info (adjust if HBIOS changes) -; -bfgbnk .equ $F3 ; HBIOS Get Bank function -bfver .equ $F1 ; HBIOS Get Version function -sigptr .equ hbimg + 3 ; HBIOS signature pointer -hbmrk .equ hbimg + $100 ; HBIOS marker -hbver .equ hbimg + $102 ; HBIOS version -hbplt .equ hbimg + $104 ; HBIOS platform -bidusr .equ hbimg + $10B ; User bank id -bidbios .equ hbimg + $10C ; BIOS bank id -pxyimg .equ hbimg + $200 ; Proxy image offset within HBIOS image -pxyloc .equ $FE00 ; Proxy run location -pxysiz .equ $0200 ; Proxy size -srcbnk .equ $FFE4 ; Address of bank copy source bank id -dstbnk .equ $FFE7 ; Address of bank copy destination bank id -curbnk .equ $FFE0 ; Address of current bank id in hbios proxy -hbxbnk .equ $FFF3 ; Bank select function entry address -hbxcpy .equ $FFF6 ; Bank copy function entry address -; -;=============================================================================== -; Code Section -;=============================================================================== -; - .org $100 ; startup org - - ; relocate ourselves to upper memory - ld hl,$0000 ; from startup location - ld de,runloc ; to running location - ld bc,$0800 ; assume we are no more that 2048 bytes - ldir ; copy ourselves - jp phase2 ; jump to new location - - .org $ + runloc ; adjust for phase 2 location -phase2: - ; setup stack (save old value) - ld (stksav),sp ; save stack - ld sp,stack ; set new stack - - ; processing... - call main ; do the real work - call crlf ; formatting - - ; return (we only get here if an error occurs) - jp 0 ; return to CP/M via reset -; -; Main routine -; -main: - call init ; initialize - ret nz ; abort on failure - - call parse ; parse command tail - ret nz ; abort on failure - -; call confirm ; confirm pending action -; ret nz ; abort on failure - - call crlf2 ; formatting - - ; Read OS image into TPA - call rdos ; do the os read - ret nz ; abort on failure - - ; If specified, read BIOS image - ld a,(newbio) ; get BIOS load flag - or a ; set flags - call nz,rdbio ; do the bios read - ret nz ; abort on failure - - call crlf ; formatting - - ; If force flag set, bypass image validitity checking - ld a,(force) ; load the flag - or a ; set flags - jr nz,main1 ; if set, bypass checks - - ; Check BIOS Image is acceptable - ld a,(newbio) ; get BIOS load flag - or a ; set flags - call nz,chkbios ; check the bios image - ret nz ; abort on failure - - ; Check OS Image is acceptable for requested operation - call chkos ; check the os image - ret nz ; abort on failure - -main1: - ; Load OS image into upper memory OS location - call ldos ; load OS - ret nz ; abort on failure - - ; If specified, load BIOS image to BIOS bank - ld a,(newbio) ; get BIOS load flag - or a ; set flags - jr z,main3 ; if not set, skip BIOS load and init - call ldbio ; load BIOS - ret nz ; abort on failure - - ; Initialize BIOS - call initbio ; initialize BIOS - -main3: - ; Launch... - ld hl,(osent) ; OS entry point - jp (hl) ; jump to OS BOOT vector -; -; Initialization -; -init: - call crlf - ld de,msgban ; point to banner - call prtstr ; display it - - ; locate cbios function table address - ld hl,(restart+1) ; load address of CP/M restart vector - ld de,-3 ; adjustment for start of table - add hl,de ; HL now has start of table - ld (cbftbl),hl ; save it - - ; save current drive no - ld c,$19 ; bdos func: get current drive - call bdos ; invoke BDOS function - inc a ; 1-based index for fcb - ld (defdrv),a ; save it - - ; check for UNA (UBIOS) - ld de,msghb ; assume HBIOS (point to HBIOS mode string) - ld a,($fffd) ; fixed location of UNA API vector - cp $c3 ; jp instruction? - jr nz,init1 ; if not, not UNA - ld hl,($fffe) ; get jp address - ld a,(hl) ; get byte at target address - cp $fd ; first byte of UNA push ix instruction - jr nz,init1 ; if not, not UNA - inc hl ; point to next byte - ld a,(hl) ; get next byte - cp $e5 ; second byte of UNA push ix instruction - jr nz,init1 ; if not, not UNA - ld hl,unamod ; point to UNA mode flag - ld (hl),$ff ; set UNA mode - ld a,6 ; UNA platform ID - ld (bioplt),a ; save it - ld de,msgub ; point to UBIOS string - -init1: - call prtstr ; print BIOS name - - ; if HBIOS active, get version number - ld a,(unamod) ; get UNA mode flag - or a ; set flags - jr nz,init2 ; skip if UNA BIOS active - ld b,bfver ; HBIOS func: get version - rst 08 ; do it - ld a,l ; platform to A - ld (bioplt),a ; save platform - ld h,e ; switch bytes - ld l,d ; ... to save as maj/min, up/pat - ld (biover),hl ; save version - ld b,bfgbnk ; HBIOS func: get current bank - rst 08 ; do it - ld a,c ; move to A - ld (tpabnk),a ; save it - -init2: - ; return success - xor a - ret -; -; Parse command tail -; -parse: - ld hl,$81 ; point to start of command tail (after length byte) - call nonblank ; locate start of parms - jp z,erruse ; no parms - call options ; process options - ret nz ; abort if error - ld de,osfcb ; point to os image fcb - call convert ; convert destination spec - jp nz,erramb ; Error, ambiguous file specification - call nonblank ; skip blanks - or a ; end of command tail (null)? - jr z,parse1 ; if end, skip bios image fcb - ld de,biofcb ; point to bios image fcb - call convert ; convert spec to fcb - jp nz,erramb ; Error, ambiguous file specification - or $FF ; flag = true - ld (newbio),a ; set newbio flag to true -; -parse1: - ; return success - xor a ; signal success - ret ; done parsing -; -options: - ; process options - cp '/' ; option introducer? - jr nz,options2 ; if not '/' exit with success - inc hl ; bump past option introducer - ld a,(hl) ; get the next character - cp 'F' ; compare to 'F' - jr z,optf ; handle if so - jp erruse ; bail out if unexpected option - -options1: - ; post-processing after option - inc hl ; move past option - call nonblank ; skip blanks - jr options ; loop - -options2: - ; success exit - xor a ; signal success - ret - -optf: - ; set force flag - or $FF ; load true - ld (force),a ; set flag - jr options1 ; done -; -; Confirm pending action with user -; -confirm: -; ; prompt -; call crlf -; ld de,sconf1 -; call prtstr -; ld hl,biofcb -; call prtfcb -; ld de,sconf2 -; call prtstr -; ld hl,osfcb -; call prtfcb -; ld de,sconf3 -; call prtstr -;; -; ; get input -; ld c,$0A ; get console buffer -; ld de,osimg ; into buf -; ld a,1 ; max of 1 character -; ld (de),a ; set up buffer -; call bdos ; invoke BDOS -; ld a,(osimg+1) ; get num chars entered -; dec a ; check that we got exactly one char -; jr nz,confirm ; bad input, re-prompt -; ld a,(osimg+2) ; get the character -; and $DF ; force upper case -; cp 'Y' ; compare to Y - xor a ; *temp* - ret ; return with Z set appropriately -; -; Read OS image file into memory -; -rdos: - ld de,msgros ; point to "Reading OS" message - call prtstr ; display it - - ; open the file - ld c,$0F ; bdos open file - ld de,osfcb ; bios image fcb - ld (rwfcb),de ; save it - call bdos ; invoke bdos function - cp $FF ; $FF is error - jp z,errfil ; handle error condition - ; read the header - ld a,$14 ; setup for bdos read sequential - ld (rwfun),a ; save bdos function - ld a,12 ; start with 1536 byte header (12 records) - ld (reccnt),a ; init record counter - ld hl,osimg ; start of buffer - ld (bufptr),hl ; init buffer pointer - call rwfil ; read the header - ret nz ; abort on error (no need to close file) - ; check header and get image size - call chkhdr ; verifies marker, hl = image size - ret nz ; abort on error (no need to close file) - ld b,7 ; right shift 7 bits to get 128 byte record count -rdos1: srl h ; shift right msb - rr l ; shift lsb w/ carry from msb - djnz rdos1 ; loop till done - ld a,l ; record count to a - ld (reccnt),a ; set remaining records to read - add a,12 ; add the header back - ld (imgsiz),a ; and save the total image size (in records) - call rwfil ; do it - ret nz ; abort on error - ; return via close file - jp closefile ; close file -; -; -; -rdbio: - ld de,msgrbio ; point to "Reading BIOS" message - call prtstr ; display it - - ; open the file - ld c,$0F ; bdos open file - ld de,biofcb ; bios image fcb - ld (rwfcb),de ; save it - call bdos ; invoke bdos function - cp $FF ; $FF is error - jp z,errfil ; handle error condition - ; read 32K HBIOS image - ld a,$14 ; setup for bdos read sequential - ld (rwfun),a ; save bdos function - ld a,0 ; 0 means 256 records (32K) - ld (reccnt),a ; init record counter - ld hl,hbimg ; start of buffer - ld (bufptr),hl ; init buffer pointer - call rwfil ; read the header - ret nz ; abort on error (no need to close file) - ; return via close file - jp closefile ; close file -; -; Examine the BIOS image loaded. Confirm existence of expected -; BIOS identification marker in first page (fail if not there). -; Display the BIOS identification information. Confirm it is HBIOS -; and fail if not. Save the HBIOS version number. -; -chkbios: - ; locate ROM signature in image - ld hl,sigptr ; point to ROM signature adr - ld a,(hl) ; dereference - inc hl ; ... to point - ld h,(hl) ; ... to location - ld l,a ; ... of signature block - ld de,hbimg ; offset by start - add hl,de ; ... of BIOS image - - ; check signature - ld a,$76 ; first byte value - cp (hl) ; compare - jp nz,errsig ; if not equal, signature error - inc hl ; bump to next byte - ld a,$B5 ; second byte value - cp (hl) ; compare - jp nz,errsig ; if not equal, signature error - inc hl ; bump to next byte - - ;; display short name - ;inc hl ; bump past structure version number - ;inc hl ; bump past rom size - ;ld e,(hl) ; load rom name - ;inc hl ; ... pointer - ;ld d,(hl) ; ... into DE - ;ld hl,hbimg ; offset by start - ;add hl,de ; ... of BIOS image - ;ex de,hl ; get pointer to DE - ;call crlf ; formatting - ;call prtstr ; and display it - - ; check BIOS variant, only HBIOS supported - ld hl,hbmrk ; get the HBIOS marker - ld a,'W' ; first byte should be 'W' - cp (hl) ; compare - jp nz,errbio ; if not equal, fail - inc hl ; next byte - ld a,~'W' ; ... should be ~'W' - cp (hl) ; compare - jp nz,errbio ; if not equal, fail - - ; if UNA is running, skip platform/ver stuff - ld a,(unamod) ; get UNA mode flag - or a ; set flags - jr nz,chkbios1 ; skip if UNA - - ; get and check platform (must match) - ld hl,hbplt ; point to BIOS platform id - ld a,(bioplt) ; get current running platform id - cp (hl) ; match? - jp nz,errplt ; if not, platform error - - ; get HBIOS image version - ld hl,(hbver) ; get version byte from image - ld (biover),hl ; save it for later - -chkbios1: - xor a - ret -; -; Examine the OS image loaded. Confirm existence of expected -; OS identification marker (fail if not there). Check the version -; number in the OS image header. Fail if OS image version does -; not match BIOS version. -; -chkos: - ; check for signature - ; Already verified in chkhdr - - ; compare platform id - ld a,(bioplt) ; get current HBIOS platform ID - ld hl,osplt ; point to OS image platform ID - cp (hl) ; compare - jp nz,errplt ; if not equal platform error - - ; bypass version check if UNA running - ld a,(unamod) ; get UNA mode flag - or a ; set flags - jr nz,chkos1 ; if UNA, bypass - - ; compare version - ld a,(osver) ; get first OS version byte (major) - rlca ; move low nibble - rlca ; ... - rlca ; ... - rlca ; ... to high nibble - ld b,a ; save in b - ld a,(osver + 1) ; get second OS version byte (minor) - or b ; combine with major nibble - ld hl,biover ; point to HBIOS version - cp (hl) ; compare - jp nz,errver ; if not equal, fail - -chkos1: - xor a ; signal success - ret -; -; Load OS image into correct destination -; -ldos: - ; compute the image size (does not include size of header) - ld hl,(osend) ; get CPM_END - ld de,(osloc) ; get CPM_LOC - or a ; clear CF - sbc hl,de ; image size := CPM_END - CPM_LOC - push hl ; move image size - pop bc ; ... to BC - ld hl,osbin ; copy from buf, skip header - ld de,(osloc) ; OS location - ldir ; do the copy - xor a - ret -; -; Load BIOS into correct destination -; -ldbio: -; - ; copy the proxy to upper memory - ld hl,pxyimg ; location of proxy image - ld de,pxyloc ; target location of proxy - ld bc,pxysiz ; size of proxy - ldir ; copy it - ld a,(tpabnk) ; get active tpa bank id - ld (curbnk),a ; fixup the proxy -; - ; copy image to bios bank - ld a,(curbnk) ; load from current bank - ld (srcbnk),a ; set source bank - ld a,(bidbios) ; copy to bios bank - ld (dstbnk),a ; set destination bank - ld hl,hbimg ; set source address - ld de,0 ; set destination address - ld bc,$8000 ; set length - ld a,(curbnk) ; return to current bank - call hbxcpy ; to the inter-bank copy -; - xor a ; signal success - ret -; -; -; -initbio: -; - ; initialize HBIOS - ld a,(bidbios) ; get bios bank - call hbxbnk ; ... and activate it - call $0000 ; call bios init entry point - ld a,(tpabnk) ; get active tpa bank id - call hbxbnk ; ... and activate it -; - xor a - ret -; -; Common routine to handle read/write for file system -; -rwfil: - ld c,$1A ; BDOS set dma - ld de,(bufptr) ; current buffer pointer - push de ; save pointer - call bdos ; do it - pop de ; recover pointer - ld hl,128 ; record length - add hl,de ; increment buffer pointer - ld (bufptr),hl ; save it - ld a,(rwfun) ; get the active function - ld c,a ; set it - ld de,(rwfcb) ; active fcb - call bdos ; do it - or a ; check return code - jp nz,errdos ; BDOS err -; call prtdot ; mark progress - ld hl,reccnt ; point to record count - dec (hl) ; decrement record count - jr nz,rwfil ; loop till done - xor a ; signal success - ret ; done -; -; Close file -; -closefile: - ld c,$10 ; BDOS close file - ld de,(rwfcb) ; active fcb - call bdos ; do it - cp $FF ; $FF is error - jp z,errclo ; if error, handle it - xor a ; signal success - ret ; done -; -jphl: jp (hl) ; indirect jump -; -; Verify system image header in osimg by checking the expected signature. -; Compute and return image size (based on header values) in HL. Size -; does not include header. NZ set if signature error. -; -chkhdr: - ; check signature - ld hl,(ossig) ; get signature - ld de,$A55A ; signature value - or a ; clear CF - sbc hl,de ; compare - jp nz,errsig ; invalid signature - ; compute the image size (does not include size of header) - ld hl,(osend) ; get CPM_END - ld de,(osloc) ; get CPM_LOC - or a ; clear CF - sbc hl,de ; image size := CPM_END - CPM_LOC - xor a ; signal success - ret ; done -; -; Convert a filename at (HL) into an FCB at (DE). -; Includes wildcard expansion. -; On return, A=0 if unambiguous name specified, and -; (HL) points to character following filename spec -; -convert: - push de ; put fcb address on stack - ex de,hl - ld a,(de) ; get first character. - or a - jp z,convrt1 - sbc a,'A'-1 ; might be a drive name, convert to binary. - ld b,a ; and save. - inc de ; check next character for a ':'. - ld a,(de) - cp ':' - jp z,convrt2 - dec de ; nope, move pointer back to the start of the line. -convrt1: - ld a,(defdrv) - ld (hl),a - jp convrt3 -convrt2: - ld a,b - ld (hl),b - inc de - ; Convert the base file name. -convrt3:ld b,08h -convrt4:ld a,(de) - call delim - jp z,convrt8 - inc hl - cp '*' ; note that an '*' will fill the remaining - jp nz,convrt5 ; field with '?' - ld (hl),'?' - jp convrt6 -convrt5:ld (hl),a - inc de -convrt6:dec b - jp nz,convrt4 -convrt7:ld a,(de) - call delim ; get next delimiter - jp z,getext - inc de - jp convrt7 -convrt8:inc hl ; blank fill the file name - ld (hl),' ' - dec b - jp nz,convrt8 -getext: ld b,03h - cp '.' - jp nz,getext5 - inc de -getext1:ld a,(de) - call delim - jp z,getext5 - inc hl - cp '*' - jp nz,getext2 - ld (hl),'?' - jp getext3 -getext2:ld (hl),a - inc de -getext3:dec b - jp nz,getext1 -getext4:ld a,(de) - call delim - jp z,getext6 - inc de - jp getext4 -getext5:inc hl - ld (hl),' ' - dec b - jp nz,getext5 -getext6:ld b,3 -getext7:inc hl - ld (hl),0 - dec b - jp nz,getext7 - pop hl ; HL := start of FCB - push de ; save input line pointer - ; Check to see if this is an ambiguous file name specification. - ; Set the A register to non-zero if it is. - ld bc,11 ; set name length. -getext8:inc hl - ld a,(hl) - cp '?' ; any question marks? - jp nz,getext9 - inc b ; count them. -getext9:dec c - jp nz,getext8 - ld a,b - or a - pop hl ; return with updated input pointer - ret -; -; Print formatted FCB at (HL) -; -prtfcb: - push hl ; save HL - call chkfcb ; set flags indicating nature of FCB - pop hl ; restore HL - ret z ; nothing to print - push af ; save FCB flags - ld a,(hl) ; get first byte of FCB (drive) - inc hl ; point to next char - or a ; is drive specified (non-zero)? - jr z,prtfcb1 ; if zero, do not print drive letter - add a,'@' ; adjust drive number to alpha - call prtchr ; print it - ld a,':' - call prtchr ; print drive separator -prtfcb1: - pop af ; restore FCB flags - bit 1,a ; bit 1 set if filename specified - ret z ; return if no filename - ld b,8 ; base is 8 characters - call prtfcb2 ; print them - ld a,'.' - call prtchr ; print file extension separator - ld b,3 ; extension is 3 characters -prtfcb2: - ld a,(hl) ; load the next character - inc hl ; point to next character - cp ' ' ; check for blank - call nz,prtchr ; print char if it is not a blank - djnz prtfcb2 ; loop till done - ret ; return -; -; Check FCB to see if a drive and/or filename is specified. -; Set bit 0 for drive and bit 1 for filename in A -; -chkfcb: - ld c,0 ; use C for flags, start with none - ld a,(hl) ; get drive - or a ; anything there? - jr z,chkfcb1 ; skip if nothing there - set 0,c ; set bit zero to indicate a drive spec -chkfcb1: - ld b,11 ; set up to check 11 bytes (base & ext) -chkfcb2: - inc hl ; bump to next byte - ld a,(hl) ; get next - cp 'A' ; blank means empty byte - jr nc,chkfcb3 ; if not blank, we have a filename - djnz chkfcb2 ; loop - jr chkfcb4 ; nothing there -chkfcb3: - set 1,c ; set bit 1 to indicate a file spec -chkfcb4: - ld a,c ; put result in a - or a ; set flags - ret -; -; Print character in A without destroying any registers -; -prtchr: - push bc ; save registers - push de - push hl - ld e,a ; character to print in E - ld c,$02 ; BDOS function to output a character - call bdos ; do it - pop hl ; restore registers - pop de - pop bc - ret -; -prtdot: - push af - ld a,'.' - call prtchr - pop af - ret -; -; Print a zero terminated string at (DE) without destroying any registers -; -prtstr: - push de -; -prtstr1: - ld a,(de) ; get next char - or a - jr z,prtstr2 - call prtchr - inc de - jr prtstr1 -; -prtstr2: - pop de ; restore registers - ret -; -; Print the value in A in hex without destroying any registers -; -prthex: - push af ; save AF - push de ; save DE - call hexascii ; convert value in A to hex chars in DE - ld a,d ; get the high order hex char - call prtchr ; print it - ld a,e ; get the low order hex char - call prtchr ; print it - pop de ; restore DE - pop af ; restore AF - ret ; done -; -; Convert binary value in A to ascii hex characters in DE -; -hexascii: - ld d,a ; save A in D - call hexconv ; convert low nibble of A to hex - ld e,a ; save it in E - ld a,d ; get original value back - rlca ; rotate high order nibble to low bits - rlca - rlca - rlca - call hexconv ; convert nibble - ld d,a ; save it in D - ret ; done -; -; Convert low nibble of A to ascii hex -; -hexconv: - and $0F ; low nibble only - add a,$90 - daa - adc a,$40 - daa - ret -; -; Start a new line (or 2) -; -crlf2: - call crlf ; double new line entry -crlf: - ld a,13 ; - call prtchr ; print it - ld a,10 ; - jr prtchr ; print it -; -; Get the next non-blank character from (HL). -; -nonblank: - ld a,(hl) ; load next character - or a ; string ends with a null - ret z ; if null, return pointing to null - cp ' ' ; check for blank - ret nz ; return if not blank - inc hl ; if blank, increment character pointer - jr nonblank ; and loop -; -; Check character at (DE) for delimiter. -; -delim: or a - ret z - cp ' ' ; blank - ret z - jr c,delim1 ; handle control characters - cp '=' ; equal - ret z - cp '_' ; underscore - ret z - cp '.' ; period - ret z - cp ':' ; colon - ret z - cp $3b ; semicolon - ret z - cp '<' ; less than - ret z - cp '>' ; greater than - ret -delim1: - ; treat control chars as delimiters - xor a ; set Z - ret ; return -; -; Invoke CBIOS function -; The CBIOS function offset must be stored in the byte -; following the call instruction. ex: -; call cbios -; .db $0C ; offset of CONOUT CBIOS function -; -cbios: - ex (sp),hl - ld a,(hl) ; get the function offset - inc hl ; point past value following call instruction - ex (sp),hl ; put address back at top of stack and recover HL - ld hl,(cbftbl) ; address of CBIOS function table to HL - call addhl ; determine specific function address - jp (hl) ; invoke CBIOS -; -; Add the value in A to HL (HL := HL + A) -; -addhl: - add a,l ; A := A + L - ld l,a ; Put result back in L - ret nc ; if no carry, we are done - inc h ; if carry, increment H - ret ; and return -; -; Errors -; -erruse: ; command usage error (syntax) - ld de,msguse - jr err -erramb: ; ambiguous file spec (wild cards) is not allowed - ld de,msgamb - jr err -errdlm: ; invalid delimiter in command tail - ld de,msgdlm - jr err -errfil: ; source file not found - ld de,msgfil - jr err -errclo: ; file close error - ld de,msgclo - jr err -errsig: ; invalid system image signature error - ld de,msgsig - jr err -errbio: ; invalid BIOS image, not HBIOS - ld de,msgbio - jr err -errplt: ; platform mismatch - ld de,msgplt - jr err -errver: ; version mismatch - ld de,msgver - jr err -err: ; print error string and return error signal - call crlf2 ; print newline - call prtstr ; print error string - or $FF ; signal error - ret ; done -errdos: ; handle BDOS errors - push af ; save return code - call crlf2 ; newline - ld de,msgdos ; load - call prtstr ; and print error string - pop af ; recover return code - call prthex ; print error code - or $FF ; signal error - ret ; done -; -;=============================================================================== -; Storage Section -;=============================================================================== -; -defdrv .db 0 ; default drive for FCB -cbftbl .dw 0 ; address of CBIOS function table -imgsiz .db 0 ; image size (count of 128 byte records) -; -osfcb .fill 36,0 ; os image FCB -biofcb .fill 36,0 ; bios image FCB -; -unamod .db 0 ; UNA move flag (non-zero if UNA running) -newbio .db 0 ; BIOS load flag (non-zero if new BIOS load) -force .db 0 ; force operation (bypass compatibility checks) -tpabnk .db 0 ; bank id of TPA when app starts -bioplt .db 0 ; Platform ID of running HBIOS -biover .dw 0 ; version of BIOS being loaded -; -stksav .dw 0 ; stack pointer saved at start - .fill stksiz,0 ; stack -stack .equ $ ; stack top -; -rwfun .db 0 ; active read/write function -rwfcb .dw 0 ; active read/write FCB -reccnt .db 0 ; active remaining records to read/write -bufptr .dw 0 ; active pointer into buffer -; -; Messages -; -msgban .db "OSLDR v1.1 for RomWBW, 16-Jan-2018",0 -msghb .db " (HBIOS Mode)",0 -msgub .db " (UBIOS Mode)",0 -msguse .db "Usage: OSLDR [/F] []\r\n" - .db " /F (force) overrides all compatibility checking",0 -msgamb .db "Ambiguous file specification not allowed",0 -msgdlm .db "Invalid delimiter",0 -msgfil .db "File not found",0 -msgclo .db "File close error",0 -msgsig .db "Obsolete or invalid BIOS image (BIOS signature)",0 -msgbio .db "Obsolete or invalid HBIOS image (HBIOS signature)",0 -msgplt .db "Platform (hardware) mismatch",0 -msgver .db "Version mismatch",0 -msgdos .db "DOS error, return code=0x",0 - -msgros .db "Reading OS... ",0 -msgrbio .db "Reading BIOS... ",0 -msglos .db "Loading OS... ",0 -msglbio .db "Loading BIOS... ",0 -; - .end diff --git a/Source/Apps/RTC.asm b/Source/Apps/RTC.asm index 22e627ad..5d731612 100644 --- a/Source/Apps/RTC.asm +++ b/Source/Apps/RTC.asm @@ -19,6 +19,11 @@ ; ;[2018/11/8] v1.2 PMS Add boot option. Code optimization. ; +;[2019/06/21] v1.3 Finalized RC2014 Z180 support. +; +;[2019/08/11] v1.4 Support SCZ180 platform. +; +;[2020/02/02] v1.5 PMS Basic command line support ; ; Constants ; @@ -30,9 +35,13 @@ mask_rst .EQU %00010000 ; De-activate RTC reset line PORT_SBC .EQU $70 ; RTC port for SBC/ZETA PORT_N8 .EQU $88 ; RTC port for N8 PORT_MK4 .EQU $8A ; RTC port for MK4 -PORT_RC .EQU $C0 ; RTC port for RC2014 +PORT_RCZ80 .EQU $C0 ; RTC port for RC2014 +PORT_RCZ180 .EQU $0C ; RTC port for RC2014 +PORT_SCZ180 .EQU $0C ; RTC port for SBCZ180 +PORT_EZZ80 .EQU $C0 ; RTC port for EZZ80 (actually does not have one!!!) BDOS .EQU 5 ; BDOS invocation vector +FCB .EQU 05CH ; Start of command line BID_BOOT .EQU $00 HB_BNKCALL .EQU $FFF9 @@ -66,7 +75,7 @@ LOOP: ; uses BC ; ; based on following algorithm: -: +; ; const ; hextab : string = ('0','1','2','3','4','5','6','7','8', ; '9','A','B','C','D','E','F'); @@ -1066,10 +1075,22 @@ HINIT: LD DE,PLT_MK4 CP $05 ; Mark IV JR Z,RTC_INIT2 - LD C,PORT_RC - LD DE,PLT_RC - CP $07 ; RC2014 + LD C,PORT_RCZ80 + LD DE,PLT_RCZ80 + CP $07 ; RC2014 w/ Z80 + JR Z,RTC_INIT2 + LD C,PORT_RCZ180 + LD DE,PLT_RCZ180 + CP $08 ; RC2014 w/ Z180 + JR Z,RTC_INIT2 + LD C,PORT_SCZ180 + LD DE,PLT_SCZ180 + CP $0A ; SCZ180 JR Z,RTC_INIT2 + ;LD C,PORT_EZZ80 + ;LD DE,PLT_EZZ80 + ;CP $09 ; Easy Z80 + ;JR Z,RTC_INIT2 ; ; Unknown platform LD DE,PLTERR ; BIOS error message @@ -1201,18 +1222,22 @@ IDBIO2: ; Note:above code is not fully in sync with current menu code RTC_TOP_LOOP: + CALL RTC_RESET_ON + CALL RTC_BIT_DELAY + CALL RTC_BIT_DELAY + CALL RTC_BIT_DELAY + + LD A,(FCB+1) ; If there a command line tail + CP '/' ; get the command and feed it + LD A,(FCB+2) ; into the input stream + JR Z,RTC_UCL + LD DE,CRLF_MSG LD C,09H ; CP/M write string to console call CALL 0005H CALL RTC_HELP - CALL RTC_RESET_ON - - CALL RTC_BIT_DELAY - CALL RTC_BIT_DELAY - CALL RTC_BIT_DELAY - RTC_TOP_LOOP_1: LD DE,RTC_TOP_LOOP1_PROMPT LD C,09H ; CP/M write string to console call @@ -1220,7 +1245,7 @@ RTC_TOP_LOOP_1: LD C,01H ; CP/M console input call CALL 0005H - +RTC_UCL: AND %01011111 ; handle lower case responses to menu CP 'L' @@ -1240,31 +1265,31 @@ RTC_TOP_LOOP_1: RET Z CP 'H' - JR Z,RTC_TOP_LOOP_HELP + JP Z,RTC_TOP_LOOP_HELP CP 'D' - JR Z,RTC_TOP_LOOP_DELAY + JP Z,RTC_TOP_LOOP_DELAY CP 'B' - JR Z,RTC_TOP_LOOP_BOOT + JP Z,RTC_TOP_LOOP_BOOT CP 'C' - JR Z,RTC_TOP_LOOP_CHARGE + JP Z,RTC_TOP_LOOP_CHARGE CP 'N' - JR Z,RTC_TOP_LOOP_NOCHARGE + JP Z,RTC_TOP_LOOP_NOCHARGE CP 'A' - JR Z,RTC_TOP_LOOP_START + JP Z,RTC_TOP_LOOP_START CP 'S' JP Z,RTC_TOP_LOOP_SET CP 'I' - JR Z,RTC_TOP_LOOP_INIT + JP Z,RTC_TOP_LOOP_INIT CP 'T' - JR Z,RTC_TOP_LOOP_TIME + JP Z,RTC_TOP_LOOP_TIME LD DE,CRLF_MSG LD C,09H ; CP/M write string to console call @@ -1284,8 +1309,19 @@ RTC_TOP_LOOP_DELAY: JP RTC_TOP_LOOP_1 RTC_TOP_LOOP_BOOT: - LD A,BID_BOOT ; BOOT BANK - LD HL,0 ; ADDRESS ZERO + LD DE,BOOTMSG ; BOOT message + LD C,9 ; BDOS string display function + CALL BDOS ; Do it + ; WAIT FOR MESSAGE TO BE DISPLAYED + LD HL,10000 +DELAY_LOOP: ; LOOP IS 26TS + DEC HL ; 6TS + LD A,H ; 4TS + OR L ; 4TS + JR NZ,DELAY_LOOP ; 12TS + ; RESTART SYSTEM FROM ROM BANK 0, ADDRESS $0000 + LD A,BID_BOOT ; BOOT BANK + LD HL,0 ; ADDRESS ZERO CALL HB_BNKCALL ; DOES NOT RETURN RTC_TOP_LOOP_CHARGE: @@ -1293,6 +1329,9 @@ RTC_TOP_LOOP_CHARGE: LD C,09H ; CP/M write string to console call CALL 0005H CALL RTC_CHARGE_ENABLE + LD A,(FCB+1) ; If we came from the + CP '/' ; command line + RET Z ; exit back to CP/M JP RTC_TOP_LOOP_1 RTC_TOP_LOOP_NOCHARGE: @@ -1300,6 +1339,9 @@ RTC_TOP_LOOP_NOCHARGE: LD C,09H ; CP/M write string to console call CALL 0005H CALL RTC_CHARGE_DISABLE + LD A,(FCB+1) ; If we came from the + CP '/' ; command line + RET Z ; exit back to CP/M JP RTC_TOP_LOOP_1 RTC_TOP_LOOP_START: @@ -1331,6 +1373,9 @@ RTC_TOP_LOOP_TIME: LD DE,RTC_PRINT_BUFFER LD C,09H ; CP/M write string to console call CALL 0005H + LD A,(FCB+1) ; If we came from the + CP '/' ; command line + RET Z ; exit back to CP/M JP RTC_TOP_LOOP_1 RTC_TOP_LOOP_RAW: @@ -1533,7 +1578,7 @@ TESTING_BIT_DELAY_OVER: RTC_HELP_MSG: .DB 0Ah, 0Dh ; line feed and carriage return - .TEXT "RTC: Version 1.2" + .TEXT "RTC: Version 1.5" .DB 0Ah, 0Dh ; line feed and carriage return .TEXT "Commands: E)xit T)ime st(A)rt S)et R)aw L)oop C)harge N)ocharge D)elay I)nit G)et P)ut B)oot H)elp" .DB 0Ah, 0Dh ; line feed and carriage return @@ -1646,15 +1691,19 @@ RTC_GET_BUFFER: .DB 0Ah, 0Dh ; line feed and carriage return .DB "$" ; line terminator -BIOERR .TEXT "\r\nUnknown BIOS, aborting...\r\n$" -PLTERR .TEXT "\r\n\r\nUnknown hardware platform, aborting...\r\n$" -UBERR .TEXT "\r\nUNA UBIOS is not currently supported, aborting...\r\n$" -HBTAG .TEXT "RomWBW HBIOS$" -UBTAG .TEXT "UNA UBIOS" -PLT_SBC .TEXT ", SBC/Zeta RTC Latch Port 0x70\r\n$" -PLT_N8 .TEXT ", N8 RTC Latch Port 0x88\r\n$" -PLT_MK4 .TEXT ", Mark 4 RTC Latch Port 0x8A\r\n$" -PLT_RC .TEXT ", RC2014 RTC Latch Port 0xC0\r\n$" +BIOERR .TEXT "\r\nUnknown BIOS, aborting...\r\n$" +PLTERR .TEXT "\r\n\r\nUnknown/unsupported hardware platform, aborting...\r\n$" +UBERR .TEXT "\r\nUNA UBIOS is not currently supported, aborting...\r\n$" +HBTAG .TEXT "RomWBW HBIOS$" +UBTAG .TEXT "UNA UBIOS" +BOOTMSG .TEXT "\r\n\r\nRebooting...$" +PLT_SBC .TEXT ", SBC/Zeta RTC Latch Port 0x70\r\n$" +PLT_N8 .TEXT ", N8 RTC Latch Port 0x88\r\n$" +PLT_MK4 .TEXT ", Mark 4 RTC Latch Port 0x8A\r\n$" +PLT_RCZ80 .TEXT ", RC2014 Z80 RTC Module Latch Port 0xC0\r\n$" +PLT_RCZ180 .TEXT ", RC2014 Z180 RTC Module Latch Port 0x0C\r\n$" +PLT_SCZ180 .TEXT ", SC Z180 RTC Module Latch Port 0x0C\r\n$" +PLT_EZZ80 .TEXT ", Easy Z80 RTC Module Latch Port 0xC0\r\n$" ; ; Generic FOR-NEXT loop algorithm diff --git a/Source/Apps/SysCopy.asm b/Source/Apps/SysCopy.asm index ffb146bf..144f9536 100644 --- a/Source/Apps/SysCopy.asm +++ b/Source/Apps/SysCopy.asm @@ -1,6 +1,6 @@ ;=============================================================================== ; SysCopy - Copy System Image to/from reserved tracks of disk for RomWBW -; adaptation of CP/M 2.2 +; adaptation of CP/M 2.2 & CP/M 3 ;=============================================================================== ; ; Author: Wayne Warthen (wwarthen@gmail.com) @@ -16,6 +16,7 @@ ; ; Change Log: ; 2016-04-24 [WBW] Updated to preserve MBR partition table +; 2020-02-17 [WBW] Updated for CP/M 3 ;_______________________________________________________________________________ ; ; ToDo: @@ -27,6 +28,9 @@ ; Definitions ;=============================================================================== ; +false .equ 0 ; define true +true .equ ~false ; define false +; stksiz .equ $40 ; we are a stack pig ; restart .equ $0000 ; CP/M restart vector @@ -86,6 +90,18 @@ main: init: ; add check for RomWBW? ; + ; get OS version + ld c,12 ; BDOS get os version + call bdos ; do it, L=version + cp $30 ; Test for v3.0 + jr c,init1 ; if <, pre v3.0 + ld a,true ; OS v3.0 or above + ld (v3os),a ; save it + jr init2 +init1: + ld a,false ; OS < v3.0 + ld (v3os),a ; save it +init2: ; locate cbios function table address ld hl,(restart+1) ; load address of CP/M restart vector ld de,-3 ; adjustment for start of table @@ -96,6 +112,20 @@ init: call bdos ; invoke BDOS function inc a ; 1-based index for fcb ld (defdrv),a ; save it + ; print version banner + call crlf ; formatting + ld de,msgban1 ; point to version message part 1 + call prtstr ; print it + ld a,(v3os) ; get OS version flag + or a ; set flags + ld de,msgv2 ; point to V2 mode message + call z,prtstr ; if V2, say so + ld de,msgv3 ; point to V3 mode message + call nz,prtstr ; if V3, say so + call crlf ; formatting + ld de,msgban2 ; point to version message part 2 + call prtstr ; print it + call crlf ; formatting ; return success xor a ret @@ -237,7 +267,7 @@ wrfil1: ; create target file ; write the image ld a,$15 ; setup for bdos write sequential ld (rwfun),a ; save bdos function - ld a,(imgsiz) ; number of records to read + ld a,(imgsiz) ; number of records to write ld (reccnt),a ; init record counter ld hl,imgbuf ; start of buffer ld (bufptr),hl ; init buffer pointer @@ -292,10 +322,8 @@ rddsk: call setdsk ; setup disk ret nz ; abort on error ; set function to read - ld hl,(cbftbl) ; get address of CBIOS function table - ld a,$27 ; $27 is CBIOS READ entry offset - call addhl ; set HL to resultant entry point - ld (actfnc),hl ; save it + ld a,13 ; CBIOS func 13: Read + ld (actfnc),a ; save it ; read the header ld a,12 ; start with 1536 byte header (12 records) ld (reccnt),a ; initialize record counter @@ -339,11 +367,10 @@ wrdsk: ld hl,mbrbuf ; override to read ld (bufptr),hl ; ... into MBR buffer ld a,4 ; 4 records = 1 512 byte sector + ld (reccnt),a ; initialize record counter ; set function to read - ld hl,(cbftbl) ; get address of CBIOS function table - ld a,$27 ; $27 is CBIOS READ entry offset - call addhl ; set HL to resultant entry point - ld (actfnc),hl ; save it + ld a,13 ; CBIOS func 13: Read + ld (actfnc),a ; save it ; read the existing MBR into memory call rwdsk ; read the sector ret nz ; abort on error @@ -366,10 +393,8 @@ wrdsk1: ; setup to write the image from memory to disk call setdsk ; setup disk ret nz ; abort on error ; set function to write - ld hl,(cbftbl) ; get address of CBIOS function table - ld a,$2A ; $2A is CBIOS WRITE entry offset - call addhl ; set HL to resultant entry point - ld (actfnc),hl ; save it + ld a,14 ; CBIOS func 14: Write + ld (actfnc),a ; save it ; setup the record count to write ld a,(imgsiz) ; get previously recorded image size ld (reccnt),a ; save it as pending record count @@ -399,7 +424,8 @@ setdsk: ld c,a ; move to c ld e,0 ; treat as first select call cbios ; invoke cbios with... - .db $1B ; SELDSK entry offset + ;.db $1B ; SELDSK entry offset + .db 9 ; SELDSK entry offset ; check return (sets HL to DPH address) ld a,h or l @@ -436,29 +462,47 @@ setdsk: ; Read or write (reccnt) sectors to/from disk via CBIOS ; rwdsk: + ld hl,128 ; assume rec len for < CP/M 3 + ld (reclen),hl ; and save it + ld a,(v3os) ; CP/M 3 or greater? + or a ; set flags + jr z,rwdsk0 ; if not, continue + ; adjust reccnt, logical (128) to physical (512) + ld a,(reccnt) ; get pending rec cnt + add a,3 ; round up + srl a ; shift to + srl a ; ... divide by 4 + ld (reccnt),a ; and resave it + ld hl,512 ; use physical rec len + ld (reclen),hl ; and save it +rwdsk0: ; setup to read/write a sector ld bc,(acttrk) ; get active track call cbios ; invoke cbios with... - .db $1E ; SETTRK entry offset + ;.db $1E ; SETTRK entry offset + .db 10 ; SETTRK entry offset ld bc,(actsec) ; get active sector call cbios ; invoke cbios with... - .db $21 ; SETSEC entry offset + ;.db $21 ; SETSEC entry offset + .db 11 ; SETSEC entry offset ld bc,(bufptr) ; get active buffer pointer call cbios ; invoke cbios with... - .db $24 ; SETDMA entry offset + ;.db $24 ; SETDMA entry offset + .db 12 ; SETDMA entry offset ; read/write sector ld a,(reccnt) ; get the pending record count dec a ; last record? ld c,2 ; allow cached writes by default jr nz,rwdsk1 ; not last record, continue ld c,1 ; last record, no caching please -rwdsk1: ld hl,(actfnc) ; load the CBIOS function vector - call jphl ; indirect call (read or write) +rwdsk1: + ld a,(actfnc) + call cbiosfn or a ; set flags on return code jp nz,errio ; if not zero, error abort ; adjust buffer pointer ld hl,(bufptr) ; get buffer pointer - ld de,128 ; record length is 128 bytes + ld de,(reclen) ; get rec len add hl,de ; adjust buffer ptr for next record ld (bufptr),hl ; save it ; next sector @@ -479,7 +523,7 @@ rwdsk1: ld hl,(actfnc) ; load the CBIOS function vector rwdsk2: ld hl,reccnt dec (hl) ; decrement pending record count ret z ; if zero, done, return with Z set - jr rwdsk ; otherwise, loop + jr rwdsk0 ; otherwise, loop ; jphl: jp (hl) ; indirect jump ; @@ -656,6 +700,15 @@ chkfcb4: or a ; set flags ret ; +; Print dot +; +prtdot: + push af + ld a,'.' + call prtchr + pop af + ret +; ; Print character in A without destroying any registers ; prtchr: @@ -775,13 +828,37 @@ delim1: ; cbios: ex (sp),hl - ld a,(hl) ; get the function offset + ld a,(hl) ; get the function number inc hl ; point past value following call instruction ex (sp),hl ; put address back at top of stack and recover HL + +cbiosfn: + ; enter here if function already in reg A + ld (bpb_fn),a ; save function +; + ld a,(v3os) ; CP/M 3 or greater? + or a ; set flags + jr nz,cbios2 ; if >= V3, handle it +; + ; CBIOS call for CP/M < v3 + ld a,(bpb_fn) ; get pending function number + ld l,a ; function number to L + add a,l ; ... and multiply by 3 for + add a,l ; ... jump table offset ld hl,(cbftbl) ; address of CBIOS function table to HL call addhl ; determine specific function address jp (hl) ; invoke CBIOS ; +cbios2: + ; CBIOS call for CP/M v3 or greater + ld (bpb_bc),bc + ld (bpb_de),de + ld (bpb_hl),hl + + ld c,50 ; direct bios call function number + ld de,bpb ; BIOS parameter block + jp bdos ; return via BDOS call +; ; Add the value in A to HL (HL := HL + A) ; addhl: @@ -862,10 +939,24 @@ actdsk .db 0 ; active disk no acttrk .dw 0 ; active track actsec .dw 0 ; active sector actspt .dw 0 ; active sectors per track -actfnc .dw 0 ; active function (read or write) +actfnc .db 0 ; active cbios i/o function (read or write) +v3os .db 0 ; true ($FF) if OS v3.0 or greater +reclen .dw 0 ; active record length +; +bpb: ; BIOS parameter block for CP/M 3 BIOS calls +bpb_fn .db 0 ; function +bpb_a .db 0 ; reg A +bpb_bc .dw 0 ; reg BC +bpb_de .dw 0 ; reg DE +bpb_hl .dw 0 ; reg HL ; ; Messages ; +msgban1 .db "SYSCOPY v2.0 for RomWBW CP/M, 17-Feb-2020$" +msgv2 .db " (CP/M 2 Mode)$" +msgv3 .db " (CP/M 3 Mode)$" +msgban2 .db "Copyright 2020, Wayne Warthen, GNU GPL v3$" + msguse .db "Usage: SYSCOPY [=]$" msgamb .db "Ambiguous file specification not allowed$" msgdlm .db "Invalid delimiter$" diff --git a/Source/Apps/Timer.asm b/Source/Apps/Timer.asm index fa8d737c..0d049a06 100644 --- a/Source/Apps/Timer.asm +++ b/Source/Apps/Timer.asm @@ -19,6 +19,7 @@ ; Change Log: ; 2018-01-14 [WBW] Initial release ; 2018-01-17 [WBW] Add HBIOS check +; 2019-11-08 [WBW] Add seconds support ;_______________________________________________________________________________ ; ; ToDo: @@ -35,12 +36,13 @@ bdos .equ $0005 ; BDOS invocation vector ; ident .equ $FFFE ; loc of RomWBW HBIOS ident ptr ; -rmj .equ 2 ; intended CBIOS version - major -rmn .equ 9 ; intended CBIOS version - minor +rmj .equ 3 ; intended CBIOS version - major +rmn .equ 1 ; intended CBIOS version - minor ; bf_sysver .equ $F1 ; BIOS: VER function bf_sysget .equ $F8 ; HBIOS: SYSGET function bf_sysgettimer .equ $D0 ; TIMER subfunction +bf_sysgetsecs .equ $D1 ; SECONDS subfunction ; ;=============================================================================== ; Code Section @@ -127,7 +129,22 @@ process1a: ld a,l ; new LSB value to A ld (last),a ; save as last value call prtcr ; back to start of line - call nz,prthex32 ; display it + ;call nz,prthex32 ; display it + call prthex32 ; display it + ld de,strtick ; tag + call prtstr ; display it + + ; get and print seconds value + ld b,bf_sysget ; HBIOS SYSGET function + ld c,bf_sysgetsecs ; SECONDS subfunction + rst 08 ; call HBIOS, DE:HL := seconds value + call prthex32 ; display it + ld a,'.' ; fraction separator + call prtchr ; print it + ld a,c ; get fractional component + call prthex ; print it + ld de,strsec ; tag + call prtstr ; display it ; process2: ld a,(cont) ; continuous display? @@ -463,8 +480,8 @@ stack .equ $ ; stack top ; ; Messages ; -msgban .db "TIMER v1.0, 14-Jan-2018",13,10 - .db "Copyright (C) 2018, Wayne Warthen, GNU GPL v3",0 +msgban .db "TIMER v1.1, 10-Nov-2019",13,10 + .db "Copyright (C) 2019, Wayne Warthen, GNU GPL v3",0 msguse .db "Usage: TIMER [/C] [/?]",13,10 .db " ex. TIMER (display current timer value)",13,10 .db " TIMER /? (display version and usage)",13,10 @@ -472,5 +489,7 @@ msguse .db "Usage: TIMER [/C] [/?]",13,10 msgprm .db "Parameter error (TIMER /? for usage)",0 msgbio .db "Incompatible BIOS or version, " .db "HBIOS v", '0' + rmj, ".", '0' + rmn, " required",0 +strtick .db " Ticks, ",0 +strsec .db " Seconds",0 ; .end diff --git a/Source/Apps/Tune/Makefile b/Source/Apps/Tune/Makefile new file mode 100644 index 00000000..3aa5c338 --- /dev/null +++ b/Source/Apps/Tune/Makefile @@ -0,0 +1,12 @@ +OBJECTS = Tune.com +DEST = ../../../Binary/Apps +TOOLS = ../../../Tools + +include $(TOOLS)/Makefile.inc + +Tune.com: Tune.asm + $(TASM) Tune.asm Tune.com + +all:: + mkdir -p $(DEST)/Tunes + cp Tunes/* $(DEST)/Tunes diff --git a/Source/Apps/Tune/Tune.asm b/Source/Apps/Tune/Tune.asm index f2c03141..046b83ac 100644 --- a/Source/Apps/Tune/Tune.asm +++ b/Source/Apps/Tune/Tune.asm @@ -23,16 +23,22 @@ ; - Max Z80 CPU clock is about 8MHz or sound chip will not handle speed. ; - Higher CPU clock speeds are possible on Z180 because extra I/O ; wait states are added during I/O to sound chip. -; - Uses hardware timer support on Z180 processors. Otherwise, a delay -; loop calibrated to CPU speed is used. +; - Uses hardware timer support on systems that support a timer. Otherwise, +; a delay loop calibrated to CPU speed is used. ; - Delay loop is calibrated to CPU speed, but it does not compensate for ; time variations in each quark loop resulting from data decompression. ; An average quark processing time is assumed in each loop. +; - Most sound files originally targeted MSX or ZX Spectrum which used +; 1.7897725 MHz and 1.773400 MHz respectively for the PSG clock. For best +; sound playback, PSG should be run at approx. this clock rate. ;_______________________________________________________________________________ ; ; Change Log: ; 2018-01-26 [WBW] Initial release ; 2018-01-28 [WBW] Added support for MYM sound files +; 2019-11-21 [WBW] Added table-driven configuration +; 2020-02-11 [WBW] Made hardware config & detection more flexible +; 2020-03-29 [WBW] Fix error in Z180 I/O W/S bracketing ;_______________________________________________________________________________ ; ; ToDo: @@ -48,14 +54,12 @@ BDOS .EQU $0005 ; BDOS invocation vector ; IDENT .EQU $FFFE ; loc of RomWBW HBIOS ident ptr ; -RMJ .EQU 2 ; intended CBIOS version - major -RMN .EQU 9 ; intended CBIOS version - minor +RMJ .EQU 3 ; intended CBIOS version - major +RMN .EQU 1 ; intended CBIOS version - minor ; BF_SYSVER .EQU $F1 ; BIOS: VER function BF_SYSGET .EQU $F8 ; HBIOS: SYSGET function ; -DCNTL .EQU $72 ; Z180 DCNTL PORT -; FCB .EQU $5C ; Location of default FCB ; HEAPEND .EQU $C000 ; End of heap storage @@ -79,40 +83,77 @@ TYPMYM .EQU 3 ; FILTYP value for MYM sound file LD A,RMJ << 4 | RMN ; Expected HBIOS ver CP D ; Compare with result above JP NZ,ERRBIO ; Handle BIOS error + LD A,L ; Platform id to A + LD (CURPLT),A ; Save as current platform id +; + LD HL,CFGTBL ; Point to start of config table +CFGSEL: + LD A,$FF ; End of table marker + CP (HL) ; Compare + JP Z,ERRHW ; Bail out if no more configs to try +; + LD BC,CFGSIZ ; Size of one entry + LD DE,CFG ; Active config structure + LDIR ; Update active config structure +; + LD A,(CURPLT) ; Get current running platform id + LD E,A ; Put in E + LD A,(PLT) ; Get platform id of loaded config + CP E ; Equal? + JR NZ,CFGSEL ; If no match keep trying +; + ; Activate card if applicable + CALL SLOWIO ; Slow down I/O now + LD A,(ACR) ; Get ACR port address (if any) + INC A ; $FF -> $00 & set flags + JR Z,PROBE ; Skip ahead to probe if no ACR + DEC A ; Restore real ACR port address + LD C,A ; Put in C for I/O + LD A,$FF ; Value to activate card + OUT (C),A ; Write value to ACR ; - ; Use platform id to derive port addresses - LD A,L ; Platform ID is still in L from above - LD C,L ; Save platform id in C for now - LD HL,$D0D8 ; For RC2014, RSEL=D8, RDAT=D0 - LD DE,MSGRC ; Message for RC2014 platform - CP 7 ; RC2014? - JR Z,_SETP ; If so, set ports - LD HL,$9D9C ; For N8, RSEL=9C, RDAT=9D - LD DE,MSGN8 ; Message for N8 platform - CP 4 ; N8? - JR Z,_SETP ; If so, set ports - LD HL,$9B9A ; Otherwise SCG, RSEL=9A, RDAT=9B - LD DE,MSGSCG ; Message for SCG platform - LD A,$FF ; Write $FF to the - OUT ($9C),A ; ... SCG ACR register to activate card -_SETP LD (PORTS),HL ; Save port values +PROBE: + ; Test for hardware (sound chip detection) + LD DE,(PORTS) ; D := RDAT, E := RSEL + LD C,E ; Port = RSEL + LD A,2 ; Register 2 + OUT (C),A ; Select register 2 + LD C,D ; Port = RDAT + LD A,$AA ; Value = $AA + OUT (C),A ; Write $AA to register 2 + LD A,(RIN) ; Port = RIN + LD C,A ; ... to C + IN A,(C) ; Read back value in register 2 + CP $AA ; Value as written? + PUSH AF ; Save AF + CALL NORMIO ; Back to normal I/O speeds + POP AF ; Recover AF + JR Z,MAT ; Hardware matched! + JR CFGSEL ; And keep trying +; +MAT: + ; Hardware matched! CALL CRLF ; Formatting - CALL PRTSTR ; Display platform string -; - ; Choose quark wait mode based on platform - LD A,C ; Recover platform id - LD B,1 ; Assume timer mode - LD DE,MSGTIM ; Corresponding display string - CP 4 ; N8? - JR Z,_SETM ; If so, commit timer mode - CP 5 ; MK4? - JR Z,_SETM ; If so, commit timer mode - LD B,0 ; Otherwise, delay mode - LD DE,MSGDLY ; Corresponding display string -_SETM LD A,B ; Mode flag value to A + LD DE,(DESC) ; Load hardware description pointer + CALL PRTSTR ; Print description +; + ; Test for timer running to determine if it can be used for delay + LD B,BF_SYSGET ; HBIOS: GET function + LD C,$D0 ; TIMER subfunction + RST 08 ; DE:HL := current tick count + LD A,L ; DE:HL == 0? + OR H + OR E + OR D + LD A,0 ; Assume no timer + LD DE,MSGDLY ; Delay mode msg + JR Z,SETDLY ; If tick count is zero, no timer active + LD A,$FF ; Value for timer active + LD DE,MSGTIM ; Timer mode msg +SETDLY: LD (WMOD),A ; Save wait mode CALL PRTSTR ; Print it -; +; ; Get CPU speed & type from RomWBW HBIOS and compute quark delay factor LD B,$F8 ; HBIOS SYSGET function 0xF8 LD C,$F0 ; CPUINFO subfunction 0xF0 @@ -121,20 +162,6 @@ _SETM LD A,B ; Mode flag value to A RR E ; ... for delay factor EX DE,HL ; Move result to HL LD (QDLY),HL ; Save result as quark delay factor -; - ; Test for hardware (sound chip detection) - LD DE,(PORTS) ; D := RDAT, E := RSEL - LD C,E ; Port = RSEL - LD A,2 ; Register 2 - OUT (C),A ; Select register 2 - LD C,D ; Port = RDAT - LD A,$AA ; Value = $AA - OUT (C),A ; Write $AA to register 2 - LD C,E ; Port = RSEL - IN A,(C) ; Read back value in register 2 - ;CALL PRTHEX ; *debug* - CP $AA ; Value as written? - JP NZ,ERRHW ; If not, handle hardware error ; ; Clear heap storage LD HL,HEAP ; Point to heap start @@ -148,7 +175,18 @@ _SETM LD A,B ; Mode flag value to A LD A,(FCB+1) ; Get first char of filename CP ' ' ; Compare to blank JP Z,ERRCMD ; If so, missing filename - LD A,(FCB+9) ; Extension char 1 + LD A,(FCB+9) ; If the filetype + CP ' ' ; is blanks + JR NZ,HASEXT ; then assume + LD A,'P' ; type PT3. + LD (FCB+9),A + LD A,'T' ; Fill in + LD (FCB+10),A ; the file + LD A,'3' ; extension + LD (FCB+11),A ; and the + LD C,TYPPT3 ; file type + JR _SET +HASEXT LD A,(FCB+9) ; Extension char 1 CP 'P' ; Check for 'P' JP NZ,CHKMYM ; If not, check for MYM extension LD A,(FCB+10) ; Extension char 2 @@ -219,7 +257,7 @@ _LDX LD C,16 ; CPM Close File function LD DE,MSGPLY ; Playing message CALL PRTSTR ; Print message ;CALL CRLF2 ; Formatting - + ;CALL SLOWCPU LD A,(FILTYP) ; Get file type CP TYPPT2 ; PT2? JR Z,GOPT2 ; If so, do it @@ -283,6 +321,7 @@ waitvb call WAITQ jr mymlp ; EXIT CALL START+8 ; Mute audio + ;CALL NORMCPU ;CALL CRLF2 ; Formatting LD DE,MSGEND ; Completion message CALL PRTSTR ; Print message @@ -380,6 +419,70 @@ IDBIO2: XOR A ; Setup return value of 0 RET ; and done ; +; +; +SLOWCPU: + LD A,(Z180) ; Z180 base I/O port + CP $FF ; Check for no value + RET Z ; Bail out if no value + ADD A,$1E ; Apply offset of CMR register + LD C,A ; And put it in C + LD B,0 ; MSB for 16-bit I/O + IN A,(C) ; Get current value + LD (CMRSAV),A ; Save it to restore later + XOR A ; Go slow + OUT (C),A ; And update CMR + INC C ; Now point to CCR register + IN A,(C) ; Get current value + LD (CCRSAV),A ; Save it to restore later + XOR A ; Go slow + OUT (C),A ; And update CCR + RET +; +; +; +NORMCPU: + LD A,(Z180) ; Z180 base I/O port + CP $FF ; Check for no value + RET Z ; Bail out if no value + ADD A,$1E ; Apply offset of CMR register + LD C,A ; And put it in C + LD B,0 ; MSB for 16-bit I/O + LD A,(CMRSAV) ; Get original CMR value + OUT (C),A ; And update CMR + INC C ; Now point to CCR register + LD A,(CCRSAV) ; Get original CCR value + OUT (C),A ; And update CCR + RET +; +; +; +SLOWIO: + LD A,(Z180) ; Z180 base I/O port + CP $FF ; Check for no value + RET Z ; Bail out if no value + ADD A,$32 ; Apply offset of DCNTL register + LD C,A ; And put it in C + LD B,0 ; MSB for 16-bit I/O + IN A,(C) ; Get current value + LD (DCSAV),A ; Save it to restore later + OR %00110000 ; Force slow operation (I/O W/S=3) + OUT (C),A ; And update DCNTL + RET +; +; +; +NORMIO: + LD A,(Z180) ; Z180 base I/O port + CP $FF ; Check for no value + RET Z ; Bail out if no value + ADD A,$32 ; Apply offset of DCNTL register + LD C,A ; And put it in C + LD B,0 ; MSB for 16-bit I/O + LD A,(DCSAV) ; Get saved DCNTL value + OUT (C),A ; And restore it + RET +; ; Print character in A without destroying any registers ; PRTCHR: @@ -553,10 +656,25 @@ CRLF: POP AF ; restore AF RET ; +; ADD HL,A +; +; A REGISTER IS DESTROYED! +; +ADDHLA: + ADD A,L + LD L,A + RET NC + INC H + RET +; ERRBIO: ; Invalid BIOS or version LD DE,MSGBIO JR ERR ; +ERRPLT: ; Invalid BIOS or version + LD DE,MSGPLT + JR ERR +; ERRHW: ; Hardware error, sound chip not detected LD DE,MSGHW JR ERR @@ -586,37 +704,92 @@ ERR1: ; without the leading crlf ERR2: ; without the string CALL CRLF ; print newline JP 0 ; fast exit - -QDLY .DW 0 ; quark delay factor -WMOD .DB 0 ; delay mode, non-zero to use timer -DCSAV .DB 0 ; for saving Z180 DCNTL value -DMA .DW 0 ; Working DMA -FILTYP .DB 0 ; Sound file type (TYPPT2, TYPPT3, TYPMYM) - -TMP .DB 0 ; work around use of undocumented Z80 - +; +; CONFIG TABLE, ENTRY ORDER MATCHES HBIOS PLATFORM ID +; +CFGSIZ .EQU 8 +; +CFGTBL: ; PLT RSEL RDAT RIN Z180 ACR + ; DESC + .DB $01, $9A, $9B, $9A, $FF, $9C ; SBC W/ SCG + .DW HWSTR_SCG +; + .DB $04, $9C, $9D, $9C, $40, $FF ; N8 W/ ONBOARD PSG + .DW HWSTR_N8 +; + .DB $05, $9A, $9B, $9A, $40, $9C ; MK4 W/ SCG + .DW HWSTR_SCG +; + .DB $07, $D8, $D0, $D8, $FF, $FF ; RCZ80 W/ RC SOUND MODULE (EB) + .DW HWSTR_RCEB +; + .DB $07, $D1, $D0, $D0, $FF, $FF ; RCZ80 W/ RC SOUND MODULE (MF) + .DW HWSTR_RCMF +; + .DB $08, $68, $60, $68, $C0, $FF ; RCZ180 W/ RC SOUND MODULE (EB) + .DW HWSTR_RCEB +; + .DB $08, $61, $60, $60, $C0, $FF ; RCZ180 W/ RC SOUND MODULE (MF) + .DW HWSTR_RCMF +; + .DB $09, $D8, $D0, $D8, $FF, $FF ; EZZ80 W/ RC SOUND MODULE (EB) + .DW HWSTR_RCEB +; + .DB $09, $D1, $D0, $D0, $FF, $FF ; EZZ80 W/ RC SOUND MODULE (EB) + .DW HWSTR_RCMF +; + .DB $0A, $68, $60, $68, $C0, $FF ; SCZ180 W/ RC SOUND MODULE (EB) + .DW HWSTR_RCEB +; + .DB $0A, $61, $60, $60, $C0, $FF ; SCZ180 W/ RC SOUND MODULE (MF) + .DW HWSTR_RCMF +; + .DB $FF ; END OF TABLE MARKER +; +CFG: ; ACTIVE CONFIG VALUES (FROM SELECTED CFGTBL ENTRY) +PLT .DB 0 ; RomWBW HBIOS platform id PORTS: -RSEL .DB 0 ; Register selection port -RDAT .DB 0 ; Register data port - -MSGBAN .DB "Tune Player for RomWBW v2.0, 28-Jan-2018",0 -MSGUSE .DB "Copyright (C) 2018, Wayne Warthen, GNU GPL v3",13,10 - .DB "PTxPlayer Copyright (C) 2004-2007 S.V.Bulba",13,10 - .DB "MYMPlay by Marq/Lieves!Tuore",13,10,13,10 - .DB "Usage: TUNE .[PT2|PT3|MYM]",0 -MSGBIO .DB "Incompatible BIOS or version, " - .DB "HBIOS v", '0' + RMJ, ".", '0' + RMN, " required",0 -MSGHW .DB "Hardware error, sound chip not detected!",0 -MSGNAM .DB "Sound filename invalid (must be .PT2, .PT3, or .MYM)",0 -MSGFIL .DB "Sound file not found!",0 -MSGSIZ .DB "Sound file too large to load!",0 -MSGRC .DB "RC2014 Ed Brindley Sound Module",0 -MSGN8 .DB "RetroBrew N8 Onboard Sound System",0 -MSGSCG .DB "RetroBrew SCG ECB Adapter",0 -MSGTIM .DB ", timer mode",0 -MSGDLY .DB ", delay mode",0 -MSGPLY .DB "Playing...",0 -MSGEND .DB " Done",0 +RSEL .DB 0 ; Register selection port +RDAT .DB 0 ; Register data port +RIN .DB 0 ; Register input port +Z180 .DB 0 ; Z180 base I/O port +ACR .DB 0 ; Aux Ctrl Reg I/O port on SCG +DESC .DW 0 ; Hardware description string adr +; +CURPLT .DB 0 ; Current platform id reported by HBIOS +QDLY .DW 0 ; quark delay factor +WMOD .DB 0 ; delay mode, non-zero to use timer +DCSAV .DB 0 ; for saving original Z180 DCNTL value +CCRSAV .DB 0 ; for saving original Z180 CCR value +CMRSAV .DB 0 ; for saving original Z180 CMR value +; +DMA .DW 0 ; Working DMA +FILTYP .DB 0 ; Sound file type (TYPPT2, TYPPT3, TYPMYM) +; +TMP .DB 0 ; work around use of undocumented Z80 +; + +MSGBAN .DB "Tune Player for RomWBW v2.5, 29-Mar-2020",0 +MSGUSE .DB "Copyright (C) 2020, Wayne Warthen, GNU GPL v3",13,10 + .DB "PTxPlayer Copyright (C) 2004-2007 S.V.Bulba",13,10 + .DB "MYMPlay by Marq/Lieves!Tuore",13,10,13,10 + .DB "Usage: TUNE .[PT2|PT3|MYM]",0 +MSGBIO .DB "Incompatible BIOS or version, " + .DB "HBIOS v", '0' + RMJ, ".", '0' + RMN, " required",0 +MSGPLT .DB "Hardware error, system not supported!",0 +MSGHW .DB "Hardware error, sound chip not detected!",0 +MSGNAM .DB "Sound filename invalid (must be .PT2, .PT3, or .MYM)",0 +MSGFIL .DB "Sound file not found!",0 +MSGSIZ .DB "Sound file too large to load!",0 +MSGTIM .DB ", timer mode",0 +MSGDLY .DB ", delay mode",0 +MSGPLY .DB "Playing...",0 +MSGEND .DB " Done",0 +; +HWSTR_SCG .DB "SCG ECB Board",0 +HWSTR_N8 .DB "N8 Onboard Sound",0 +HWSTR_RCEB .DB "RC2014 Sound Module (EB)",0 +HWSTR_RCMF .DB "RC2014 Sound Module (MF)",0 ; ;=============================================================================== ; PTx Player Routines @@ -2000,15 +2173,9 @@ LOUT OUT (C),A #ENDIF #IF WBW - LD A,(WMOD) ; If WMOD = 1, CPU is Z180 - OR A ; Set flags - JR Z,LOUT0 ; Skip Z180 stuff DI - IN0 A,(DCNTL) ; Get wait states - LD (DCSAV),A ; Save value - OR %00110000 ; Force slow operation (I/O W/S=3) - OUT0 (DCNTL),A ; And update DCNTL -LOUT0 LD DE,(PORTS) ; D := RDAT, E := RSEL + CALL SLOWIO + LD DE,(PORTS) ; D := RDAT, E := RSEL XOR A ; start with reg 0 LD C,E ; point to address port LD HL,AYREGS ; start of value list @@ -2025,11 +2192,8 @@ LOUT OUT (C),A ; select register JP M,LOUT2 ; if bit 7 set, return w/o writing value LD C,D ; select data port OUT (C),A ; write value to register 13 -LOUT2 LD A,(WMOD) ; If WMOD = 1, CPU is Z180 - OR A ; Set flags - RET Z ; Skip Z180 stuff - LD A,(DCSAV) ; Get saved DCNTL value - OUT0 (DCNTL),A ; And restore it +LOUT2 + CALL NORMIO EI RET ; And done #ENDIF @@ -2175,7 +2339,7 @@ T_PACK .DB $06EC*2/256,$06EC*2 ; ; MYMPLAY - Player for MYM-tunes ; MSX-version by Marq/Lieves!Tuore & Fit 30.1.2000 -: +; ; 1.2.2000 - Added the disk loader. Thanks to Yzi & Plaque for examples. ; 7.2.2000 - Removed one unpack window -> freed 1.7kB memory ; @@ -2393,10 +2557,7 @@ upsg: ld a,(WMOD) ; if WMOD = 1, CPU is z180 or a ; set flags jr z,upsg1 ; skip z180 stuff di - in0 a,(DCNTL) ; get wait states - ld (DCSAV),a ; save value - or %00110000 ; force slow operation (i/o w/s=3) - out0 (DCNTL),a ; and update DCNTL + call SLOWIO upsg1: ld hl,(psource) ld de,(PORTS) ; E := RSEL, D := RDAT @@ -2431,11 +2592,7 @@ notrig: ld hl,(psource) dec a ld (played),a -endint: ld a,(WMOD) ; If WMOD = 1, CPU is Z180 - or a ; Set flags - ret z ; Skip Z180 stuff - ld a,(DCSAV) ; Get saved DCNTL value - out0 (DCNTL),a ; And restore it +endint: call NORMIO ei ret ; And done ; @@ -2475,47 +2632,47 @@ HEAP .EQU $ VARS -ChanA .DS CHP -ChanB .DS CHP -ChanC .DS CHP +ChanA .DS CHP +ChanB .DS CHP +ChanC .DS CHP ;GlobalVars -DelyCnt .DS 1 -CurESld .DS 2 -CurEDel .DS 1 +DelyCnt .DS 1 +CurESld .DS 2 +CurEDel .DS 1 Ns_Base_AddToNs -Ns_Base .DS 1 -AddToNs .DS 1 +Ns_Base .DS 1 +AddToNs .DS 1 AYREGS -VT_ .DS 256 ;CreatedVolumeTableAddress +VT_ .DS 256 ;CreatedVolumeTableAddress -EnvBase .EQU VT_+14 +EnvBase .EQU VT_+14 -T1_ .EQU VT_+16 ;Tone tables data depacked here +T1_ .EQU VT_+16 ;Tone tables data depacked here -T_OLD_1 .EQU T1_ -T_OLD_2 .EQU T_OLD_1+24 -T_OLD_3 .EQU T_OLD_2+24 -T_OLD_0 .EQU T_OLD_3+2 -T_NEW_0 .EQU T_OLD_0 -T_NEW_1 .EQU T_OLD_1 -T_NEW_2 .EQU T_NEW_0+24 -T_NEW_3 .EQU T_OLD_3 +T_OLD_1 .EQU T1_ +T_OLD_2 .EQU T_OLD_1+24 +T_OLD_3 .EQU T_OLD_2+24 +T_OLD_0 .EQU T_OLD_3+2 +T_NEW_0 .EQU T_OLD_0 +T_NEW_1 .EQU T_OLD_1 +T_NEW_2 .EQU T_NEW_0+24 +T_NEW_3 .EQU T_OLD_3 -PT2EMPTYORN .EQU VT_+31 ;1,0,0 sequence +PT2EMPTYORN .EQU VT_+31 ;1,0,0 sequence -NT_ .FILL 192 ;CreatedNoteTableAddress +NT_ .DS 192 ;CreatedNoteTableAddress ;local var -Ampl .EQU AYREGS+AmplC +Ampl .EQU AYREGS+AmplC -VAR0END .EQU VT_+16 ;INIT zeroes from VARS to VAR0END-1 +VAR0END .EQU VT_+16 ;INIT zeroes from VARS to VAR0END-1 -VARSEND .EQU $ +VARSEND .EQU $ -MDLADDR .EQU $ +MDLADDR .EQU $ ; ;=============================================================================== ; MYM Player Storage @@ -2524,10 +2681,11 @@ MDLADDR .EQU $ .ORG HEAP ; Reserve room for uncompressed data uncomp: -.org $+(3*FRAG*REGS) + .DS (3*FRAG*REGS) ; The tune is stored here -rows: .dw 0 +rows: + .DS 2 ; WORD value data: ; ;=============================================================================== diff --git a/Source/Apps/XM/Build.cmd b/Source/Apps/XM/Build.cmd index 26cb0ccc..4fbbae00 100644 --- a/Source/Apps/XM/Build.cmd +++ b/Source/Apps/XM/Build.cmd @@ -11,9 +11,12 @@ set ZXINCDIR=%TOOLS%\cpm\include\ zx mac xmdm125.asm $PO zx slr180 -xmhb/HF +rem zx slr180 -xmuf/HF zx mload25 XM=xmdm125,xmhb +rem zx mload25 XMUF=xmdm125,xmuf rem set PROMPT=[Build] %PROMPT% rem %comspec% copy /Y XM.com ..\..\..\Binary\Apps\ +rem copy /Y XMUF.com ..\..\..\Binary\Apps\ diff --git a/Source/Apps/XM/Makefile b/Source/Apps/XM/Makefile new file mode 100644 index 00000000..b16ff7ef --- /dev/null +++ b/Source/Apps/XM/Makefile @@ -0,0 +1,13 @@ +#OBJECTS = xm.com xmuf.com +OBJECTS = xm.com +DEST = ../../../Binary/Apps +TOOLS = ../../../Tools +OTHERS = *.hex + +include $(TOOLS)/Makefile.inc + +xm.com: xmdm125.hex xmhb.hex + $(ZXCC) $(CPM)/MLOAD25 XM=xmdm125,xmhb + +#xmuf.com: xmdm125.hex xmuf.hex +# $(ZXCC) $(CPM)/MLOAD25 XMUF=xmdm125,xmuf diff --git a/Source/Apps/XM/xmdm125.asm b/Source/Apps/XM/xmdm125.asm index 01ab3052..ac881800 100644 --- a/Source/Apps/XM/xmdm125.asm +++ b/Source/Apps/XM/xmdm125.asm @@ -2213,6 +2213,14 @@ ILLDU: CALL ERXIT ; RCVRECD:XRA A ; Initialize error count to zero STA ERRCT +; +;WBW BEGIN: Be more patient waiting for host to start sending file + LDA FRSTIM ; WBW: Get first time flag + ORA A ; WBW: Set CPU flags + JNZ RCVRPT ; WBW: If not first time, bypass + MVI A,-10 ; WBW: Else increase error limit + STA ERRCT ; WBW: Save error new limit +;WBW END ; RCVRPT: IF CONFUN ; Check for function key? CALL FUNCHK ; Yeah, why not? @@ -2224,7 +2232,8 @@ RCVRPT: IF CONFUN ; Check for function key? JNZ RCVSABT ; If so, bail out now... ENDIF ; - MVI B,10-1 ; 10-second timeout + ;MVI B,10-1 ; 10-second timeout + MVI B,5-1 ; WBW: 5-second timeout CALL RECV ; Get any character received JC RCVSTOT ; Timeout ; diff --git a/Source/Apps/XM/xmhb.180 b/Source/Apps/XM/xmhb.180 index f0c19936..8f926794 100644 --- a/Source/Apps/XM/xmhb.180 +++ b/Source/Apps/XM/xmhb.180 @@ -1,19 +1,15 @@ ;======================================================================= ; -; XMHB.Z80 - XMODEMXX PATCH FILE FOR ROMWBW HBIOS +; XMHB.Z80 - XMODEM12 PATCH FILE FOR ROMWBW HBIOS ; ; Wayne Warthen - wwarthen@gmail.com -; Updated: 2018-06-06 ; ; 2018-06-06 WBW Added support for RC2014 w/ Z180 +; 2019-08-17 WBW Refactored and merged Phil's ECB-FIFO support +; 2019-08-28 WBW Refactored ASCI support ; ;======================================================================= ; -; Overlay file is Z80, build with M80: -; M80 =XMHB -; L80 XMHB,XMHB/N/X/E -; - .Z80 ASEG ; NO EQU 0 @@ -31,22 +27,22 @@ BDOS EQU 00005H ; BDOS function dispatch vector ; one in XMODEM. Note the ORG of 103H - This jump table has no jump to ; 'BEGIN'. ; - ORG BASE + 3 ;start after 'JMP BEGIN' + ORG BASE + 3 ; start after 'JMP BEGIN' ; - JP CONOUT ;must be 00000h if not used, see below - JP MINIT ;initialization routine (if needed) - JP UNINIT ;undo whatever 'MINIT' did (or return) + JP CONOUT ; must be 00000h if not used, see below + JP MINIT ; initialization routine (if needed) + JP UNINIT ; undo whatever 'MINIT' did (or return) JPTBL: - JP SENDR ;send character (via pop psw) - JP CAROK ;test for carrier - JP MDIN ;receive data byte - JP GETCHR ;get character from modem - JP RCVRDY ;check receive ready - JP SNDRDY ;check send ready - JP SPEED ;get speed value for file transfer time - JP EXTRA1 ;extra for custom routine - JP EXTRA2 ;extra for custom routine - JP EXTRA3 ;extra for custom routine + JP SENDR ; send character (via pop psw) + JP CAROK ; test for carrier + JP MDIN ; receive data byte + JP GETCHR ; get character from modem + JP RCVRDY ; check receive ready + JP SNDRDY ; check send ready + JP SPEED ; get speed value for file transfer time + JP EXTRA1 ; extra for custom routine + JP EXTRA2 ; extra for custom routine + JP EXTRA3 ; extra for custom routine ; ;----------------------------------------------------------------------- ; @@ -63,7 +59,6 @@ CONOUT EQU 0 ; not used ; entries as appropriate. ; MINIT: -; ; Announce LD DE,RBC ; RetroBrew Computers LD C,9 ; BDOS string display function @@ -78,24 +73,39 @@ MINIT: JR Z,UINIT ; Do UBIOS setup ; ; Neither UNA nor RomWBW - LD DE,BIOERR ; BIOS error message + LD DE,ERR_BIO ; BIOS error message LD C,9 ; BDOS string display function CALL BDOS ; Do it JP 0 ; Bail out! ; -HINIT: +MINIT_RET: + PUSH HL ; Save HL (JP table adr) + + ; Display port notification string + LD C,9 ; BDOS string display function + CALL BDOS ; Do it +; + ; Newline + LD C,9 ; BDOS string display function + LD DE,CRLF ; Newline + CALL BDOS ; Do it ; + ; Copy real vectors into active jump table + POP HL ; Recover HL + LD DE,JPTBL ; Real jump table is destination + LD BC,7 * 3 ; Copy 7 3-byte entries + LDIR ; Do the copy +; + ; Return with CPU speed in A + LD A,(CPUSPD) ; A := CPU speed in MHz + LD HL,(RCVSCL) ; HL := receive scalar + RET ; and return +; +HINIT: ; Display RomWBW notification string LD DE,HBTAG ; BIOS notification string LD C,9 ; BDOS string display function CALL BDOS ; Do it -; - ; Get platform id from RomWBW HBIOS and save it - LD B,0F1H ; HBIOS VER function 0xF1 - LD C,0 ; Required reserved value - RST 08 ; Do it, L := Platform ID - LD A,L ; Move to A - LD (PLTID),A ; Save it ; ; Get CPU speed from RomWBW HBIOS and save it LD B,0F8H ; HBIOS SYSGET function 0xF8 @@ -103,10 +113,21 @@ HINIT: RST 08 ; Do it, L := CPU speed in MHz LD A,L ; Move it to A LD (CPUSPD),A ; Save it - JR MINIT1 ; Continue general initialization ; -UINIT: + ; Get HBIOS character 0 device type + LD B,006H ; HBIOS DEVICE function 0x06 + LD C,000H ; HBIOS char 0 device + RST 08 ; Do it, D=device type + LD A,D ; Put result in A + CP 000H ; UART? + JP Z,U_INIT ; If so, do UART init + CP 010H ; ASCI? + JP Z,A_INIT ; If so, do ASCI init + CP 080H ; USB-FIFO? + JP Z,UF_INIT ; If so, do USB-FIFO init + JP H_INIT ; Otherwise, use HBIOS I/O ; +UINIT: ; Display UNA notification string LD DE,UBTAG ; BIOS notification string LD C,9 ; BDOS string display function @@ -115,7 +136,7 @@ UINIT: ; Get CPU speed from UNA and save it LD C,0F8H ; UNA BIOS Get PHI function RST 08 ; Returns speed in Hz in DE:HL - LD B,4 ; Divide MHz in DE:HL by 100000H + LD B,4 ; Divide MHz in DE:HL by 100000H UINIT1: SRL D ; ... to get approx CPU speed in RR E ; ...MHz. Throw away HL, and @@ -123,70 +144,22 @@ UINIT1: INC E ; Fix up for value truncation LD A,E ; Put in A LD (CPUSPD),A ; Save it - JR MINIT1 ; Continue general initialization -; -MINIT1: - ; NOTE: PLTID is only set if RomWBW HBIOS is active. This is OK - ; because RC2014 is only supported by RomWBW HBIOS at this point. - LD A,(PLTID) ; Get the platform id - CP 7 ; Check for RC2014 - JR Z,RCINIT ; Handle RC2014 special - CP 8 ; Check for RC2014 w/ Z180 - JR Z,ARCINIT ; Handle RC2014 w/ Z180 -; - ; Check for Z180 which implies ASCI serial port +; + ; Check CPU, Z80=UART, Z180=ASCI LD DE,00202H ; D := 2, E := 2 MLT DE ; DE := D * E == 4 BIT 2,E ; Bit 2 wil be set if mlt happend - JR Z,MINIT2 ; Not Z180 (ASCI), look for others - LD HL,ASCI_JPTBL ; Point to Z180 (ASCI) jump table - LD DE,ASCI ; ASCI port notification string - JR MINIT3 ; Complete the initialization -; -MINIT2: - ; Not a Z180, so assume RBC standard UART serial port - LD HL,UART_JPTBL ; Assume Z80 (UART) - LD DE,UART ; UART port notification string - JR MINIT3 ; Complete the initialization -; -RCINIT: - ; RC2014, use HBIOS calls - LD HL,1250 ; Smaller receive loop tiemout scalar - LD (RCVSCL),HL ; ... to compensate for BIOS overhead - LD HL,HBIOS_JPTBL ; HBIOS jump table address - LD DE,COMX ; HBIOS console notification string - JR MINIT3 ; Complete the initialization -; -ARCINIT: - ; RC2014 running Z180 - LD HL,ARC_JPTBL ; ASCI RC2014 jump table address - LD DE,ASCIRC ; ASCI RC2014 console notification string - JR MINIT3 ; Complete the initialization -; -MINIT3: - PUSH HL ; Save HL - - ; Display port notification string - LD C,9 ; BDOS string display function - CALL BDOS ; Do it + JP Z,U_INIT ; UART initialization + JP A_INIT ; otherwise, ASCI ; - ; Newline +HWERR: + ; Failed to identify target comm hardware + LD DE,ERR_HW ; Hardware error message LD C,9 ; BDOS string display function - LD DE,CRLF ; Newline CALL BDOS ; Do it + JP 0 ; Bail out! ; - ; Copy real vectors into active jump table - POP HL ; Recover HL - LD DE,JPTBL ; Real jump table is destination - LD BC,7 * 3 ; Copy 7 3-byte entries - LDIR ; Do the copy -; - ; Return with CPU speed in A - LD A,(CPUSPD) ; A := CPU speed in MHz - LD HL,(RCVSCL) ; HL := receive scalar - RET ; and return -; -; Identify active BIOS. RomWBW HBIOS=1, UNA UBIOS=2, else 0 +; Identify active BIOS. RomWBW HBIOS=1, UNA UBIOS=2, else 0 ; IDBIO: ; @@ -223,32 +196,32 @@ IDBIO2: XOR A ; Setup return value of 0 RET ; and done ; -; -; -BIOID DB 0 ; BIOS ID, 1=HBIOS, 2=UBIOS -PLTID DB 0 ; Platform ID -CPUSPD DB 10 ; CPU speed in MHz -RCVSCL DW 2800 ; RECV loop timeout scalar -; -RBC DB "RBC, 06-Jun-2018$" -; -UART DB ", UART0$" -ASCI DB ", ASCI0$" -ASCIRC DB ", ASCI0 (RC2014)$" -COMX DB ", COM0$" -; -UBTAG DB " [UNA]$" -HBTAG DB " [WBW]$" -; -CRLF DB 13, 10, "$" -; -BIOERR DB 13, 10, 13, 10, "++ Unknown BIOS ++", 13, 10, "$" -; ;----------------------------------------------------------------------- ; ; Uninitialize modem ; UNINIT: + LD A,(BIOID) + CP 1 ; Is HBIOS? + JR Z,H_UNINIT ; Handle HBIOS + CP 2 ; Is UBIOS? + JR Z,U_UNINIT ; Handle UBIOS + RET ; Just return +; +H_UNINIT: + ; HBIOS: Reset character device 0 + LD B,04H ; HBIOS CIOINIT function 0x04 + LD C,0 ; Unit = 0 + LD DE,-1 ; Reset w/ current settings + RST 08 ; Do it + RET ; not initialized, so no 'UN-INITIALIZE' +; +U_UNINIT: + ; UBIOS: Reset character device 0 + LD C,10H ; UNA INIT function 0x10 + LD B,0 ; Unit = 0 + LD DE,-1 ; Reset w/ current settings + RST 08 ; Do it RET ; not initialized, so no 'UN-INITIALIZE' ; ;----------------------------------------------------------------------- @@ -268,12 +241,30 @@ EXTRA2: EXTRA3: RET ; +BIOID DB 0 ; BIOS ID, 1=HBIOS, 2=UBIOS +CPUSPD DB 10 ; CPU speed in MHz +RCVSCL DW 6600 ; RECV loop timeout scalar +; +RBC DB "RBC, 28-Aug-2019$" +; +U_LBL DB ", UART$" +A_LBL DB ", ASCI$" +S_LBL DB ", SIO$" +H_LBL DB ", COM$" +UF_LBL DB ", USB-FIFO$" +; +UBTAG DB " [UNA]$" +HBTAG DB " [WBW]$" +; +CRLF DB 13, 10, "$" +; +ERR_BIO DB 13, 10, 13, 10, "++ Unknown BIOS ++", 13, 10, "$" +ERR_HW DB 13, 10, 13, 10, "++ Unknown Hardware ++", 13, 10, "$" +; ;======================================================================= ;======================================================================= ; -; Standard RBC Projects 8250-like UART port @ 68H -; -; Will be used for all RBC Z80 systems. +; 8250-like UART @ Port 68H ; ;======================================================================= ;======================================================================= @@ -291,12 +282,13 @@ U_RCVR EQU 01H ; value when ready to receive U_PARE EQU 04H ; bit for parity error U_OVRE EQU 02H ; bit for overrun error U_FRME EQU 08H ; bit for framing error +U_ERRS EQU U_FRME | U_OVRE | U_PARE ; ; Following jump table is dynamically patched into real jump ; table at program startup. See MINIT above. Note that only a ; subset of the jump table is overlaid (SENDR to SPEED). ; -UART_JPTBL: +U_JPTBL: JP U_SENDR ; send character (via pop psw) JP U_CAROK ; test for carrier JP U_MDIN ; receive data byte @@ -307,6 +299,18 @@ UART_JPTBL: ; ;----------------------------------------------------------------------- ; +; UART initialization +; +U_INIT: + LD HL,13000 ; Receive loop timeout scalar + LD (RCVSCL),HL ; ... for UART RCVRDY timing +; + LD HL,U_JPTBL + LD DE,U_LBL + JP MINIT_RET +; +;----------------------------------------------------------------------- +; ; Send character on top of stack ; U_SENDR: @@ -316,7 +320,7 @@ U_SENDR: ; ;----------------------------------------------------------------------- ; -; Test and rep;ort carrier status, Z set if carrier present +; Test and report carrier status, Z set if carrier present ; U_CAROK: XOR A ; not used, always indicate present @@ -345,7 +349,7 @@ U_RCVRDY: ; With error detection (slower) PUSH BC ; save scratch register PUSH AF ; save full status on stack - AND U_FRME | U_OVRE | U_PARE ; isolate line err bits + AND U_ERRS ; isolate line err bits LD B,A ; save err status in B POP AF ; get full status back AND U_RCVB ; isolate ready bit @@ -385,53 +389,98 @@ U_SPEED: ;======================================================================= ;======================================================================= ; -; Standard RBC Projects Z180 primary ASCI port +; Z180 Primary ASCI ; -; Will be used for all RBC Z180 systems. +; - Port is determined dynamically in A_INIT ; ;======================================================================= ;======================================================================= ; ; ASCI port constants ; -A_DATP EQU 48H ;Z180 TSR - ASCI receive data port -A_DATO EQU 46H ;Z180 TDR - ASCI transmit data port -A_CTLP EQU 44H ;Z180 STAT - ASCI status port -A_CTL2 EQU 40H ;Z180 CNTLA - ASCI control port -; -A_SNDB EQU 02H ;Z180 STAT:TDRE - xmit data reg empty bit -A_SNDR EQU 02H ;Z180 STAT:TDRE - xmit data reg empty value -A_RCVB EQU 80H ;Z180 STAT:RDRF - rcv data reg full bit -A_RCVR EQU 80H ;Z180 STAT:RDRF - rcv data reg full value -A_PARE EQU 20H ;Z180 STAT:PE - parity error bit -A_OVRE EQU 40H ;Z180 STAT:OVRN - overrun error bit -A_FRME EQU 10H ;Z180 STAT:FE - framing error bit +A_DATP EQU 08H ; Z180 TSR - ASCI receive data port +A_DATO EQU 06H ; Z180 TDR - ASCI transmit data port +A_CTLP EQU 04H ; Z180 STAT - ASCI status port +A_CTL2 EQU 00H ; Z180 CNTLA - ASCI control port +; +A_SNDB EQU 02H ; Z180 STAT:TDRE - xmit data reg empty bit +A_SNDR EQU 02H ; Z180 STAT:TDRE - xmit data reg empty value +A_RCVB EQU 80H ; Z180 STAT:RDRF - rcv data reg full bit +A_RCVR EQU 80H ; Z180 STAT:RDRF - rcv data reg full value +A_PARE EQU 20H ; Z180 STAT:PE - parity error bit +A_OVRE EQU 40H ; Z180 STAT:OVRN - overrun error bit +A_FRME EQU 10H ; Z180 STAT:FE - framing error bit +A_ERRS EQU A_FRME | A_OVRE | A_PARE +; +A_BASE DB 00H ; internal IO base address for Z180 ; ; Following jump table is dynamically patched over initial jump ; table at program startup. See MINIT above. Note that only a ; subset of the jump table is overlaid (SENDR to SPEED). ; -ASCI_JPTBL: - JP A_SENDR ;send character (via pop psw) - JP A_CAROK ;test for carrier - JP A_MDIN ;receive data byte - JP A_GETCHR ;get character from modem - JP A_RCVRDY ;check receive ready - JP A_SNDRDY ;check send ready - JP A_SPEED ;get speed value for file transfer time +A_JPTBL: + JP A_SENDR ; send character (via pop psw) + JP A_CAROK ; test for carrier + JP A_MDIN ; receive data byte + JP A_GETCHR ; get character from modem + JP A_RCVRDY ; check receive ready + JP A_SNDRDY ; check send ready + JP A_SPEED ; get speed value for file transfer time +; +;----------------------------------------------------------------------- +; +; ASCI initialization +; +A_INIT: + LD HL,7500 ; Receive loop timeout scalar + LD (RCVSCL),HL ; ... for ASCI RCVRDY timing +; + ; Test for location of Z180 internal registers + ; and use appropriate I/O address. + LD B,0 ; set MSB for 16 bit I/O + LD C,040H|3FH ; internal registers @ 40H? + IN A,(C) ; read + CP 040H|01FH ; same value except for bit 5? + JR Z,A_INIT1 ; do ASCI init (port in C) + LD C,0C0H|3FH ; internal registers @ C0H? + IN A,(C) ; read + CP 0C0H|1FH ; same value except for bit 5? + JR Z,A_INIT1 ; do ASCI init (port in C) + JP HWERR ; unknown hardware error +; +A_INIT1: + LD A,C ; test port value to A + AND 0C0H ; only top two bits relevant + LD (A_BASE),A ; save it + ADD A,A_CTLP ; status port offset + LD C,A ; put in C for I/O + LD B,0 ; MSB for 16 bit I/O + XOR A ; clear interrupt enable flags + OUT (C),A ; do it +; + LD HL,A_JPTBL + LD DE,A_LBL + JP MINIT_RET ; ;----------------------------------------------------------------------- ; ; Send character on top of stack ; A_SENDR: - POP AF ; get character to send from stack - OUT0 (A_DATO),A ; send to port - RET + EX (SP),HL ; save HL, HL := char to send + PUSH BC ; save scratch register + LD A,(A_BASE) ; IO base address + ADD A,A_DATO ; data out port offset + LD C,A ; put in C for I/O + LD B,0 ; MSB for 16 bit I/O + OUT (C),H ; send to port + POP BC ; restore scratch reg + POP HL ; restore HL + RET ; done ; ;----------------------------------------------------------------------- ; -; Test and rep;ort carrier status, Z set if carrier present +; Test and report carrier status, Z set if carrier present ; A_CAROK: XOR A ; not used, always indicate present @@ -443,7 +492,13 @@ A_CAROK: ; A_MDIN: A_GETCHR: - IN0 A,(A_DATP) ; read character from port + PUSH BC ; save scratch register + LD A,(A_BASE) ; IO base address + ADD A,A_DATP ; data in port offset + LD C,A ; put in C for I/O + LD B,0 ; MSB for 16 bit I/O + IN A,(C) ; read character from port + POP BC ; restore scratch reg RET ; ;----------------------------------------------------------------------- @@ -453,22 +508,32 @@ A_GETCHR: ; *** Error code does not seem to be used *** ; A_RCVRDY: - IN0 A,(A_CTLP) ; get modem status PUSH BC ; save scratch register + LD A,(A_BASE) ; IO base address + ADD A,A_CTLP ; status port offset + LD C,A ; put in C for I/O + LD B,0 ; MSB for 16 bit I/O + IN A,(C) ; get modem status PUSH AF ; save full status on stack - AND A_FRME | A_OVRE | A_PARE ; isolate line err bits + AND A_ERRS ; isolate line err bits LD B,A ; save err status in B - + ; Z180 ASCI ports will stall if there are errors. ; Error bits are NOT cleared by merely reading - ; the status register. Below, bit 3 of ASCI - ; control register is written with a zero to + ; the status register. Below, bit 3 of ASCI + ; control register is written with a zero to ; clear error(s) if needed. - JP Z,A_RCVRDY2 ; if no errs, continue - IN0 A,(A_CTL2) ; get current control register + JR Z,A_RCVRDY2 ; if no errs, continue + PUSH BC ; save scratch reg + LD A,(A_BASE) ; IO base address + ADD A,A_CTL2 ; status port offset + LD C,A ; put in C for I/O + LD B,0 ; MSB for 16 bit I/O + IN A,(C) ; get current control reg value AND 0F7H ; force err reset bit to zero - OUT0 (A_CTL2),A ; write control register - + OUT (C),A ; write control register + POP BC ; restore scratch reg +; A_RCVRDY2: POP AF ; get full status back AND A_RCVB ; isolate ready bit @@ -482,9 +547,15 @@ A_RCVRDY2: ; Test for ready to send a character, Z = ready ; A_SNDRDY: - IN A,(A_CTLP) ; get status + PUSH BC ; save scratch register + LD A,(A_BASE) ; IO base address + ADD A,A_CTLP ; status port offset + LD C,A ; put in C for I/O + LD B,0 ; MSB for 16 bit I/O + IN A,(C) ; get modem status AND A_SNDB ; isolate transmit ready bit CP A_SNDR ; test for ready value + POP BC ; restore scratch register RET ; ;----------------------------------------------------------------------- @@ -498,47 +569,71 @@ A_SPEED: ;======================================================================= ;======================================================================= ; -; RC2014 Z180 primary ASCI port -; -; Will be used for all RC2014 Z180 systems. +; Zilog SIO @ Port 80H ; ;======================================================================= ;======================================================================= ; -; ASCI port constants for RC2014 +; Currently assumes the port address and ordering conventions of the +; official RC2014 SIO module. Will not work with others such as EZZ80 +; or ZP. ; -AR_DATP EQU 0C8H ;Z180 TSR - ASCI receive data port -AR_DATO EQU 0C6H ;Z180 TDR - ASCI transmit data port -AR_CTLP EQU 0C4H ;Z180 STAT - ASCI status port -AR_CTL2 EQU 0C0H ;Z180 CNTLA - ASCI control port +; SIO port constants ; -; Following jump table is dynamically patched over initial jump +S_BASE EQU 80H ; SIO base port +S_DATP EQU S_BASE + 1 ; data in port +S_DATO EQU S_BASE + 1 ; data out port +S_CTLP EQU S_BASE + 0 ; control/status port +S_SNDB EQU 04H ; bit to test for send ready +S_SNDR EQU 04H ; value when ready to send +S_RCVB EQU 01H ; bit to test for receive ready +S_RCVR EQU 01H ; value when ready to receive +; +; Following jump table is dynamically patched into real jump ; table at program startup. See MINIT above. Note that only a ; subset of the jump table is overlaid (SENDR to SPEED). ; -ARC_JPTBL: - JP AR_SENDR ;send character (via pop psw) - JP AR_CAROK ;test for carrier - JP AR_MDIN ;receive data byte - JP AR_GETCHR ;get character from modem - JP AR_RCVRDY ;check receive ready - JP AR_SNDRDY ;check send ready - JP AR_SPEED ;get speed value for file transfer time +S_JPTBL: + JP S_SENDR ; send character (via pop psw) + JP S_CAROK ; test for carrier + JP S_MDIN ; receive data byte + JP S_GETCHR ; get character from modem + JP S_RCVRDY ; check receive ready + JP S_SNDRDY ; check send ready + JP S_SPEED ; get speed value for file transfer time +; +;----------------------------------------------------------------------- +; +; SIO initialization +; +S_INIT: + LD HL,12000 ; Receive loop timeout scalar + LD (RCVSCL),HL ; ... for UART RCVRDY timing +; + ; Suppress interrupts + LD A,01H ; WR1 + OUT (S_CTLP),A ; Select WR1 + XOR A ; No interrupts + OUT (S_CTLP),A ; Do it +; + LD HL,S_JPTBL + LD DE,S_LBL + JP MINIT_RET ; ;----------------------------------------------------------------------- ; ; Send character on top of stack ; -AR_SENDR: +S_SENDR: POP AF ; get character to send from stack - OUT0 (AR_DATO),A ; send to port + OUT (S_DATO),A ; send to port RET ; ;----------------------------------------------------------------------- ; -; Test and rep;ort carrier status, Z set if carrier present +; Test and report carrier status, Z set if carrier present ; -AR_CAROK: +S_CAROK: XOR A ; not used, always indicate present RET ; @@ -546,9 +641,9 @@ AR_CAROK: ; ; Get a character (assume character ready has already been tested) ; -AR_MDIN: -AR_GETCHR: - IN0 A,(AR_DATP) ; read character from port +S_MDIN: +S_GETCHR: + IN A,(S_DATP) ; read character from port RET ; ;----------------------------------------------------------------------- @@ -557,55 +652,40 @@ AR_GETCHR: ; Error code returned in A register ; *** Error code does not seem to be used *** ; -AR_RCVRDY: - IN0 A,(AR_CTLP) ; get modem status - PUSH BC ; save scratch register - PUSH AF ; save full status on stack - AND A_FRME | A_OVRE | A_PARE ; isolate line err bits - LD B,A ; save err status in B - - ; Z180 ASCI ports will stall if there are errors. - ; Error bits are NOT cleared by merely reading - ; the status register. Below, bit 3 of ASCI - ; control register is written with a zero to - ; clear error(s) if needed. - JP Z,A_RCVRDY2 ; if no errs, continue - IN0 A,(AR_CTL2) ; get current control register - AND 0F7H ; force err reset bit to zero - OUT0 (AR_CTL2),A ; write control register - -AR_RCVRDY2: - POP AF ; get full status back - AND A_RCVB ; isolate ready bit - CP A_RCVR ; test it (set flags) - LD A,B ; get the error code back - POP BC ; restore scratch register +S_RCVRDY: + ;XOR A + ;OUT (S_CTLP),A ; select WR0 + IN A,(S_CTLP) ; get status + AND S_RCVB ; isolate ready bit + CP S_RCVR ; test it (set flags) + LD A,0 ; report no line errors RET ; ;----------------------------------------------------------------------- ; ; Test for ready to send a character, Z = ready ; -AR_SNDRDY: - IN A,(AR_CTLP) ; get status - AND A_SNDB ; isolate transmit ready bit - CP A_SNDR ; test for ready value +S_SNDRDY: + ;XOR A + ;OUT (S_CTLP),A ; select WR0 + IN A,(S_CTLP) ; get status + AND S_SNDB ; isolate ready bit + CP S_SNDR ; test it (set flags) + LD A,0 ; report no line errors RET ; ;----------------------------------------------------------------------- ; ; Report baud rate (index into SPTBL returned in register A) ; -AR_SPEED: +S_SPEED: LD A,8 ; arbitrarily return 9600 baud RET ; ;======================================================================= ;======================================================================= ; -; HBIOS CONSOLE (COM0:) -; -; Will be used for all RC2014 systems +; HBIOS Console (COM0:) ; ;======================================================================= ;======================================================================= @@ -614,20 +694,32 @@ AR_SPEED: ; table at program startup. See MINIT above. Note that only a ; subset of the jump table is overlaid (SENDR to SPEED). ; -HBIOS_JPTBL: - JP HB_SENDR ;send character (via pop psw) - JP HB_CAROK ;test for carrier - JP HB_MDIN ;receive data byte - JP HB_GETCHR ;get character from modem - JP HB_RCVRDY ;check receive ready - JP HB_SNDRDY ;check send ready - JP HB_SPEED ;get speed value for file transfer time +H_JPTBL: + JP H_SENDR ; send character (via pop psw) + JP H_CAROK ; test for carrier + JP H_MDIN ; receive data byte + JP H_GETCHR ; get character from modem + JP H_RCVRDY ; check receive ready + JP H_SNDRDY ; check send ready + JP H_SPEED ; get speed value for file transfer time +; +;----------------------------------------------------------------------- +; +; HBIOS initialization +; +H_INIT: + LD HL,1250 ; Smaller receive loop timeout scalar + LD (RCVSCL),HL ; ... to compensate for BIOS overhead +; + LD HL,H_JPTBL + LD DE,H_LBL + JP MINIT_RET ; ;----------------------------------------------------------------------- ; ; Send character on top of stack ; -HB_SENDR: +H_SENDR: POP AF ; get character to send from stack PUSH BC PUSH DE @@ -643,9 +735,9 @@ HB_SENDR: ; ;----------------------------------------------------------------------- ; -; Test and rep;ort carrier status, Z set if carrier present +; Test and report carrier status, Z set if carrier present ; -HB_CAROK: +H_CAROK: XOR A ; not used, always indicate present RET ; @@ -655,23 +747,23 @@ HB_CAROK: ; ; This routine must NOT block. ; -HB_MDIN: -HB_GETCHR: +H_MDIN: +H_GETCHR: PUSH BC PUSH DE PUSH HL LD B,02H ; HBIOS IST function LD C,0 ; console is unit 0 by fiat RST 08 ; HBIOS call, A := bytes pending - JR NZ,HB_MDIN1 ; If char(s) waiting, go get it + JR NZ,H_MDIN1 ; If char(s) waiting, go get it XOR A ; otherwise, return null - JR HB_MDIN2 ; and done -HB_MDIN1: + JR H_MDIN2 ; and done +H_MDIN1: LD B,00H ; HBIOS IN function LD C,0 ; console is unit 0 by fiat RST 08 ; HBIOS call LD A,E ; byte received to A -HB_MDIN2: +H_MDIN2: POP HL POP DE POP BC @@ -683,7 +775,7 @@ HB_MDIN2: ; Error code returned in A register ; *** Error code does not seem to be used *** ; -HB_RCVRDY: +H_RCVRDY: PUSH BC PUSH DE PUSH HL @@ -703,7 +795,7 @@ HB_RCVRDY: ; ; Test for ready to send a character, Z = ready ; -HB_SNDRDY: +H_SNDRDY: PUSH BC PUSH DE PUSH HL @@ -722,8 +814,107 @@ HB_SNDRDY: ; ; Report baud rate (index into SPTBL returned in register A) ; -HB_SPEED: +H_SPEED: LD A,8 ; arbitrarily return 9600 baud RET +; +; +;======================================================================= +;======================================================================= +; +; WILL SOWERBUTTS ECB USB-FIFO +; +;======================================================================= +;======================================================================= +; +UF_BASE EQU 0CH +UF_DATA EQU (UF_BASE+0) +UF_STATUS EQU (UF_BASE+1) +UF_SEND_IMM EQU (UF_BASE+2) +; +; Following jump table is dynamically patched over initial jump +; table at program startup. See MINIT above. Note that only a +; subset of the jump table is overlaid (SENDR to SPEED). +; +UF_JPTBL: + JP UF_SENDR ; send character (via pop psw) + JP UF_CAROK ; test for carrier + JP UF_MDIN ; receive data byte + JP UF_GETCHR ; get character from modem + JP UF_RCVRDY ; check receive ready + JP UF_SNDRDY ; check send ready + JP UF_SPEED ; get speed value for file transfer time +; +;----------------------------------------------------------------------- +; +; USB-FIFO initialization +; +UF_INIT: + LD HL,12000 ; Receive loop timeout scalar + LD (RCVSCL),HL ; ... for UART RCVRDY timing +; + LD HL,UF_JPTBL + LD DE,UF_LBL + JP MINIT_RET +; +;----------------------------------------------------------------------- +; +; Send character on top of stack +; +UF_SENDR: + + POP AF ; get character to send from stack + OUT (UF_DATA),A ; WRITE TO FIFO + OUT (UF_SEND_IMM),A ; SEND IMMEDIATE + RET +; +;----------------------------------------------------------------------- +; +; Test and report carrier status, Z set if carrier present +; +UF_CAROK: + XOR A ; not used, always indicate present + RET +; +;----------------------------------------------------------------------- +; +; Get a character (assume character ready has already been tested) +; +; This routine must NOT block. +; +UF_MDIN: +UF_GETCHR: + IN A,(UF_DATA) ; GET CHAR + RET +; +;----------------------------------------------------------------------- +; +; Test for character ready to receive, Z = ready +; Error code returned in A register +; *** Error code does not seem to be used *** +; +UF_RCVRDY: + IN A,(UF_STATUS) ; B7=0 IF CHAR AVAIL, =1 IF NO CHAR. + RLCA ; B0=0 IF CHAR AVAIL, =1 IF NO CHAR. + AND 00000001B ; A=0, ZF=1 IF NO CHAR, A=1, ZF=0 IF CHAR AVAIL, + LD A,0 + RET +; +;----------------------------------------------------------------------- +; +; Test for ready to send a character, Z = ready +; +UF_SNDRDY: + IN A,(UF_STATUS) ; Bit 0=0 IF SPACE AVAIL, =1 IF FULL + AND 00000001B ; A=0, ZF=1 IF SPACE AVAIL, A=1, ZF=0 IF FULL. + RET +; +;----------------------------------------------------------------------- +; +; Report baud rate (index into SPTBL returned in register A) +; +UF_SPEED: + LD A,8 ; arbitrarily return 9600 baud + RET ; END diff --git a/Source/Apps/XM/xmuf.180 b/Source/Apps/XM/xmuf.180 new file mode 100644 index 00000000..53b52d86 --- /dev/null +++ b/Source/Apps/XM/xmuf.180 @@ -0,0 +1,331 @@ +;======================================================================= +; +; XMUF.Z80 - XMODEMXX PATCH FILE FOR ECB USB-FIFO +; +; Phil Summers - difficultylevelhigh@gmail.com +; Updated: 2019-08-16 +; +;======================================================================= +; +; Overlay file is Z80, build with M80: +; M80 =XMHB +; L80 XMHB,XMHB/N/X/E +; + .Z80 + ASEG +; +NO EQU 0 +YES EQU NOT NO +; +ERRDET EQU NO ; detect parity/framing/overrun errs +; +BASE EQU 100H ; start of cp/m normal program area +; +BDOS EQU 00005H ; BDOS function dispatch vector +; +;======================================================================= +; +; Jump table: The jump table must be in exactly the same sequence as the +; one in XMODEM. Note the ORG of 103H - This jump table has no jump to +; 'BEGIN'. +; + ORG BASE + 3 ;start after 'JMP BEGIN' +; + JP CONOUT ;must be 00000h if not used, see below + JP MINIT ;initialization routine (if needed) + JP UNINIT ;undo whatever 'MINIT' did (or return) +JPTBL: + JP SENDR ;send character (via pop psw) + JP CAROK ;test for carrier + JP MDIN ;receive data byte + JP GETCHR ;get character from modem + JP RCVRDY ;check receive ready + JP SNDRDY ;check send ready + JP SPEED ;get speed value for file transfer time + JP EXTRA1 ;extra for custom routine + JP EXTRA2 ;extra for custom routine + JP EXTRA3 ;extra for custom routine +; +;----------------------------------------------------------------------- +; +; Output character to console +; +CONOUT EQU 0 ; not used +; +;----------------------------------------------------------------------- +; +; Initialize modem +; +; This procedure has been usurped to dynamically detect the type +; of system we are running on and install the *real* jump table +; entries as appropriate. +; +MINIT: +; + ; Announce + LD DE,RBC ; RetroBrew Computers + LD C,9 ; BDOS string display function + CALL BDOS ; Do it +; + ; Identify BIOS (RomWBW HBIOS or UNA UBIOS) + CALL IDBIO ; 1=HBIOS, 2=UBIOS + LD (BIOID),A ; Save it + DEC A ; Test for HBIOS + JR Z,HINIT ; Do HBIOS setup + DEC A ; Test for UBIOS + JR Z,UINIT ; Do UBIOS setup +; + ; Neither UNA nor RomWBW + LD DE,BIOERR ; BIOS error message + LD C,9 ; BDOS string display function + CALL BDOS ; Do it + JP 0 ; Bail out! +; +HINIT: +; + ; Display RomWBW notification string + LD DE,HBTAG ; BIOS notification string + LD C,9 ; BDOS string display function + CALL BDOS ; Do it +; + ; Get platform id from RomWBW HBIOS and save it + LD B,0F1H ; HBIOS VER function 0xF1 + LD C,0 ; Required reserved value + RST 08 ; Do it, L := Platform ID + LD A,L ; Move to A + LD (PLTID),A ; Save it +; + ; Get CPU speed from RomWBW HBIOS and save it + LD B,0F8H ; HBIOS SYSGET function 0xF8 + LD C,0F0H ; CPUINFO subfunction 0xF0 + RST 08 ; Do it, L := CPU speed in MHz + LD A,L ; Move it to A + LD (CPUSPD),A ; Save it + JR MINIT1 ; Continue general initialization +; +UINIT: +; + ; Display UNA notification string + LD DE,UBTAG ; BIOS notification string + LD C,9 ; BDOS string display function + CALL BDOS ; Do it +; + ; Get CPU speed from UNA and save it + LD C,0F8H ; UNA BIOS Get PHI function + RST 08 ; Returns speed in Hz in DE:HL + LD B,4 ; Divide MHz in DE:HL by 100000H +UINIT1: + SRL D ; ... to get approx CPU speed in + RR E ; ...MHz. Throw away HL, and + DJNZ UINIT1 ; ...right shift DE by 4. + INC E ; Fix up for value truncation + LD A,E ; Put in A + LD (CPUSPD),A ; Save it + JR MINIT1 ; Continue general initialization +; +MINIT1: + LD HL,1250 ; Smaller receive loop timeout scalar + LD (RCVSCL),HL ; ... to compensate for BIOS overhead + LD HL,UF_JPTBL ; HBIOS jump table address + LD DE,USB_FIFO ; HBIOS console notification string + JR MINIT3 ; Complete the initialization +; +MINIT3: + PUSH HL ; Save HL + + ; Display port notification string + LD C,9 ; BDOS string display function + CALL BDOS ; Do it +; + ; Newline + LD C,9 ; BDOS string display function + LD DE,CRLF ; Newline + CALL BDOS ; Do it +; + ; Copy real vectors into active jump table + POP HL ; Recover HL + LD DE,JPTBL ; Real jump table is destination + LD BC,7 * 3 ; Copy 7 3-byte entries + LDIR ; Do the copy +; + ; Return with CPU speed in A + LD A,(CPUSPD) ; A := CPU speed in MHz + LD HL,(RCVSCL) ; HL := receive scalar + + RET ; and return +; +; Identify active BIOS. RomWBW HBIOS=1, UNA UBIOS=2, else 0 +; +IDBIO: +; + ; Check for UNA (UBIOS) + LD A,(0FFFDH) ; fixed location of UNA API vector + CP 0C3H ; jp instruction? + JR NZ,IDBIO1 ; if not, not UNA + LD HL,(0FFFEH) ; get jp address + LD A,(HL) ; get byte at target address + CP 0FDH ; first byte of UNA push ix instruction + JR NZ,IDBIO1 ; if not, not UNA + INC HL ; point to next byte + LD A,(HL) ; get next byte + CP 0E5H ; second byte of UNA push ix instruction + JR NZ,IDBIO1 ; if not, not UNA, check others + LD A,2 ; UNA BIOS id = 2 + RET ; and done +; +IDBIO1: + ; Check for RomWBW (HBIOS) + LD HL,(0FFFEH) ; HL := HBIOS ident location + LD A,'W' ; First byte of ident + CP (HL) ; Compare + JR NZ,IDBIO2 ; Not HBIOS + INC HL ; Next byte of ident + LD A,~'W' ; Second byte of ident + CP (HL) ; Compare + JR NZ,IDBIO2 ; Not HBIOS + LD A,1 ; HBIOS BIOS id = 1 + RET ; and done +; +IDBIO2: + ; No idea what this is + XOR A ; Setup return value of 0 + RET ; and done +; +; +; +BIOID DB 0 ; BIOS ID, 1=HBIOS, 2=UBIOS +PLTID DB 0 ; Platform ID +CPUSPD DB 10 ; CPU speed in MHz +RCVSCL DW 2800 ; RECV loop timeout scalar +; +RBC DB "RBC, 16-Aug-2018$" +; +USB_FIFO DB ", USB-FIFO$" +; +UBTAG DB " [UNA]$" +HBTAG DB " [WBW]$" +; +CRLF DB 13, 10, "$" +; +BIOERR DB 13, 10, 13, 10, "++ Unknown BIOS ++", 13, 10, "$" +; +;----------------------------------------------------------------------- +; +; Uninitialize modem +; +UNINIT: + LD A,(BIOID) + CP 1 ; Is HBIOS? + RET NZ ; If not, just return + + ; Reset character device 0 + LD B,04H ; HBIOS CIOINIT function 0x04 + LD C,0 ; Unit = 0 + LD DE,-1 ; Reset w/ current settings + RST 08 ; Do it + RET ; not initialized, so no 'UN-INITIALIZE' +; +;----------------------------------------------------------------------- +; +; The following are all dummy routines that are unused because MINIT +; dynamically installs the real jump table. +; +SENDR: +CAROK: +MDIN: +GETCHR: +RCVRDY: +SNDRDY: +SPEED: +EXTRA1: +EXTRA2: +EXTRA3: + RET +; +;======================================================================= +;======================================================================= +; +; WILL SOWERBUTTS ECB USB-FIFO +; +;======================================================================= +;======================================================================= +; +FIFO_BASE EQU 0CH +FIFO_DATA EQU (FIFO_BASE+0) +FIFO_STATUS EQU (FIFO_BASE+1) +FIFO_SEND_IMM EQU (FIFO_BASE+2) +; +; Following jump table is dynamically patched over initial jump +; table at program startup. See MINIT above. Note that only a +; subset of the jump table is overlaid (SENDR to SPEED). +; +UF_JPTBL: + JP UF_SENDR ;send character (via pop psw) + JP UF_CAROK ;test for carrier + JP UF_MDIN ;receive data byte + JP UF_GETCHR ;get character from modem + JP UF_RCVRDY ;check receive ready + JP UF_SNDRDY ;check send ready + JP UF_SPEED ;get speed value for file transfer time +; +;----------------------------------------------------------------------- +; +; Send character on top of stack +; +UF_SENDR: + + POP AF ; get character to send from stack + OUT (FIFO_DATA),A ; WRITE TO FIFO + OUT (FIFO_SEND_IMM),A ; SEND IMMEDIATE + RET +; +;----------------------------------------------------------------------- +; +; Test and report carrier status, Z set if carrier present +; +UF_CAROK: + XOR A ; not used, always indicate present + RET +; +;----------------------------------------------------------------------- +; +; Get a character (assume character ready has already been tested) +; +; This routine must NOT block. +; +UF_MDIN: +UF_GETCHR: + IN A,(FIFO_DATA) ; GET CHAR + RET +; +;----------------------------------------------------------------------- +; +; Test for character ready to receive, Z = ready +; Error code returned in A register +; *** Error code does not seem to be used *** +; +UF_RCVRDY: + IN A,(FIFO_STATUS) ; B7=0 IF CHAR AVAIL, =1 IF NO CHAR. + RLCA ; B0=0 IF CHAR AVAIL, =1 IF NO CHAR. + AND 00000001B ; A=0, ZF=1 IF NO CHAR, A=1, ZF=0 IF CHAR AVAIL, + LD A,0 + RET +; +;----------------------------------------------------------------------- +; +; Test for ready to send a character, Z = ready +; +UF_SNDRDY: + IN A,(FIFO_STATUS) ; Bit 0=0 IF SPACE AVAIL, =1 IF FULL + AND 00000001B ; A=0, ZF=1 IF SPACE AVAIL, A=1, ZF=0 IF FULL. + RET +; +;----------------------------------------------------------------------- +; +; Report baud rate (index into SPTBL returned in register A) +; +UF_SPEED: + LD A,8 ; arbitrarily return 9600 baud + RET +; + END diff --git a/Source/BPBIOS/@WBW Issues.txt b/Source/BPBIOS/@WBW Issues.txt new file mode 100644 index 00000000..f7d12f4c --- /dev/null +++ b/Source/BPBIOS/@WBW Issues.txt @@ -0,0 +1,15 @@ +Loader uses CBIOS Disk I/O prior to CBOOT/WBOOT being run. As a +result, DIOBUF is not properly initialized. At present, it is +initialized to $7C00 which will work unless the location of the +physical disk buffer in HBIOS ever changes! + +The clock drivers supplied with BPBIOS (LDDS, LDP2D, and LDNZT) +load into ZSYS user memory. If the user segment is not at the +same location as the original BPBIOS ($E900), then the clock +driver will just overlay other code. At present, the N config +is OK because it does not relocate the user segment, but the T +config fails because it uses a user segment at $E700. Note that +this does not affect ZSDOS2 variants because they do not require +a loadable clock driver. + +BPBIOS needs to assign disk units dynamically via discovery of disk type diff --git a/Source/BPBIOS/@WBW Z3ENV.txt b/Source/BPBIOS/@WBW Z3ENV.txt index 0b8bb617..8c17e2ca 100644 --- a/Source/BPBIOS/@WBW Z3ENV.txt +++ b/Source/BPBIOS/@WBW Z3ENV.txt @@ -75,7 +75,7 @@ should be consulted to understand these. The build process used here produces several different configurations which can be loaded at runtime. The original distributed memory segment configuration occupies the top of memory which, unfortunately, conflicts with the RomWBW HBIOS need to -occupt this space. +occupy this space. Although RomWBW HBIOS is implemented in it's own dedicated memory bank, it requires a small proxy at the top of memory which acts as a mechansim to diff --git a/Source/BPBIOS/Build.cmd b/Source/BPBIOS/Build.cmd index db50c0d7..54186541 100644 --- a/Source/BPBIOS/Build.cmd +++ b/Source/BPBIOS/Build.cmd @@ -3,6 +3,8 @@ setlocal setlocal & cd ZCPR33 && call Build || exit /b 1 & endlocal +setlocal & cd Z34RCP11 && call Build || exit /b 1 & endlocal +setlocal & cd NZFCP13 && call Build || exit /b 1 & endlocal set PATH=%PATH%;..\..\Tools\zx;..\..\Tools\cpmtools; @@ -26,22 +28,22 @@ call :makebp 41nbnk rem pause -cpmrm.exe -f wbw_hd0 ../../Binary/hd0.img 0:ws*.* - -cpmrm.exe -f wbw_hd0 ../../Binary/hd0.img 0:*.img -cpmcp.exe -f wbw_hd0 ../../Binary/hd0.img *.img 0: - -cpmrm.exe -f wbw_hd0 ../../Binary/hd0.img 0:*.rel -cpmcp.exe -f wbw_hd0 ../../Binary/hd0.img *.rel 0: - -rem cpmrm.exe -f wbw_hd0 ../../Binary/hd0.img 0:*.dat -rem cpmcp.exe -f wbw_hd0 ../../Binary/hd0.img *.dat 0: - -cpmrm.exe -f wbw_hd0 ../../Binary/hd0.img 0:*.zex -cpmcp.exe -f wbw_hd0 ../../Binary/hd0.img *.zex 0: - -cpmrm.exe -f wbw_hd0 ../../Binary/hd0.img 0:myterm.z3t -cpmcp.exe -f wbw_hd0 ../../Binary/hd0.img myterm.z3t 0:myterm.z3t +rem cpmrm.exe -f wbw_hd0 ../../Binary/hd_bp.img 0:ws*.* +rem +rem cpmrm.exe -f wbw_hd0 ../../Binary/hd_bp.img 0:*.img +rem cpmcp.exe -f wbw_hd0 ../../Binary/hd_bp.img *.img 0: +rem +rem cpmrm.exe -f wbw_hd0 ../../Binary/hd_bp.img 0:*.rel +rem cpmcp.exe -f wbw_hd0 ../../Binary/hd_bp.img *.rel 0: +rem +rem rem cpmrm.exe -f wbw_hd0 ../../Binary/hd_bp.img 0:*.dat +rem rem cpmcp.exe -f wbw_hd0 ../../Binary/hd_bp.img *.dat 0: +rem +rem cpmrm.exe -f wbw_hd0 ../../Binary/hd_bp.img 0:*.zex +rem cpmcp.exe -f wbw_hd0 ../../Binary/hd_bp.img *.zex 0: +rem +rem cpmrm.exe -f wbw_hd0 ../../Binary/hd_bp.img 0:myterm.z3t +rem cpmcp.exe -f wbw_hd0 ../../Binary/hd_bp.img myterm.z3t 0:myterm.z3t goto :eof @@ -57,6 +59,8 @@ rem if exist bpbio-ww.rel del bpbio-ww.rel zx ZMAC -BPBIO-WW -/P if exist bp%VER%.prn del bp%VER%.prn ren bpbio-ww.prn bp%VER%.prn +ren bpbio-ww.err bp%VER%.err +copy bpbio-ww.rel bp%VER%.rel rem pause diff --git a/Source/BPBIOS/Clean.cmd b/Source/BPBIOS/Clean.cmd index 62c3210f..f713819a 100644 --- a/Source/BPBIOS/Clean.cmd +++ b/Source/BPBIOS/Clean.cmd @@ -8,5 +8,8 @@ if exist *.img del *.img if exist bp*.rel del bp*.rel if exist zcpr33*.rel del zcpr33*.rel if exist *.bak del *.bak +if exist def-ww.lib del def-ww.lib setlocal & cd ZCPR33 && call Clean.cmd & endlocal +setlocal & cd Z34RCP11 && call Clean.cmd & endlocal +setlocal & cd NZFCP13 && call Clean.cmd & endlocal diff --git a/Source/BPBIOS/Makefile b/Source/BPBIOS/Makefile new file mode 100644 index 00000000..d1b0c403 --- /dev/null +++ b/Source/BPBIOS/Makefile @@ -0,0 +1,47 @@ +VERSIONS = \ + 33t 33tbnk \ + 33n 33nbnk \ + 34t 34tbnk \ + 34n 34nbnk \ + 41tbnk 41nbnk + +HD0IMG = ../../Binary/hd_bp.img +IMGFILES = $(foreach ver,$(VERSIONS),bp$(ver).img) +DISTFILES = *.zex *.rel myterm.z3t + +OTHERS = zcpr33n.rel zcpr33t.rel \ + bpbio-ww.rel bpsys.dat bpsys.bak bpbio-ww.err def-ww.lib *.img + +TOOLS = ../../Tools + +SUBDIRS = ZCPR33 NZFCP13 Z34RCP11 +include $(TOOLS)/Makefile.inc + +$(HD0IMG): $(IMGFILES) + if [ -f $(HD0IMG) ] ; then \ + for f in $(IMGFILES) $(DISTFILES) ; do \ + $(BINDIR)/cpmrm -f wbw_hd0 $(HD0IMG) 0:$$f ; \ + done ; \ + $(CPMCP) -f wbw_hd0 $(HD0IMG) $(IMGFILES) $(DISTFILES) 0: ; \ + fi + +zcpr33n.rel zcpr33t.rel: + (cd ZCPR33 ; make) + +all:: $(HD0IMG) + +clobber:: + @rm -f $(HD0IMG) + +%.img: zcpr33n.rel zcpr33t.rel + $(eval VER := $(subst .img,,$(subst bp,,$@))) + cp def-ww-z$(VER).lib def-ww.lib + rm -f bpbio-ww.rel + $(ZXCC) $(CPM)/ZMAC -BPBIO-WW -/P + mv bpbio-ww.prn bp$(VER).prn + cp bp$(VER).dat bpsys.dat + $(ZXCC) ./bpbuild.com -bpsys.dat 0 < bpbld1.rsp + cp bpsys.img bpsys.dat + $(ZXCC) ./bpbuild.com -bpsys.dat 0 < bpbld2.rsp + mv bpsys.img bp$(VER).img + diff --git a/Source/BPBIOS/NZFCP13/Build.cmd b/Source/BPBIOS/NZFCP13/Build.cmd new file mode 100644 index 00000000..2c53cfd9 --- /dev/null +++ b/Source/BPBIOS/NZFCP13/Build.cmd @@ -0,0 +1,11 @@ +@echo off +setlocal + +set PATH=%PATH%;..\..\..\Tools\zx;..\..\..\Tools\cpmtools; + +set ZXBINDIR=../../../tools/cpm/bin/ +set ZXLIBDIR=../../../tools/cpm/lib/ +set ZXINCDIR=../../../tools/cpm/include/ + +zx Z80ASM -nzfcp13/MF +rem zx ZMAC -nzfcp13.z80 -/P diff --git a/Source/BPBIOS/NZFCP13/Clean.cmd b/Source/BPBIOS/NZFCP13/Clean.cmd new file mode 100644 index 00000000..a088f4e8 --- /dev/null +++ b/Source/BPBIOS/NZFCP13/Clean.cmd @@ -0,0 +1,7 @@ +@echo off +setlocal + +if exist *.prn del *.prn +if exist *.lst del *.lst +if exist *.err del *.err +if exist *.rel del *.rel diff --git a/Source/BPBIOS/NZFCP13/Makefile b/Source/BPBIOS/NZFCP13/Makefile new file mode 100644 index 00000000..614a877d --- /dev/null +++ b/Source/BPBIOS/NZFCP13/Makefile @@ -0,0 +1,5 @@ +OBJECTS = nzfcp13.rel +OTHERS = +TOOLS = ../../../Tools + +include $(TOOLS)/Makefile.inc diff --git a/Source/BPBIOS/NZFCP13/fcp-4.zrl b/Source/BPBIOS/NZFCP13/fcp-4.zrl new file mode 100644 index 00000000..a6f2c9a2 Binary files /dev/null and b/Source/BPBIOS/NZFCP13/fcp-4.zrl differ diff --git a/Source/BPBIOS/NZFCP13/fcp-4t.zrl b/Source/BPBIOS/NZFCP13/fcp-4t.zrl new file mode 100644 index 00000000..8d3cb48c Binary files /dev/null and b/Source/BPBIOS/NZFCP13/fcp-4t.zrl differ diff --git a/Source/BPBIOS/NZFCP13/fcp-5.zrl b/Source/BPBIOS/NZFCP13/fcp-5.zrl new file mode 100644 index 00000000..a2456212 Binary files /dev/null and b/Source/BPBIOS/NZFCP13/fcp-5.zrl differ diff --git a/Source/BPBIOS/NZFCP13/fcp-5t.zrl b/Source/BPBIOS/NZFCP13/fcp-5t.zrl new file mode 100644 index 00000000..eb94964c Binary files /dev/null and b/Source/BPBIOS/NZFCP13/fcp-5t.zrl differ diff --git a/Source/BPBIOS/NZFCP13/fcp-6.zrl b/Source/BPBIOS/NZFCP13/fcp-6.zrl new file mode 100644 index 00000000..6aa1aea9 Binary files /dev/null and b/Source/BPBIOS/NZFCP13/fcp-6.zrl differ diff --git a/Source/BPBIOS/NZFCP13/fcp-6t.zrl b/Source/BPBIOS/NZFCP13/fcp-6t.zrl new file mode 100644 index 00000000..b75fbfb7 Binary files /dev/null and b/Source/BPBIOS/NZFCP13/fcp-6t.zrl differ diff --git a/Source/BPBIOS/NZFCP13/fcp-7t.zrl b/Source/BPBIOS/NZFCP13/fcp-7t.zrl new file mode 100644 index 00000000..8a12be09 Binary files /dev/null and b/Source/BPBIOS/NZFCP13/fcp-7t.zrl differ diff --git a/Source/BPBIOS/NZFCP13/nzfcp.doc b/Source/BPBIOS/NZFCP13/nzfcp.doc new file mode 100644 index 00000000..1eb1b0ab --- /dev/null +++ b/Source/BPBIOS/NZFCP13/nzfcp.doc @@ -0,0 +1,89 @@ + Z-Relocatable Flow Control Packages + 11 October 89 by Carson Wilson + +The seven pre-compiled Flow Control Packages (FCP's) in this library may be +loaded directly to Z System with NZ-COM, Z3PLUS, or JetLDR, provided +sufficient space has been allocated to the FCP segment. The FCP's come in two +flavors. Files named FCP-nT.ZRL implement extended flow control processing +when needed using a program named IF.COM at the root of your path, or at +directory A0 if there is no path. Files named simply FCP-n.ZRL rely +exclusively on memory-based processing. The "n" in the filename indicates the +total number of records required by each package. The standard number of +records for both NZ-COM and Z3PLUS is currently 5, so if you wish to load a +larger FCP you must reconfigure your system with MKZCM or by editing your .Z3P +file. For general help with flow control commands, see the file IF.HLP, or +section 3.2.2 of your NZ-COM or Z3PLUS manual. + +While IF.COM allows options not available in memory-resident flow-control +processing, fully resident versions free the user from the requirement that +IF.COM be present during flow control processing. Resident processing is also +somewhat faster due to the need to load IF.COM from disk for each flow control +command. Finally, the ability to locate and load the transient IF.COM +requires considerable code space in the resident code itself. Generally, +IF.COM is best used on fixed-disk systems, while resident processing is more +suited to floppy-based systems. + +The options included in fully resident versions were selected somewhat +differently than those of those using IF.COM. Criteria, in order of +precedence, were as follows: + +Transient Versions Resident Versions +------------------ ----------------- +1. Is the feature available in 1. How useful is the feature? + IF.COM? 2. How much memory does the feature +2. Does the feature require disk require? + access? +3. How useful is the feature? +4. How much memory does the feature + require? + +The tables below summarize the size and features of each flavor of FCP. Each +package includes only the features appearing above it. For example, +FCP-5T.ZRL (the default FCP for both NZ-COM and Z3PLUS) contains only ZIF, +IFQ, OR, AND, negation, ERROR, and NULL. Size is expressed as records plus +remaining bytes required by the FCP. As FCP space is allocated in record +units only, I have attempted to include only combinations which leave as +little remaining space possible. Sizes without corresponding filenames are +included for reference purposes only. + +FEATURES OF TRANSIENT (IF.COM) FCPS SIZE(RECS+BYTES) FILE NAME +----------------------------------------------------------------------------- +ZIF - unconditionally clear IF states? +IFQ - show current if status? 3+124 FCP-4T.ZRL +OR - set state at current level? 4+25 +AND - reset state at current level? +Allow negation of conditions? +ERROR - test program error flag? +NULL - test for no file name? 4+84 FCP-5T.ZRL +REG - test register values? 5+16 +AMBIG - test for "?" in file spec? +COMPR - test for compressed filespec? += - test tokens for equality? 5+92 FCP-6T.ZRL +INPUT - test user input? 6+25 +Allow "IF T" and "IF F" forms? +WHEEL - test wheel byte? +TCAP - test whether TCAP loaded? +EXIST - test for file existence? 6+106 FCP-7T.ZRL +EMPTY - test files for contents? 7+10 + + +FEATURES OF RESIDENT FCPS SIZE(RECS+BYTES) FILE NAME +----------------------------------------------------------------------------- +ZIF - unconditionally clear IF states? +IFQ - show current if status? +OR - set state at current level? +AND - reset state at current level? +Allow negation of conditions? +NULL - test for no file name? +INPUT - test user input? += - test tokens for equality? +ERROR - test program error flag? +Allow "IF T" and "IF F" forms? +EXIST - test for file existence? 3+123 FCP-4.ZRL +AMBIG - test for "?" in file spec? +COMPR - test for compressed filespec? +REG - test register values? +WHEEL - test wheel byte? +TCAP - test whether TCAP loaded? 4+113 FCP-5.ZRL +EMPTY - test files for contents? 5+17 FCP-6.ZRL + \ No newline at end of file diff --git a/Source/BPBIOS/NZFCP13/nzfcp.lib b/Source/BPBIOS/NZFCP13/nzfcp.lib new file mode 100644 index 00000000..db55f33f --- /dev/null +++ b/Source/BPBIOS/NZFCP13/nzfcp.lib @@ -0,0 +1,161 @@ +; Module: NZFCP.LIB +; Author: Carson Wilson +; Version: 1.3 +; Date: 9 Oct 89 +; Changes: Prompts for options if SLR true. + +; This module is a set of configuration options for the flow control package +; code in NZFCP.Z80. The options are not fully commented here; see +; "ZCPR3, The Manual" for a detailed discussion of most of them. + + +; * * * * * * N O T I C E * * * * * * +; +; The main code in Z33FCP.Z80 has been written with relative jump instructions +; for minimum code size. If the options below are changed, then some of the +; jumps may become out-of-range. You will then have to change the code based +; on the error messages you get from your assembler. I cannot think of any +; way to handle the jumps automatically (macros fail because the jump distances +; cannot be resolved fully even in two passes of the assembler). If you want +; to play things really safe, you can do a global search and replace to change +; all occurrences of the JR instruction to JP (but note that this change is +; irreversible). I prefer to live with this nuisance so that I can get the +; most out of the code. +; +; Another possible way to clear up a problem with a relative jump that is out +; of range is to take the block of code with the labels IFCTRUE and IFCFALSE +; and move it up or down in the code. It is placed somewhere in the middle of +; the resident options, so that the options can reach those entry points with +; a relative jump. You should try to place that code near the middle of the +; options which you have enabled. +; +; Jay Sage (May 17, 1987) + +;============================================================================= +; +; M A C R O S +; +;============================================================================= + +; SLR assemblers allow selection of options during assembly. + +SLR equ no + + if SLR + +y equ yes +n equ no + +select macro option default comment + .accept comment,option ; Prompt user for selection + endm + + .printx + .printx Answer "Y" to include, "N" to exclude commands: + .printx + + else + +select macro option default comment +option equ default ; Use selections from file + endm + endif ; SLR + +; ------------------------------------------------------------------------- + +; Command names + +; The CTABLE macro, which constructs the command dispatch table, includes a +; line for each command, the first parameter of which is the name of the +; command. These names may be changed if you wish. But make sure that you +; do not use a name that is longer than the maximum allowed length. If you +; do, the name will be truncated and a nonfatal error will occur during +; assembly. DO NOT CHANGE ANY PARAMETER OTHER THAN THE COMMAND NAME. Lower +; case letters will be converted to upper case. The macro COMMAND is defined +; in Z34MAC.LIB. + +cmdsize equ 4 ; Maximum length of command names + +; Command table name, enable, wheel, jump_addr + ; [ DO NOT CHANGE THESE PARAMETERS ] +ctable macro +ifcmd: command if, yes, no, ifstart + command and, andopt, no, andstart + command or, oropt, no, orstart + command else, yes, no, ifelse + command fi, yes, no, ifend + command ifq, ifqopt, no, ifstat0 + command xif, yes, no, ifexit + command zif, zifopt, no, ifzero + endm + +;----------------------------------------------------------------------------- + +; General configuration options + +NOISE equ no ; Don't display if-state messages + +select COMIF no 'Enable transient IF processing? ' + +PATHROOT equ yes ; Find transient IF in root of path + +ifdrv equ 'A' ; Drive to use if PATHROOT is off or + ; ..if the path is empty +ifusr equ 0 ; User to use if PATHROOT is off or + ; ..if the path is empty + +; --------------------------------------------------------------------- + +; Command inclusion options + +select ZIFOPT yes 'ZIF - unconditionally clear IF states? ' +select IFQOPT yes 'IFQ - show current if status? ' +select OROPT yes 'OR - set state at current level? ' +select ANDOPT yes 'AND - reset state at current level? ' + +;----------------------------------------------------------------------------- + +; If Condition Options + + if COMIF ; Different precedence if transient IF available. +select IFONEG yes 'Allow negation of conditions? ' +negchar equ '~' ; Character to use if negation allowed +select IFOERROR yes 'ERROR - test program error flag? ' +select IFONULL yes 'NULL - test for no file name? ' +select IFOREG yes 'REG - test register values? ' +select IFAMBIG yes 'AMBIG - test for "?" in file spec? ' +select IFCOMPR yes 'COMPR - test for compressed filespec? ' +select IFOEQ yes '= - test tokens for equality? ' +select IFOINPUT yes 'INPUT - test user input? ' +XEQOPT equ yes ; Test only first token for equal sign +select IFOTRUE yes 'Allow "IF T" and "IF F" forms? ' +select IFOWHEEL no 'WHEEL - test wheel byte? ' +select IFOTCAP no 'TCAP - test whether TCAP loaded? ' +select IFOEXIST yes 'EXIST - test for file existence? ' +select IFOEMPTY yes 'EMPTY - test files for contents? ' + else +select IFONEG yes 'Allow negation of conditions? ' +negchar equ '~' ; Character to use if negation allowed +select IFONULL yes 'NULL - test for no file name? ' +select IFOINPUT yes 'INPUT - test user input? ' +select IFOEQ yes '= - test tokens for equality? ' +XEQOPT equ yes ; Test only first token for equal sign +select IFOERROR yes 'ERROR - test program error flag? ' +select IFOTRUE yes 'Allow "IF T" and "IF F" forms? ' +select IFOEXIST yes 'EXIST - test for file existence? ' +select IFAMBIG no 'AMBIG - test for "?" in file spec? ' +select IFCOMPR no 'COMPR - test for compressed filespec? ' +select IFOREG no 'REG - test register values? ' +select IFOWHEEL no 'WHEEL - test wheel byte? ' +select IFOTCAP no 'TCAP - test whether TCAP loaded? ' +select IFOEMPTY no 'EMPTY - test files for contents? ' + endif ; COMIF + +; Miscellaneous configuration information + +curusr equ z3msg+2eh ; Current logged user address +curdr equ z3msg+2fh ; Current logged drive address +curint equ '$' ; Path symbol for current drive/user + +; END of NZFCP.LIB + \ No newline at end of file diff --git a/Source/BPBIOS/NZFCP13/nzfcp.lib.sav b/Source/BPBIOS/NZFCP13/nzfcp.lib.sav new file mode 100644 index 00000000..18f557f8 --- /dev/null +++ b/Source/BPBIOS/NZFCP13/nzfcp.lib.sav @@ -0,0 +1,161 @@ +; Module: NZFCP.LIB +; Author: Carson Wilson +; Version: 1.3 +; Date: 9 Oct 89 +; Changes: Prompts for options if SLR true. + +; This module is a set of configuration options for the flow control package +; code in NZFCP.Z80. The options are not fully commented here; see +; "ZCPR3, The Manual" for a detailed discussion of most of them. + + +; * * * * * * N O T I C E * * * * * * +; +; The main code in Z33FCP.Z80 has been written with relative jump instructions +; for minimum code size. If the options below are changed, then some of the +; jumps may become out-of-range. You will then have to change the code based +; on the error messages you get from your assembler. I cannot think of any +; way to handle the jumps automatically (macros fail because the jump distances +; cannot be resolved fully even in two passes of the assembler). If you want +; to play things really safe, you can do a global search and replace to change +; all occurrences of the JR instruction to JP (but note that this change is +; irreversible). I prefer to live with this nuisance so that I can get the +; most out of the code. +; +; Another possible way to clear up a problem with a relative jump that is out +; of range is to take the block of code with the labels IFCTRUE and IFCFALSE +; and move it up or down in the code. It is placed somewhere in the middle of +; the resident options, so that the options can reach those entry points with +; a relative jump. You should try to place that code near the middle of the +; options which you have enabled. +; +; Jay Sage (May 17, 1987) + +;============================================================================= +; +; M A C R O S +; +;============================================================================= + +; SLR assemblers allow selection of options during assembly. + +SLR equ yes + + if SLR + +y equ yes +n equ no + +select macro option default comment + .accept comment,option ; Prompt user for selection + endm + + .printx + .printx Answer "Y" to include, "N" to exclude commands: + .printx + + else + +select macro option default comment +option equ default ; Use selections from file + endm + endif ; SLR + +; ------------------------------------------------------------------------- + +; Command names + +; The CTABLE macro, which constructs the command dispatch table, includes a +; line for each command, the first parameter of which is the name of the +; command. These names may be changed if you wish. But make sure that you +; do not use a name that is longer than the maximum allowed length. If you +; do, the name will be truncated and a nonfatal error will occur during +; assembly. DO NOT CHANGE ANY PARAMETER OTHER THAN THE COMMAND NAME. Lower +; case letters will be converted to upper case. The macro COMMAND is defined +; in Z34MAC.LIB. + +cmdsize equ 4 ; Maximum length of command names + +; Command table name, enable, wheel, jump_addr + ; [ DO NOT CHANGE THESE PARAMETERS ] +ctable macro +ifcmd: command if, yes, no, ifstart + command and, andopt, no, andstart + command or, oropt, no, orstart + command else, yes, no, ifelse + command fi, yes, no, ifend + command ifq, ifqopt, no, ifstat0 + command xif, yes, no, ifexit + command zif, zifopt, no, ifzero + endm + +;----------------------------------------------------------------------------- + +; General configuration options + +NOISE equ no ; Don't display if-state messages + +select COMIF no 'Enable transient IF processing? ' + +PATHROOT equ yes ; Find transient IF in root of path + +ifdrv equ 'A' ; Drive to use if PATHROOT is off or + ; ..if the path is empty +ifusr equ 0 ; User to use if PATHROOT is off or + ; ..if the path is empty + +; --------------------------------------------------------------------- + +; Command inclusion options + +select ZIFOPT yes 'ZIF - unconditionally clear IF states? ' +select IFQOPT yes 'IFQ - show current if status? ' +select OROPT yes 'OR - set state at current level? ' +select ANDOPT yes 'AND - reset state at current level? ' + +;----------------------------------------------------------------------------- + +; If Condition Options + + if COMIF ; Different precedence if transient IF available. +select IFONEG yes 'Allow negation of conditions? ' +negchar equ '~' ; Character to use if negation allowed +select IFOERROR yes 'ERROR - test program error flag? ' +select IFONULL yes 'NULL - test for no file name? ' +select IFOREG yes 'REG - test register values? ' +select IFAMBIG yes 'AMBIG - test for "?" in file spec? ' +select IFCOMPR yes 'COMPR - test for compressed filespec? ' +select IFOEQ yes '= - test tokens for equality? ' +select IFOINPUT yes 'INPUT - test user input? ' +XEQOPT equ yes ; Test only first token for equal sign +select IFOTRUE yes 'Allow "IF T" and "IF F" forms? ' +select IFOWHEEL no 'WHEEL - test wheel byte? ' +select IFOTCAP no 'TCAP - test whether TCAP loaded? ' +select IFOEXIST yes 'EXIST - test for file existence? ' +select IFOEMPTY yes 'EMPTY - test files for contents? ' + else +select IFONEG yes 'Allow negation of conditions? ' +negchar equ '~' ; Character to use if negation allowed +select IFONULL yes 'NULL - test for no file name? ' +select IFOINPUT yes 'INPUT - test user input? ' +select IFOEQ yes '= - test tokens for equality? ' +XEQOPT equ yes ; Test only first token for equal sign +select IFOERROR yes 'ERROR - test program error flag? ' +select IFOTRUE yes 'Allow "IF T" and "IF F" forms? ' +select IFOEXIST yes 'EXIST - test for file existence? ' +select IFAMBIG yes 'AMBIG - test for "?" in file spec? ' +select IFCOMPR yes 'COMPR - test for compressed filespec? ' +select IFOREG yes 'REG - test register values? ' +select IFOWHEEL no 'WHEEL - test wheel byte? ' +select IFOTCAP no 'TCAP - test whether TCAP loaded? ' +select IFOEMPTY yes 'EMPTY - test files for contents? ' + endif ; COMIF + +; Miscellaneous configuration information + +curusr equ z3msg+2eh ; Current logged user address +curdr equ z3msg+2fh ; Current logged drive address +curint equ '$' ; Path symbol for current drive/user + +; END of NZFCP.LIB + \ No newline at end of file diff --git a/Source/BPBIOS/NZFCP13/nzfcp13.for b/Source/BPBIOS/NZFCP13/nzfcp13.for new file mode 100644 index 00000000..40fb0c2c --- /dev/null +++ b/Source/BPBIOS/NZFCP13/nzfcp13.for @@ -0,0 +1,9 @@ +Source code plus the compiled Flow Control Packages (FCP's) +distributed in FCP.LBR as part of the Z System. The precompiled +modules are in Z-Relocatable form, and must be loaded with JetLDR, +NZCOM, or Z3PLUS. Now features interactive assembly of code +a-la-Z34RCP11 under the SLR or ZMAC assemblers. Macro now reports +length of resulting FCP in records and bytes following assembly. +JetLDR signons also automatically generated. 9/15/90 Carson Wilson + + \ No newline at end of file diff --git a/Source/BPBIOS/NZFCP13/nzfcp13.z80 b/Source/BPBIOS/NZFCP13/nzfcp13.z80 new file mode 100644 index 00000000..fe700554 --- /dev/null +++ b/Source/BPBIOS/NZFCP13/nzfcp13.z80 @@ -0,0 +1,1424 @@ + +; Program: NZFCP +; Date: October 9, 1989 +; Author: Carson Wilson +; Version: 1.3 +; Changes: Updated & improved JetLDR signon. +; Changed four JR's to JP's. + +; Date: August 21, 1988 +; Author: Carson Wilson +; Version: 1.2 + +; Derived from: + +; Date: April 1988 +; Name changed and code modified for NZ-COM. + +; Derived from: + +; PROGRAM: Z34FCP +; AUTHOR: Jay Sage +; VERSION: 1.0 +; DATE: May 25, 1987 +; DERIVATION: FCP10 by Jay Sage (ZSIG) + +; ZCPR34 is copyright 1987 by Jay P. Sage. All rights reserved. End-user +; distribution and duplication permitted for non-commercial purposes only. +; Any commercial use of ZCPR34, defined as any situation where the duplicator +; recieves revenue by duplicating or distributing ZCPR34 by itself or in +; conjunction with any hardware or software product, is expressly prohibited +; unless authorized in writing by Jay P. Sage. + +;============================================================================= +; +; R E V I S I O N H I S T O R Y +; +;============================================================================= +; +; 21 Aug 88 Added JetLDR signon description. +; IF IN now prints ' (Y/N)? ', and accepts only Y or y or +; N or n. +; Added macro code to show FCP length following assembly. +; +; Carson Wilson. +; +; 6 April 88 Handles latest Type 4 IF.COM +; 1.2 Changed command tail loader to accept :IF. Joe Wright +; +; 12/31/87 Modified for use with Z34CMN.LIB for NZ-COM. Joe Wright. +; 1.1 +; +; 05/25/87 Created ZCPR33 version from the code I released through ZSIG. +; 1.0 This code differs only in the more efficient way in which it +; determines if it was invoked with a directory prefix that +; signals that the transient IF.COM should be used to process +; the IF command. This permits the user to force the use of a +; more powerful option processor in the transient IF.COM than in +; the resident code. Option bytes were added after the end of +; the resident option dispatch table so that SHOW can report +; configuration options to the user. +; +; FCP10 notes +; +; The transient processor can now be loaded at an address other +; than 100h so as not to interfere with code loaded in the TPA. +; Then the GO command can normally be used even after IF.COM is +; used to process the flow test. If the LOADCHK equate +; is true then the FCP will verify that the transient +; processor has been loaded to the page in memory for which +; it was assembled. If loaded to the wrong page, it will +; be reloaded to the correct one. +; +; The test for the form ARG1=ARG2 was tightened up so as not to +; be confused by an equal sign in some later part of the command +; tail (e.g., "IF REG 1 = 2"). Now only the first token +; (contiguous string of characters) is checked. This extra code +; is under the control of the XEQOPT equate. The only option +; that is still a problem is the COMIF form '~='. Since the '=' +; is in the first token, this 'not equal' condition cannot be +; distinguished from an equality test against the character '~'. +; The solution is to turn off equality testing in the resident FCP +; or to use the alternative COMIF options 'NE' or '~EQ' for this +; test. +; +; Added optional commands AND and OR. These work like IF except +; that they affect the current IF level rather than going one +; level deeper. +; +; Added optional command ZIF to zero out all IF states no matter +; whether current state is true or false (XIF only works if state +; is true. +; +; Added new optional command IFQ (if-query) and enhanced the +; IFSTAT code that is invoked when the NOISE equate is true. +; In both cases, the entire tree of IF states is now shown, +; starting with the current level. For example, IFQ might result +; in the display "IF FTT" (we are at third IF level and it is +; false; the second and first IF levels are true). If the +; current IF level is 0, then the display is "IF None". +; +; Added two new resident options: AMBIGUOUS (AM) returns true if +; the file specification in the second token has a '?' (or '*') +; in it; COMPRESSED (CO) returns true if the file specificaton in +; the second token has a 'Z' or a 'Q' in the second character of +; the file type. +; +; Howard Goldstein contributed significantly to the development +; of this code. Bridger Mitchell also offered helpful +; suggestions. +; +; Jay Sage +; +; Notes from earlier SYSFCP revisions +; +; 09/12/85 Fixed bug in my code used when IF.COM is found in a specified +; drive/user area. The values of CDISK and CUSER were not being +; set, and as a result the user was not returned to the correct +; directory. The EXIST and EMPTY tests did not work correctly +; unless a DIR: or DU: was given explicitly with each file name. +; Jay Sage + +; 08/29/85 Reorganized code so that COMIF code handles only those +; options not in the table of local IF functions. Also changed +; code to allow searching for IF.COM in a specified directory +; instead of using the ROOT of the path. Also renamed macros +; to make code ZAS compatible. +; Jay Sage + +; 07/21/85 Corrected reversed sensing of program error flag in the +; IF ERROR test. +; Jay Sage + +; 01/02/85 Revised to correct a bug in the IF EMPTY test. First, the +; current record byte was not being set to zero before trying +; to read from the file. Secondly, the test for error was not +; testing for FF but for 00. My BDOS does not return 0 for +; success. It seems to return 00, 01, 02, or 03. This made the +; file appear to be empty. +; Jay Sage + + +;============================================================================= +; +; M A C R O S A N D E Q U A T E S +; +;============================================================================= + + name ('FCP') + +; External macro references + + maclib Z34CMN.LIB ; Source of system addresses + maclib NZFCP.LIB ; Source of configuration options + maclib Z34MAC.LIB ; Z34 macros + +; Equates section + +version equ 13 + +lf equ 0ah +cr equ 0dh +bell equ 07h + +base equ 0 +wboot equ base+0000h ; CP/M warm boot address +udflag equ base+0004h ; User num in high nybble, disk in low +bdos equ base+0005h ; BDOS function call entry point +tfcb equ base+005ch ; Default FCB buffer +fcb1 equ tfcb ; 1st and 2nd FCBs +fcb2 equ tfcb+16 +tbuff equ base+0080h ; Default disk I/O buffer +tpa equ base+0100h ; Base of TPA + + +;============================================================================= +; +; J e t L D R S I G N - O N +; +;============================================================================= + +; This prints an extended ID message upon loading with JetLDR. +; These are NOT the command names. + + COM /_ID_/ + + db 'Copyright 1989 ZSA',cr,lf + db 'Commands:',cr,lf + db ' IF ELSE FI XIF ' + + if andopt + db 'AND ' + endif + if oropt + db 'OR ' + endif + if ifqopt + db 'IFQ ' + endif + if zifopt + db 'ZIF ' + endif + + db cr,lf,'Options' + if ifoneg + db ' (use "',negchar,'" to negate)' + endif + if noise + db '; (noise)' + endif + db ':',cr,lf + + if ifotrue + db ' T F ' + endif + if ifambig + db 'AMbig ' + endif + if ifcompr + db 'COmpr ' + endif + if ifoempty + db 'EMpty ' + endif + if ifoeq + db 'x=y ' + endif + if ifoerror + db 'ERror ' + endif + if ifoexist + db 'EXist ' + endif + if ifoinput + db 'INput ' + endif + if ifonull + db 'NUll ' + endif + if iforeg + db 'REgs ' + endif + if ifotcap + db 'TCap ' + endif + if ifowheel + db 'WHeel ' + endif + + if comif + db cr,lf,' Use ' + if pathroot + db 'root:' + endif + db 'IF.COM' + endif + + db 0 ; End of JetLDR sign-on message + + CSEG + +;============================================================================= + +; Start of code + +start: + db 'Z3FCP' ; Flag for Package Loader + +;============================================================================= +; +; C O M M A N D T A B L E +; +;============================================================================= + +; The command name table is structured as follows: +; +; The first byte is the number of characters in each command name. +; Next come records consisting of command names followed by entry +; point addresses for the code to process the command. Finally, +; there is a null to indicate the end of the dispatch table. + + db cmdsize ; Size of text entries +ctab: ctable ; Macro defined in NZFCP.LIB + db 0 + +;============================================================================= +; +; I F C O N D I T I O N O P T I O N S +; +;============================================================================= + +condtab: + + if ifotrue + db 'T ' ; TRUE + dw ifctrue + db 'F ' ; FALSE + dw ifcfalse + endif ; ifotrue + + if ifambig ; Ambiguous file spec + db 'AM' + dw ifcambig + endif ; ifambig + + if ifcompr ; Squeezed or crunched + db 'CO' + dw ifccompr + endif ; ifcompr + + if ifoempty + db 'EM' ; File empty + dw ifcempty + endif ; ifoempty + + if ifoerror + db 'ER' ; Error message + dw ifcerror + endif ; ifoerror + + if ifoexist + db 'EX' ; File exists + dw ifcex + endif ; ifoexist + + if ifoinput + db 'IN' ; User input + dw ifcinput + endif ; ifoinput + + if ifonull + db 'NU' + dw ifcnull + endif ; ifonull + + if ifotcap ; Z3 TCAP available + db 'TC' + dw ifctcap + endif ; ifotcap + + if ifowheel ; Wheel Byte + db 'WH' + dw ifcwheel + endif ; ifowheel + + db 0 + +; Option bytes: these option bytes can be used to convey information to +; programs such as SHOW. The first one is used to reduce the chance of +; misinterpreting data from an earlier version of the FCP that does not +; have the option bytes. The next byte tells if COMIF has been activated +; and if the root of the path will be used as the directory in which to look +; for IF.COM. If PATHROOT is not selected (or if the path is empty), then +; the specified drive/user will be used. The overflow bit in case the user +; number is greater than 15 is kept in bit 2 of the second option byte. The +; combined user/drive value is kept in the third option byte. + +highuser defl ifusr gt 15 + +opt0: db 34h ; ZCPR34 version ID +opt1: optflag highuser,pathroot,comif +opt2: db ( ifusr and 0fh ) shl 4 + ( ifdrv - 'A' ) ; user/drive flag + +;============================================================================= +; +; C O M M A N D P R O C E S S I N G C O D E +; +;============================================================================= + +; Command: ZIF +; +; This command zeros out the IF system no matter what the current +; level IF state is. + + if zifopt + +ifzero: + if noise + call nl ; Print new line + endif ; noise + + jr ifexit1 + + endif ; zifopt + +;----------------------------------------------------------------------------- + +; Command: XIF +; +; If current IF state is true, XIF terminates all IFs, restoring a basic +; TRUE state. + +ifexit: + if noise + call nl ; Print new line + endif ; noise + + call iftest ; See if current IF is running and FALSE + + if noise + jr z,ifstat ; Abort with status message if so + else ; not noise + ret z ; Or just return if false + endif ; noise + +ifexit1: + ld hl,z3msg+1 ; Pt to IF flag + ld (hl),0 ; Zero IF flag + jr ifendmsg ; Print message + +;----------------------------------------------------------------------------- + +; Command: FI +; +; FI decrements to the previous IF level. It does this by shifting the +; current-if-bit in the first 'if' message in the Z3MSG buffer right one +; position. + +ifend: + if noise + call nl ; Print new line + endif ; noise + +; ld hl,z3msg+1 ; Point to IF flag +; ld a,(hl) ; Get it +; or a ; No IF active? + + call msgbf1 + dec hl ; Save a byte over the three lines above + + jr z,ifnderr + +ifendmsg: + if noise + call print + dc 'To ' ; Prefix to status display + endif ; noise + + srl (hl) ; Adjust active bit + + if noise + jr nz,ifstat ; Print status if IF still active + endif ; noise + +ifnderr: + if noise + + call print ; Print message + dc 'No ' + jp prif + + else ; not noise + + ret + + endif ; noise + +;----------------------------------------------------------------------------- + +; Command: ELSE +; +; ELSE complements the Active Bit for the Current IF provided the +; previous IF state was true. If the previous state was false, the +; command is flushed. +; +; This is accomplished according to the following algorithm. If the +; current IF is 0 (no IF) or 1 (one IF), then take the previous state +; to be true and perform the toggle. Otherwise, test the previous +; IF level condition and toggle only if it is true. + +ifelse: + if noise and (not ifqopt) + call nl ; Print new line + endif ; noise and (not ifqopt) + + call msgbf1 ; Get current if + ld b,a ; Save in B + srl a ; Back up if pointer bit to previous IF level + jr z,iftog ; If no previous IF level, go to toggle code + and (hl) ; Determine state of previous IF level + + if noise + if ifqopt + jr z,ifstat0 ; Print status on new line + else + jr z,ifstat ; If false, just print status + endif ; Ifqopt + else ; not noise + ret z ; Or simply return + endif ; noise + +iftog: + ld a,(hl) ; Get if-status message byte + xor b ; Flip current state + ld (hl),a ; Put result back in message byte + ; ..and fall thru to print status + + if not noise + ret + endif + +;----------------------------------------------------------------------------- + +; Indicate if current IF is True or False + +ifstat0: + call nl +ifstat: + call prif ; Print 'IF ' + call msgbf1 ; Get current if byte and set flags + ld b,a ; Get it into B + jr nz,ifstat1 ; Nz means if active + + call print + dc 'None' + ret + +ifstat1: + ld a,(hl) ; Get if-status message byte + and b ; Mask in currently active IF level status + ld c,'F' ; Load with false indicator + jr z,ifstat2 ; If current IF is false, jump + ld c,'T' ; Else, load with true indicator +ifstat2: + ld a,c + call conout + + srl b ; Drop one IF level + jr nz,ifstat1 ; Loop through all IF states + ret + +;------------------------- + +; Output CRLF + +nl: call print + dc cr,lf + ret + +;----------------------------------------------------------------------------- + +; Command: OR + +; This command performs a logical or operation by updating the +; if state without going to a new level. If there are active +; IFs and the current state is true, we do nothing. Else we back +; up one level and fall through to normal IF processing. + + if oropt + +orstart: + call msgbf1 ; Get if active byte + jr z,backup ; Treat like if if no IFs active + and (hl) ; Check current state + jr z,backup ; Current STATE false so go proecess + + if noise + jr ifstat0 ; Else return and show status + else + ret ; Or just return + + endif ; Noise + endif ; Oropt + +;----------------------------------------------------------------------------- + +; Command: AND + +; This command performs a logical and operation by updating the +; if state without going to a new level. If there are active +; IFs and the current state is false, we do nothing. Else we back +; up one level and fall through to normal IF processing. + + if andopt + +andstart: + call iftest ; Test for IF running and false + if noise + jr z,ifstat0 ; Condition met, show status & return + else + ret z ; Condition met, return + endif ; Noise + endif ; Andopt + +; Common stuff for and and or + + if andopt or oropt + +backup: + dec hl ; Pt to flag byte + srl (hl) ; Drop back one level +; +; Poke "IF" into external fcb for transient +; + if comif +pokefcb: + ld de,extfcb+1 ; Pt to external fcb + ld hl,ifcmd ; Pointer to IF command in table + ld bc,cmdsize ; Length + ldir ; Move it in + + endif ; comif + +; Fall through to IF PROCESSING + endif ;Andopt or oropt + +;----------------------------------------------------------------------------- + +; FCP Command: IF +; +; If current IF state is false, then advance to next level and set it +; to false also. If current IF state is true, then test condition and +; set the next level accordingly. + +ifstart: + if not ifqopt + + ld a,(extfcb) ; NZ if explicit + ld hl,tbuff + or (hl) + jp z,ifstat0 ; Report IF status + + endif ; not ifqopt + +ifstrt: + if noise + call nl ; Print new line + endif ; noise + + call iftest ; See if current IF is running and FALSE + jP z,ifcf ; Yes, do the right thing + +; Test for presence of colon in command. If colon present, then go directly +; to COMIF processing. + + if comif + ld a,(extfcb) ; Check drive byte of external FCB + or a ; If it is zero, no colon was present + jp nz,runcomif ; If colon, go to comif processing + ; Else fall through to resident processing + endif ; comif + +;----------------------------------------------------------------------------- +; +; R E S I D E N T C O M M A N D P R O C E S S I N G +; +;----------------------------------------------------------------------------- + +resident: + +; Test for Equality if Equal Sign in Token + + if ifoeq + + ld hl,tbuff+1 + + if xeqopt ; Extended equal testing + +skipsp: ; Skip over any space to first token + ld a,(hl) + or a ; Check for end of tail + jr z,ifck0 ; If so , go on + cp ' '+1 ; Test for space or control character + jr nc,tsteq ; If not, we are at first token + inc hl ; Otherwise advance to next character + jr skipsp ; ..and continue testing + + endif ; xeqopt + +tsteq: + ld a,(hl) ; Get character from command tail + inc hl ; Point to next one + or a ; EOL? + jr z,ifck0 ; Continue if so + + if xeqopt + cp ' '+1 ; End of token? + jr c,ifck0 ; If so, go on + endif ; xeqopt + + cp '=' ; Found '=' ? + jr nz,tsteq ; If not, continue scan + + ld hl,fcb1+1 ; Else, get ready to compare FCBs + ld de,fcb2+1 + ld b,11 ; 11 bytes +eqtest: + ld a,(de) ; Compare + cp (hl) + jr nz,ifcf + inc hl ; Pt to next + inc de + djnz eqtest + jr ifct + + endif ; ifoeq + + +ifck0: + ld de,fcb1+1 ; Point to first character in FCB1 + + if ifoneg + ld a,(de) ; Get it + ld (negflag),a ; Set negate flag + cp negchar ; Is it a negate? + jr nz,ifck1 ; If not, go on + inc de ; Else point to character after negchar +ifck1: + endif ; ifoneg + + if iforeg ; REGISTERS + call regtest ; Test for register value + jr nz,runreg + endif ; iforeg + + call condtest ; Test of condition match + jr nz,runcond ; If found, process condition + + if comif + jp runcomif ; If function not found in table, use transient + else + + call print ; Beep to indicate error + dc bell + + if noise + jp ifstat ; No condition, display current condition + else ; no noise + ret + endif ; noise + endif ; comif + +;----------------------------------------------------------------------------- +; +; Process register - register value is in A +; +;----------------------------------------------------------------------------- + + if iforeg +runreg: + push af ; Save value + call getnum ; Extract value in FCB2 as a number + pop af ; Get value + cp b ; Compare against extracted value + jr jrtrue ; True if match; false if not + endif ; iforeg + +;----------------------------------------------------------------------------- +; +; Process conditional test - address of conditional routine is in HL +; +;----------------------------------------------------------------------------- + +runcond: + jp (hl) ; "call" routine pted to by HL + +;============================================================================= +; +; R E S I D E N T C O N D I T I O N O P T I O N S +; +;============================================================================= + +; Condition: AMBIGUOUS + + if ifambig + +ifcambig: + ld hl,fcb2+1 ; Scan FCB2 for a '?' character + ld bc,11 ; Characters to scan + ld a,'?' ; Reference character + cpir + jr jrtrue ; True if '?' found; false if not + + endif ; ifambig + +;----------------------------------------------------------------------------- + +; Condition: COMPRESSED + + if ifcompr + +ifccompr: + ld a,(fcb2+10) ; Get middle character of file type + cp 'Z' ; Crunched + jr z,ifctrue + cp 'Q' ; Squeezed + jr jrtrue + + endif ; ifcompr + +;----------------------------------------------------------------------------- + +; Condition: TRUE +; IFCTRUE enables an active IF +; Condition: FALSE +; IFCFALSE enables an inactive IF + + if ifoempty or ifoerror or ifoexist or ifowheel +jrfalse: + jr z,ifcfalse + endif ; Ifoempty or ifoerror or ifoexist or ifowheel + +ifctrue: + + if ifoneg + call negtest ; Test for negate + jr z,ifcf + endif ; ifoneg + +ifct: + ld b,0ffh ; Active + jp ifset + + if iforeg or ifambig or ifcompr or ifoinput or ifonull +jrtrue: + jr z,ifctrue + endif ; Iforeg or ifambig or ifcompr or ifoinput or ifonull + +ifcfalse: + + if ifoneg + call negtest ; Test for negate + jr z,ifct + endif ; ifoneg + +ifcf: + ld b,0 ; Inactive + jp ifset + +;----------------------------------------------------------------------------- + +; Condition: EMPTY filename.typ + + if ifoempty +ifcempty: + call tlog ; Log into FCB2's DU + ld de,fcb2 ; Pt to fcb2 + ld c,15 ; Open file + push de ; Save fcb ptr + call bdos + pop de + inc a ; Not found? + jr z,ifctrue + ld c,20 ; Try to read a record + xor a ; set cr value to zero + ld (fcb2+32),a ; to attempt to read first record + call bdos + or a ; 0=OK + jr jrfalse ; true if no read + endif ; ifoempty + +;----------------------------------------------------------------------------- + +; Condition: ERROR + + if ifoerror +ifcerror: + ld a,(z3msg+6) ; Get error byte + or a ; 0=FALSE (no error registered) + jr jrfalse + endif ; ifoerror + +;----------------------------------------------------------------------------- + +; Condition: EXIST filename.typ + + if ifoexist +ifcex: + call tlog ; Log into DU + ld de,fcb2 ; Pt to fcb + ld c,17 ; Search for first + call bdos + inc a ; Set zero if error + jr jrfalse + endif ; Ifoexist + +;----------------------------------------------------------------------------- + +; Condition: INPUT (from user) + +; Modified to say " (Y/N)? ", and accept ONLY Y or y or N or n +; Carson Wilson 3/1/88 + + if ifoinput +ifcinput: + call print + dc ' (Y/N)? ' +ifcinp1: + ld hl,z3msg+7 ; Pt to ZEX message byte + ld (hl),10b ; Suspend ZEX input + push hl ; Save ptr to ZEX message byte +ifcinp2: + ld e,0ffh + ld c,6 ; Direct input from console + call bdos + or a ; Any input yet? + jr z,ifcinp2 ; Nope, try again + + pop hl ; Get ptr to ZEX message byte + ld (hl),0 ; Return ZEX to normal processing + and 5fh ; Mask and capitalize user input + cp 'Y' + jr nz,testN ; No, check if 'N' + call conout ; Display 'Y' + jr ifctrue ; Process as true +testN: + cp 'N' + jr nz,notN ; Not 'N' or 'n' + call conout ; Display 'N' + jr ifcfalse ; Process as false +notN: + ld a,bell ; Protest! + call conout + jr ifcinp1 ; Force either Y or y or N or n + + endif ; ifoinput + +;----------------------------------------------------------------------------- + +; Condition: NULL (2nd file name) + + if ifonull +ifcnull: + ld a,(fcb2+1) ; Get first char of 2nd file name + cp ' ' ; Space = null + jr jrtrue + endif ; ifonull + +;----------------------------------------------------------------------------- + +; Condition: TCAP + + if ifotcap +ifctcap: + ld a,(z3env+80h) ; Get first char of Z3 TCAP Entry + cp ' '+1 ; Space or less = none + jP c,ifcfalse + jP ifctrue + endif ; ifotcap + +;----------------------------------------------------------------------------- + +; Condition: WHEEL + + if ifowheel +ifcwheel: + ld hl,(z3env+29h) ; Get address of wheel byte + ld a,(hl) ; Get byte + or a ; Test for true + jP jrfalse ; False if 0 + endif ; ifowheel + +;============================================================================= +; +; S U P P O R T R O U T I N E S +; +;============================================================================= + +; Convert chars in FCB2 into a number in B + + if iforeg +getnum: + ld b,0 ; Set number + ld hl,fcb2+1 ; Pt to first char +getn1: + ld a,(hl) ; Get char + inc hl ; Pt to next + sub '0' ; Convert to binary + ret c ; Done if error + cp 10 ; Range? + ret nc ; Done if out of range + ld c,a ; Value in C + ld a,b ; A=old value + add a,a ; *2 + add a,a ; *4 + add a,b ; *5 + add a,a ; *10 + add a,c ; Add in new digit value + ld b,a ; Result in B + jr getn1 ; Continue processing + endif ; iforeg + +;----------------------------------------------------------------------------- + +; Log into DU in FCB2 + + if ifoexist or ifoempty + +tlog: + ld a,(fcb2) ; Get disk + or a ; Current? + jr nz,tlog1 + ld c,25 ; Get disk + call bdos + inc a ; Increment for following decrement +tlog1: + dec a ; A=0 + ld e,a ; Disk in E + ld c,14 + call bdos + ld a,(fcb2+13) ; Pt to user + ld e,a + ld c,32 ; Set user + jp bdos + + endif ; ifoexist or ifoempty + +;----------------------------------------------------------------------------- + +; Test of Negate Flag = negchar + + if ifoneg +negtest: +negflag equ $+1 ; Pointer for in-the-code modification + ld a,0 ; 2nd byte is filled in + cp negchar ; Test for No + ret + endif ; ifoneg + +;----------------------------------------------------------------------------- + +; Test FCB1 against a single digit (0-9) +; Return with register value in A and NZ if so + + if iforeg +regtest: + ld a,(de) ; Get digit + sub '0' + jr c,zret ; Z flag for no digit + cp 10 ; Range? + jr nc,zret ; Z flag for no digit + ld hl,z3msg+30h ; Pt to registers + add a,l ; Pt to register + ld l,a + ld a,h ; Add in H + adc 0 + ld h,a + xor a ; Set NZ + dec a + ld a,(hl) ; Get register value + ret +zret: + xor a ; Set Z + ret + endif ; iforeg + +;----------------------------------------------------------------------------- + +; Test to see if a current IF is running and if it is FALSE +; If so, return with Zero Flag Set (Z) +; If not, return with Zero Flag Clear (NZ) +; Affect only HL and PSW + +iftest: + call msgbf1 ; Test for active IF + jr z,ifok ; No active IF + and (hl) ; Check active flag + ret z ; Return Z since IF running and FALSE +ifok: + or 255 ; Return NZ for OK + ret + +msgbf1: + ld hl,z3msg+1 ; Get IF active flag + ld a,(hl) + inc hl ; Pt to If status byte + or a ; Set z if no IF active + ret + +;----------------------------------------------------------------------------- + +; Test FCB1 against condition table (must have 2-char entries) +; Return with routine address in HL if match and NZ flag + +condtest: + ld hl,condtab ; Pt to table +condt1: + ld a,(hl) ; End of table? + or a + ret z + ld a,(de) ; Get char + cp (hl) ; Comppare entries + inc hl ; Pt to next + inc de + jr nz,condt2 + ld a,(de) ; Get 2nd char + cp (hl) ; Compare + jr nz,condt2 + inc hl ; Pt to address + ld a,(hl) ; Get address in HL + inc hl + ld h,(hl) + ld l,a ; HL = address + jr ifok ; Set NZ for OK +condt2: + inc hl ; Pt to next entry + inc hl ; Skip over addr + inc hl + dec de ; Pt to 1st char of condition + jr condt1 + +;----------------------------------------------------------------------------- + +; Turn on next IF level +; B register is 0 if level is inactive, 0FFH if level is active + +ifset: +; ld hl,z3msg+1 ; Get IF flag +; ld a,(hl) +; or a ; If no if at all, start 1st one + + call msgbf1 + dec hl + + jr z,ifset1 +ifset0: + add a,a ; Advance to next level + jr c,iferr ; Check for overflow (8 IFs max) + ld (hl),a ; Set IF byte + jr ifset2 +ifset1: + inc a ; A=1 + ld (hl),a ; Set 1st IF +ifset2: + ld d,a ; Get IF byte + and b ; Set interested bit + ld b,a + inc hl ; Pt to active flag + ld a,d ; Complement IF byte + cpl + and (hl) ; Mask in only uninterested bits + or b ; Mask in interested bit + ld (hl),a ; Save result + + if noise + jp ifstat ; Print status and exit + else + ret ; Or just exit + endif ; noise + +iferr: + call print ; Beep to indicate overflow + dc bell + ret + +;============================================================================= +; +; T R A N S I E N T I F P R O C E S S I N G +; +;============================================================================= + + + if comif + +runcomif: + +; First we have to find IF.COM + + ld bc,100h*(ifdrv-'A')+ifusr ; Values to use if null path + + if pathroot + + ld hl,(expath) ; Point to symbolic path (indirect) +fndroot: + ld a,(hl) ; Check for end of path + or a + jr z,froot2 ; If end, branch + +; Process Next Path Element + + cp curint ; Current disk/user symbol? + jr nz,froot0 ; If not, branch + ld a,(curdr) ; Get current disk + inc a ; Compensate for following decrement +froot0: + dec a ; Shift to range 0..15 + ld b,a ; Set disk + inc hl ; Point to user in path + ld a,(hl) ; Get user + cp curint ; Current drive/user symbol? + jr nz,froot1 ; If not, branch + ld a,(curusr) ; Get current user +froot1: + ld c,a ; Set user + inc hl ; Point to next element in symbolic path + jr fndroot + +; Done with Search - BC Contains ROOT DU (or specified DU if path is empty) + + endif ; pathroot + +froot2: + call logbc ; Log into IF.COM's directory + +; Try to Open File IF.COM + + ld de,extfcb ; Point to command FCB + xor a + ld (de),a ; Force current drive + ld c,15 ; Open file + call bdos + inc a + jr nz,ifload ; Branch if file found + +; IF.COM not found - process as IF F + +ifnotfnd: + call iferr ; Ring bell + call reset ; Return home + jp ifcf + +; Load File IF.COM + +ifload: + call defdma ; First record to tbuff + call readcmd ; Read 1st record from IF.COM + jr nz,ifnotfnd ; If eof, treat as if file not found + + ld (extfcb+32),a ; Start from scratch (record 0) + ld a,(tbuff+8) + cp 3 + jr c,ifnotfnd ; Only Types 3 and 4 are acceptable + + call loadif ; Load IF.COM and set IFADR appropriately +; +; Build the command tail at tbuff +; + ld de,tbuff ; Point DE to tbuff + push de ; Save it for later + ld hl,(z3msg+4) ; Points into MCL buffer +; +; Advance HL to first 'space' after IF or .IF or :IF +; +advsp: inc hl + ld a,(hl) + cp ' '+1 ; Carry if space or null + jr nc,advsp + + ld c,0 ; Clear a counter + +putt: inc de ; Advance tbuff pointer + ld a,(hl) ; From MCL + ld (de),a ; To tbuff + inc hl ; Advance MCL pointer + or a ; Check for null + jr z,putx ; End of command line + cp ';' ; Command separator + jr z,putx ; End of command + inc c ; Count it up + jr putt ; Next.. + +putx: xor a ; Get a null + ld (de),a ; Terminate the line in tbuff + pop hl ; Beginning of tbuff + ld (hl),c ; Character count +; +; Pick up the execution address for Type 3 or 4 +; + ld hl,(ifadr) ; Load address + ld a,(hl) ; First byte at load address + cp 0c7h ; Test for RST 0 + jr nz,runif ; Nope, execute it + ld (hl),0c3h ; Plug in a JP +; +; Arrive here to execute IF.COM +; +runif: ld hl,z3env ; Pass environment in HL + db 0c3h ; JP instruction +ifadr: dw 0 ; Load/Execution address of IF.COM + +; +; Load IF.COM +; +loadif: + ld hl,(tbuff+11) ; Type 3 load address + jr z,loada ; Load as Type 3 +; +; Assume Type 4 (or higher) +; + ld hl,extfcb+32 ; Point to CR of extfcb + ld (hl),2 ; Set up for record 2 + push hl ; Save the pointer + call readcmd ; Get it into tbuff + pop hl + jp nz,ifnotfnd ; Too short + ld (hl),a ; Record 0 again + ld hl,(tbuff+11) ; Size word + push hl ; Save it + call readcmd ; Read record 0 again + pop bc ; Size + ld de,(ccp) ; CCP start + ld hl,z3env + dec a ; Phony fullget flag + call tbuff+9 ; Call Type 4 loader + push hl ; Save load address + call readcmd ; Read record 1 to tbuff (point to record 2) + pop hl ; Load address +; +loada: ld (ifadr),hl ; Save it +; +; Load IF.COM to (HL) until end of file, reset DMA and DU and return +; +load: push hl ; Save loading address + call setdma ; According to HL + call readcmd ; Read a record from file + pop hl ; Get current loading address back + jr nz,reset ; End of file + ld de,128 ; Advance it by one record + add hl,de + jr load ; Back to read some more + +; Reset DMA and Current DU + +reset: call defdma + ld bc,(curusr) ; Return home + +; Log Into DU in BC + +logbc: ld e,b ; Set disk + push bc + ld c,14 ; Select disk + call bdos + pop bc + ld e,c ; Set user + ld c,32 ; Select user + jp bdos + + +; Set default DMA address + +defdma: ld hl,tbuff + +; Set DMA to address according to HL + +setdma: push hl ; Save it + ex de,hl ; To DE + ld c,26 ; Set DMA command + call bdos ; Do it + pop hl ; DMA address + ret + +; Read a record from file in EXTFCB + +readcmd: + ld de,extfcb + ld c,20 + call bdos + or a ; Set NZ if error (end of file) + ret + + endif ; comif + +;============================================================================= +; +; U T I L I T Y S U B R O U T I N E S +; +;============================================================================= + +; Print "IF " + +prif: + call print + dc 'IF ' + ret + +;----------------------------------------------------------------------------- + +; Print String (terminated in 0 or MSB Set) at Return Address + +print: + ex (sp),hl ; Get address + call print1 + ex (sp),hl ; Put address + ret + +; Print String (terminated by MSB Set) pted to by HL + +print1: + ld a,(hl) ; Done? + inc hl ; Pt to next + call conout ; Print char + or a ; Set msb flag (m) + ret m ; Msb terminator + jr print1 + +;----------------------------------------------------------------------------- + +; Console Output Routine + +conout: + push hl ; Save regs + push de + push bc + push af + and 7fh ; Clear msb + ld e,a ; Char in E + ld c,2 ; Output + call bdos + pop af ; Get regs + pop bc + pop de + pop hl + ret + +;============================================================================= +; +; Display current length in records +; +prtval macro m1,v1,m2,v2,m3 + .radix 10 + .printx m1 v1 m2 v2 m3 + endm + +length equ $ - start +recs equ length / 128 +bytes equ length mod 128 + + .printx + prtval ,%recs,,%bytes, + .printx + + end + +; End of NZFCP.Z80 + \ No newline at end of file diff --git a/Source/BPBIOS/NZFCP13/z34cmn.lib b/Source/BPBIOS/NZFCP13/z34cmn.lib new file mode 100644 index 00000000..ad8c8cec --- /dev/null +++ b/Source/BPBIOS/NZFCP13/z34cmn.lib @@ -0,0 +1,105 @@ +; +; Library: Z34CMN.LIB +; Author: Carson Wilson +; Version: 1.2 +; Date: 16 June 1988 +; Changes: Renamed CRT0 to LINS for NZCOM compatibility. +; Added CUSR " " " + +; Author: Carson Wilson +; Version: 1.1 +; Date: 12 June 1988 +; Changes: Added CDRV for various Z34RCP commands. +; Added CRT0 for Z34RCP TYPE command. +; Added Z3TCAP and CLRSCR for Z34RCP CLS command. +; Added QUIET for ZCPR34 time in prompt. +; Added RSDMSG for ZCPR34 time in prompt. + +; Library: Z34CMN.LIB +; Author: Joe Wright +; Date: 23 March 1988 + +; As a replacement for Z3BASE.LIB, some usual equates. + +base equ 0 ; Base Page + +false equ 0 +true equ not false + +no equ false +yes equ true + +off equ false +on equ true + +; Named COMMON declarations start here. For compatibility, these +; are the same names used by Bridger Mitchell's JetLDR. + + common /_ENV_/ +z3env: ; Z3 Environment descriptor +z3envs equ yes ; There is one + +expath equ z3env+9 ; Address of External Path +expaths equ 10 ; Maximum 10 elements for MPATH + +rcp equ z3env+0ch ; Address of RCP +rcps equ yes ; Used as existence test, not size + +fcp equ z3env+12h ; Address of FCB +fcps equ yes ; Used as existence test, not size + +z3ndir equ z3env+15h ; Address of NDR +z3ndirs equ yes ; Used as existence test, not size + +quiet equ z3env+28h ; Quiet flag address + +z3whl equ z3env+29h ; Wheel byte address +z3whls equ yes ; There is a wheel + +lins equ z3env+33h ; CRT text lines address + +ccp equ z3env+3fh ; CCP entry +ccps equ z3env+41h ; Size + +dos equ z3env+42h ; DOS entry (+6) +doss equ z3env+44h ; Size + +bio equ z3env+45h ; BIO entry + +z3tcap equ z3env+80h ; TCAP address + +clrscr equ z3env+97h ; Clear screen string address + + common /_SSTK_/ +shstk: ; Top of Shell stack +shstks equ yes ; There is a shell stack + + common /_MSG_/ +z3msg: ; Message buffer +z3msgs equ yes ; There is one + +cusr equ z3msg+2eh ; Current user +cdrv equ z3msg+2fh ; Current drive + +rsdmsg equ z3msg+3ah ; Reserved bytes + + common /_FCB_/ +extfcb: ; External file control block +extfcbs equ yes ; There is one + + common /_MCL_/ +z3cl: ; Multiple command line +z3cls equ yes ; There is one + + common /_XSTK_/ +extstk: ; External stack +extstks equ yes ; There is one + + common /_BIOS_/ +bios: + + cseg ; Select Code Segment + +; End of Z34CMN.LIB + + \ No newline at end of file diff --git a/Source/BPBIOS/NZFCP13/z34mac.lib b/Source/BPBIOS/NZFCP13/z34mac.lib new file mode 100644 index 00000000..8ab3ffbe --- /dev/null +++ b/Source/BPBIOS/NZFCP13/z34mac.lib @@ -0,0 +1,122 @@ + +; Z33MAC.LIB : Macros for use with ZCPR33 + +; General purpose macros + +putreg macro + push hl ; Save registers in order + push de + push bc + endm + +getreg macro + pop bc ; Restore registers in order + pop de + pop hl + endm + +swap macro + rrca ; Exchange nibbles + rrca + rrca + rrca + endm + +;---------------------------------------- + +; Macro for forming option bytes + +; This macro generates a byte with bits corresponding to up to 8 option +; flags. The bits are filled in the order of the parameters and are right +; justified in the byte. + +optflag macro f1,f2,f3,f4,f5,f6,f7,f8 + +flag defl 0 ;; initial value + + irp temp, + + if not nul temp +flag defl flag shl 1 + if temp +flag defl flag or 1 + endif ;;temp + endif ;;not nul temp + + endm ;; irp + + defb low flag + + endm ;; optflag + +;----------------------------------------------------------------------------- + +; Command table entry definition macro + +; Macro to form an entry for one command in the table. The first parameter is +; the name to be used for the command (no quotes); the second parameter is the +; flag that indicates whether or not the command is to be enabled; the third +; parameter is the wheel control flag; and the last parameter is the jump +; address to the code that carries out the command. The command names are +; automatically padded out to the correct length (they will be truncated and +; an error message will result if a command name is too long). The characters +; in the command name are automatically converted to upper case. + +command macro cmdname,enableflag,wheelflag,address + + if enableflag ;; Generate command only if enabled + +whlmask defl wheelflag ;; Initialize variables +count defl cmdsize ;; Initialize to size of each command name + + irpc char,cmdname ;; Repeat over letters in command name + +count defl count - 1 ;; Count down characters in name + + if [ count lt cmdsize ] + + ;; If character is lower case, convert to upper case + + if [ '&char' ge 'a' ] and [ '&char' le 'z' ] + + if whlmask + defb [ '&char' and 5fh ] + 80h + else ;;not whlmask + defb [ '&char' and 5fh ] + endif ;;whlmask + + else ;;not lower case + + if whlmask + defb '&char' + 80h ;; If controlled by wheel, set high bit + else ;;not whlmask + defb '&char' ;; If not restricted, leave high bit clear + endif ;;whlmask + + endif ;;lower case + + endif ;;[ count lt cmdsize ] + +whlmask defl false ;; Turn off high-bit setting after first char + + endm ;irpc + + ;; Pad command name with blanks + + if [ count gt cmdsize ] ;; If we underflowed + *** Command name "&cmdname" is too long / truncated *** + else + rept count + defb ' ' + endm + endif ;[ count gt cmdsize ] + + dw address ;; Dispatch address for command + + endif ;enable + + endm ;command + +; End Z33MAC.LIB + + \ No newline at end of file diff --git a/Source/Images/hd0/s0/u0/WW.Z3T b/Source/BPBIOS/WW.Z3T similarity index 100% rename from Source/Images/hd0/s0/u0/WW.Z3T rename to Source/BPBIOS/WW.Z3T diff --git a/Source/BPBIOS/Z34RCP11/Build.cmd b/Source/BPBIOS/Z34RCP11/Build.cmd new file mode 100644 index 00000000..1323b95f --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/Build.cmd @@ -0,0 +1,11 @@ +@echo off +setlocal + +set PATH=%PATH%;..\..\..\Tools\zx;..\..\..\Tools\cpmtools; + +set ZXBINDIR=../../../tools/cpm/bin/ +set ZXLIBDIR=../../../tools/cpm/lib/ +set ZXINCDIR=../../../tools/cpm/include/ + +rem zx Z80ASM -z34rcp11/MF +zx ZMAC -z34rcp11.z80 -/P diff --git a/Source/BPBIOS/Z34RCP11/Clean.cmd b/Source/BPBIOS/Z34RCP11/Clean.cmd new file mode 100644 index 00000000..a088f4e8 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/Clean.cmd @@ -0,0 +1,7 @@ +@echo off +setlocal + +if exist *.prn del *.prn +if exist *.lst del *.lst +if exist *.err del *.err +if exist *.rel del *.rel diff --git a/Source/BPBIOS/Z34RCP11/Makefile b/Source/BPBIOS/Z34RCP11/Makefile new file mode 100644 index 00000000..7d8b55f8 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/Makefile @@ -0,0 +1,5 @@ +OBJECTS = z34rcp11.rel +TOOLS = ../../../Tools +DEST = + +include $(TOOLS)/Makefile.inc diff --git a/Source/BPBIOS/Z34RCP11/cledinst.com b/Source/BPBIOS/Z34RCP11/cledinst.com new file mode 100644 index 00000000..c26a3cf0 Binary files /dev/null and b/Source/BPBIOS/Z34RCP11/cledinst.com differ diff --git a/Source/BPBIOS/Z34RCP11/cledsave.com b/Source/BPBIOS/Z34RCP11/cledsave.com new file mode 100644 index 00000000..dde04bc1 Binary files /dev/null and b/Source/BPBIOS/Z34RCP11/cledsave.com differ diff --git a/Source/BPBIOS/Z34RCP11/nzrcp.z80 b/Source/BPBIOS/Z34RCP11/nzrcp.z80 new file mode 100644 index 00000000..52b844e3 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/nzrcp.z80 @@ -0,0 +1,3010 @@ + +; New Name: NZRCP.Z80 Joe Wright July 1987 + +; Program: Z34RCP +; Version: 1.0 +; Description: Resident Command Package (RCP) for ZCPR34 +; Author: Jay Sage +; Date: March 1, 1987 +; Derivation: SYSRCP (Richard Conn) and many other contributions + +; ZCPR34 is copyright 1987 by Jay P. Sage. All rights reserved. End-user +; distribution and duplication permitted for non-commercial purposes only. +; Any commercial use of ZCPR34, defined as any situation where the duplicator +; recieves revenue by duplicating or distributing ZCPR34 by itself or in +; conjunction with any hardware or software product, is expressly prohibited +; unless authorized in writing by Jay P. Sage. + +version equ 2 +subver equ 4 + +; Vers 2.4 - Add whlchk subroutine for indirect addressing of wheel byte. +; 4 Apr 88 - Fixed command list routines. +; Joe Wright - Inhibit SPOP if there is no shell. Allow shstks = 1. + +; Vers 2.3 - Reset command, after calling F37, jumps to 0. +; 22 Feb 88 + +; Vers 2.2 - Modified for use of Z34CMN.LIB and NZ-COM. +; 31 Dec 87 +; Joe Wright + + +; Vers 2.1 - R command uses ZRDOS function 37 to reset drives. +; 6 Sep 87 - WHL command changed, WHLQ disappears. +; Joe Wright - REG command expanded. Add REG E (program error byte) +; - SPOP command added. Pops the shell stack + +;============================================================================= +; +; D E F I N I T I O N S S E C T I O N +; +;============================================================================= + + name ('RCP') + + maclib z34cmn.lib ; Defines ZCPR system addresses + maclib z34defn.lib ; Defines offsets in Z34 command processor + maclib z34mac.lib ; Macros + maclib sysdef.lib ; Common logic, sys, ascii defines + maclib nzrcp.lib ; Defines command options + +;============================================================================= +; +; E N T R Y C O D E S E C T I O N +; +;============================================================================= + +start: + db 'Z3RCP' ; Package ID + + +;---------------------------------------- + +; Command table + + db cmdsize ; Length of each command name + cmdtbl ; Dispatch table from Z33RCP.LIB + db 0 ; Marks end of command jump table + + +;---------------------------------------- + +; Name of RCP + +; This block allows the 'H' command and/or the SHOW utility to display a name +; and version number for this RCP as well as the commands that are supported. + +rcpname: + idstring ; From macro in Z33RCP.LIB + +; Include only those code sections that are required. + +; include rcph ; 'H' help (command list) command + page + +; RCP-H.Z80 'H' Command + +;============================================================================= +; +; H E L P C O M M A N D +; +;============================================================================= + +; This command displays a list of all resident commands that are supported, +; including those in the CPR (command processor), RCP, and FCP. + +clist: + +; Print the CPR-resident command names + + if listcpr + + call print ; Print "CPR" + db lf + db 'CP','R'+80h +; + ld hl,(ccp) ; CCP location from Z3ENV + ld de,offcmd ; Offset to CPR command table + add hl,de + call cmdlist ; Display the list of commands + + endif ;listcpr + +; Print the FCP-resident command names + + if listfcp + + ld hl,(fcp) + ld a,h + or l + jr z,rcplist ; No FCP + + ld a,(hl) + or a + jr z,rcplist ; FCP removed + + call print ; Print header for FCP + db lf + db 'FC','P'+80h + ld de,5 + add hl,de ; Point to FCP command table + call cmdlist + + endif ;listfcp + +; Print the RCP-resident command names + +rcplist: + if listrcp + + call crlf ; Skip a line + ld hl,rcpname ; Print RCP name + call printhl + ld hl,start+5 ; Point to RCP command table + + else + + ret + + endif ;listrcp + + ; Fall through to CMDLIST + +;---------------------------------------- + +; Subroutine to display list of commands in a command table (code above +; falls through to this routine -- do not move it). The commands are +; displayed 5 per line with 8 character spaces allowed for each command +; (subject to equates below). + +cmdlist: + call crlf ; Start with new line + ld e,(hl) ; Get size of each command name into DE + ld d,0 + inc hl ; Point to name of first command + ld c,cmdsline ; Set names-per-line value + +cmdlist1: + ld a,(hl) ; Get first character of the command name + or a ; See if it is null + jr nz,cmdlist1a ; If not, continue + ld a,cmdsline ; See if we are already on a new line + cp c + call nz,crlf ; If not, skip a line + ret + +cmdlist1a: + if noshow ; Option to suppress wheel-limited cmds + rla ; Shift high bit of name into carry bit + jr nc,cmdlist2 ; If not restricted, go on + call whlchk ; Check wheel byte + jr nz,cmdlist2 ; If wheel set, continue as usual + add hl,de ; Otherwise skip this command + jr cmdlist5 + endif + +; Print leading spaces between names + +cmdlist2: + ld a,cmdspace ; Spacing between command names + sub e ; Less length of each command name + ld b,a + ld a,' ' +cmdlist3: + call conout + djnz cmdlist3 + +; Print name of command + + ld b,e ; Length of each name into B +cmdlist4: + ld a,(hl) ; Get command name character + call conout + inc hl ; Point to next + djnz cmdlist4 + + dec c ; Decrement count of names on this line + jr nz,cmdlist5 ; Branch if room for more names + call crlf ; Otherwise, end this line and + ld c,cmdsline ; ..reset count for another line of commands + +; Skip to next command name + +cmdlist5: + inc hl ; Skip jump vector + inc hl + jr cmdlist1 ; Back to process next name + +; End RCP-H.Z80 + +;============================================================================= +; +; P O P S H E L L S T A C K C O M M A N D +; +;============================================================================= + +; +; POP the Shell Stack +; + if spopon + +; Pop the shell stack + +spop: ld hl,(z3env+1eh) ; SHSTK (indirect) + ld a,h + or l + ret z ; No shell stack + + ex de,hl ; SHSTK to DE + ld hl,(z3env+20h) ; SHSTKS to L, SHSIZE to H + push hl ; Save SHSIZE + xor a ; Your basic null in A + ld b,l + dec b ; SHSTKS-1 in B + jr z,sp0a ; Clear one entry + + push de ; Save SHSTK + ld e,h ; SHSIZE to E + ld d,a ; Clear D + ld h,a ; Clear H.. + ld l,a ; ..and L +sp0: add hl,de ; Multiply SHSIZE*(SHSTKS-1) + djnz sp0 + ld b,h + ld c,l ; Length to BC + ex de,hl ; SHSIZE to HL + pop de ; Get SHSTK (destination) + add hl,de ; SHSTK+SHSIZE to HL (Source) + ldir + +sp0a: pop bc ; Get SHSIZE in B +sp1: ld (de),a ; Clear last entry + inc de + djnz sp1 + ret + endif ; SPOPON + + + if clson +; include rcpcls ; 'CLS' clear screen command + page + +; RCP-CLS.Z80 'CLS' Command + +;============================================================================= +; +; C L E A R S C R E E N C O M M A N D +; +;============================================================================= + +; Command: CLS +; Function: To clear the CRT screen +; Comments: The setting of the CLSTCAP equate determines whether this +; command uses the TCAP information or not. If not, it uses the +; clear-screen string passed in macro CLSSTR. That string should +; end with the high bit set. + +cls: + if clstcap ; If using TCAP for clear screen string + + ld a,(z3env+80h) ; Get beginning of tcap + cp ' '+1 ; See if blank or perhaps null + jr nc,cls1 ; If not, go to clear screen code + call print ; If blank, then give error message + db ' No TCA','P'+80h + ret + +cls1: ld hl,z3env+97h ; Point to beginning of clear screen string + jp printhl ; Display it + + else ; Not using tcap + + call print + clsstr ; String from Z33RCP.LIB + ret + + endif ;clstcap + +; End RCP-CLS.Z80 + + endif ;clson + + if reson +; include rcpr ; 'R' disk reset command + page + +; RCP-R.Z80 'R' command + +;============================================================================= +; +; D I S K R E S E T C O M M A N D +; +;============================================================================= + +; Command: RESET +; Function: Reset the disk system +; Comments: ZRDOS does not require a disk system reset when disks are +; changed, but directory programs will not show the correct +; size if this is not done. It is also good practice. Since +; no warm boot performed, the disk in drive A need not have the +; operating system on it. +; Ver 2.1 Now logs all drives off and forces fixed and ram disks to +; re-log. +reset: + if resmsg ; If displaying a reset message + call print ; Report action + dc ' Reset' + endif ;resmsg + + ld de,-1 ; All 16 drives + ld c,37 ; Disks reset ZRDOS function + call bdos + ld c,13 ; Reset disk system + jp bdos + +; End RCP-R.Z80 + + endif ;reson + + if tston +; include rcptst ; 'TST' error test command + page + +; RCP-TST.Z80 'TST' Command + +;============================================================================= +; +; E R R O R T E S T C O M M A N D +; +;============================================================================= + +; Command: TST +; Function: To set the message buffer program error flag based on +; error count reported by M80 or L80 +; Syntax: TST PN where PN is (at least) the first letter of M80 or L80 + +testerr: + +; Check for name of program to test + + ld a,(fcb1+1) ; Get first character in program name + + if testm80 + ld hl,m80f ; Preset for m80 test counts + ld de,m80w + cp 'M' + jr z,testcount + endif ; Testm80 + + if testf80 + ld hl,f80f + ld de,f80w + cp 'F' + jr z,testcount + endif ; Testf80 + +; If no match, give error message + + call print + db 'bad nam','e'+80h + +testcount: + ld a,(hl) ; Test first error count word + inc hl + or (hl) + ex de,hl ; Test second word + or (hl) + inc hl + or (hl) + ld hl,z3msg+6 ; Point to program error flag + ld (hl),0 ; Clear it + ret z ; If counts were zero, we are done + ld (hl),0ffh ; Else set the error flag + ret + +; End RCP-TST.Z80 + + endif ;tston + + if spaceon +; include rcpsp ; 'SP' space on disk command + page + +; RCP-SP.Z80 'SP' Command + +;============================================================================= +; +; D I S K S P A C E C O M M A N D +; +;============================================================================= + +; Command: SP +; Function: Shows space remaining on designated drive +; Syntax: SP [DIR:|DU:] +; Comments: This code can be called by several other RCP commands so that +; they can show the space remaining on the disk after their +; operation. + + if [erasp or cpsp or dirsp] +crspace: ; Used to call space after other subroutines + call crlf ; Start new line + endif ;[erasp or cpsp or dirsp] + +space: + ld a,(fcb1) ; Determine requested drive + or a ; If drive explicitly selected + jr nz,space1 ; ..then skip + + ld c,25 ; BDOS get current drive function + call bdos + inc a ; Shift to range 1..16 + +space1: + dec a ; Shift to range 0..15 + ld e,a ; Save in E for selecting disk below + add 'A' ; Convert to letter and + ld (seldrv),a ; save in message string below + ld c,14 ; BDOS select disk function + call bdos ; Not needed if no drive selected, but smallest + ; ..possible code size this way. + +; Here we extract the following disk parameter information from the disk +; parameter block (DPB): +; BLKSHF: block shift factor (1 byte) +; BLKMAX: max number of blocks on disk (2 bytes) + +dparams: + ld c,31 ; BDOS get disk parameters function + call bdos + inc hl ; Advance to block shift factor byte + inc hl + ld a,(hl) ; Get value and + ld (blkshf),a ; ..save it in code below + inc hl ; Advance to max block number word + inc hl + inc hl + ld e,(hl) ; Get value into HL + inc hl + ld d,(hl) + inc de ; Add 1 for max number of blocks + +; Compute amount of free space left on disk + +dfree: + ld c,27 ; BDOS get allocation vector function + push de ; Save BLKMAX value + call bdos ; Get allocation vector into HL + ld b,h ; Copy allocation vector to BC + ld c,l + pop hl ; Restore MAXBLK value to HL + ld de,0 ; Inititialize count of free blocks + +; At this point we have +; BC = allocation vector address +; DE = free block count +; HL = number of blocks on disk + +free1: + push bc ; Save allocation address + ld a,(bc) ; Get bit pattern of allocation byte + ld b,8 ; Set to process 8 blocks +free2: + rla ; Rotate allocated block bit into carry flag + jr c,free3 ; If set (bit=1), block is allocated + inc de ; If not set, block is not allocated, so + ; ..increment free block count +free3: + ld c,a ; Save remaining allocation bits in C + dec hl ; Count down number of blocks on disk + ld a,l ; See if we are down to zero + or h + jr z,free4 ; Branch if no more blocks to check + ld a,c ; Get back current allocation bit pattern + djnz free2 ; Loop through 8 bits + pop bc ; Get pointer to allocation vector + inc bc ; Point to next allocation byte + jr free1 ; Continue by processing next allocation byte + +free4: + pop bc ; Clean up stack + ex de,hl ; Free block count to HL +blkshf equ $+1 ; Pointer for in-the-code modification + ld a,0 ; Get block shift factor + sub 3 ; Convert to log base 2 of K per block + jr z,free6 ; Done if single density (1k per block) + +; Convert for blocks of more than 1K each + +free5: + add hl,hl + dec a + jr nz,free5 + +; At this point HL = amount of free space on disk in K + +free6: + call print + db ' Space on ' +seldrv: db 0 ; Modified above to contain drive letter + db ':',[' '+80h] + +; Display decimal value of HL + + ld b,0 ; Initialize count of digits already printed + ld de,10000 ; Divisor in DE + call decdsp ; Print digit (or space if leading '0') + ld de,1000 + call decdsp + call decdsp3 ; Display hundreds, tens, and units + ld a,'K' + jp conout ; Final return from space routine + +; End RCP-SP.Z80 + + endif ;spaceon + + if diron +; include rcpdir ; 'DIR' directory command + page + +; RCP-DIR.Z80 'DIR' Command + +;============================================================================= +; +; D I R E C T O R Y D I S P L A Y C O M M A N D +; +;============================================================================= + +; Command: DIR +; Function: Display a directory of the files on disk +; Syntax: DIR Displays the DIR files +; DIR S Displays the SYS files +; DIR A Display both DIR and SYS files +; DIR /S Equivalent to DIR *.* S +; DIR /A Equivalent to DIR *.* A + +dir: + call retsave ; Save return address and set stack + +; See if FCB should be made wild (all '?') + + ld hl,fcb1+1 ; Point to file name in FCP + ld a,(hl) ; Get first character of filename + + if slashchk ; Allow "DIR /S" and "DIR /A" formats + cp '/' ; If name does not start with '/' + jr nz,dir01 ; ..branch and process normally + inc hl ; Point to second character + ld a,(hl) ; Get option character after slash + ld (fcb2+1),a ; ..and put it into second FCB + dec hl ; Back to first character + ld a,' ' ; Simulate empty FCB + endif ;slashchk + +dir01: + ld b,11 ; Prepare to fill FCB name and type with '?' + cp ' ' ; See if no file spec given + ld a,'?' ; Get ready to fill with '?' + call z,fillp ; ..carry out fill + + if nosys ; Suppress-SYS-file-if-no-wheel option + call whlchk ; Check wheel byte + jr z,dirnly ; If wheel off, ignore options + endif + + ld a,(fcb2+1) ; Get first char of 2nd file name + ld b,1 ; Set for both dir and sys files + cp allflag ; SYS and DIR flag specifier? + jr z,dirpr ; Got system specifier + dec b ; B=0 for sys files only + cp sysflag ; SYS only? + jr z,dirpr + +dirnly: ld b,80h ; Must be dir-only selection + +; DIRECTORY PRINT ROUTINE; ON ENTRY, B REG IS SET AS FOLLOWS: +; 0 FOR ONLY SYSTEM FILES, 80H FOR ONLY DIR FILES, 1 FOR BOTH +; +dirpr: + ld a,b ; Get systst flag + call getdir ; Load and sort directory + jp z,prfnf ; Print no file message + ld e,width ; Count down to 0 +; +; ENTRY PRINT LOOP; ON ENTRY, HL PTS TO FILES SELECTED (TERMINATED BY 0) +; AND E IS ENTRY COUNTER +; +dir3: + ld a,(hl) ; Check for done + or a + if dirsp and spaceon + jp z,spaexit ; Show space when done + else + jp z,exit ; Exit if done + endif ; Dirsp and spaceon + ld a,e ; Get entry counter + or a ; Output if 4 entries printed in line + jr nz,dir3a ; Continue + call crlf ; New line + ld e,width ; Reset entry count + ld a,e ; Get entry count +dir3a cp width ; First entry? + jr z,dir4 + call print +; + if wide +; + db ' ' ; 2 spaces + db fence ; Then fence char + db ' '+80h ; Then 1 more space +; + else +; + db ' ' ; Space + db fence+80h ; Then fence char +; + endif ; Wide +; +dir4: + call prfn ; Print file name + call break ; Check for abort + dec e ; Decrement entry counter + jr dir3 + +; End RCP-DIR.Z80 + + endif ;diron + + if eraon +; include rcpera ; 'ERA' erase command + page + +; RCP-ERA.Z80 'ERA' Command + +;============================================================================= +; +; E R A S E C O M M A N D +; +;============================================================================= + +;Command: ERA +;Function: Erase files +;Forms: +; ERA Erase Specified files and print their names +; ERA I Erase Specified files and print their names, but ask +; for verification before Erase is done + +era: + call retsave + ld a,(fcb2+1) ; Get eraflg if it's there + ld (eraflg),a ; Save it as a flag + ld a,1 ; Dir files only + call getdir ; Load directory of files + jp z,prfnf ; Abort if no files +; +; MAIN ERASE LOOP +; +era1: + call break ; See if user wants to stop + push hl ; Save ptr to file + call prfn ; Print its name + ld (nxtfile),hl ; Save ptr to next file + pop hl ; Get ptr to this file + call rotest ; Test file pted to by hl for r/o + jr nz,era3 +eraflg equ $+1 ; Address of flag + ld a,0 ; 2nd byte is flag + cp 'I' ; Is it an inspect option? + jr nz,era2 ; Skip prompt if it is not + call eraq ; Erase? + jr nz,era3 ; Skip if not +era2: + ld de,fcb1+1 ; Copy into fcb1 + ld b,11 ; 11 bytes + call blkmov + call initfcb1 ; Init fcb + ld c,19 ; Delete file + call bdos +era3: + ld hl,(nxtfile) ; Hl pts to next file + ld a,(hl) ; Get char + or a ; Done? + if erasp and spaceon + jp z,spaexit + else + jp z,exit + endif ; Erasp and spaceon + call crlf ; New line + jr era1 + +; End RCP-ERA.Z80 + + endif ;eraon + + if lton +; include rcplt ; 'LIST' and 'TYPE' commands + page + +; RCP-LT.Z80 + +;============================================================================= +; +; L I S T A N D T Y P E C O M M A N D S +; +;============================================================================= + +;Command: LIST +;Function: Print out specified file on the LST: Device +;Forms: +; LIST Print file (NO Paging) +;Notes: +; The flags which apply to TYPE do not take effect with LIST + + if liston +list: +; +; CHECK FOR WHEEL APPROVAL IF OPTION ENABLED + + + call retsave + ld a,0ffh ; Turn on printer flag + jr type0 + endif ;liston + +;Command: TYPE +;Function: Print out specified file on the CON: Device +;Forms: +; TYPE Print file +; TYPE P Print file with paging flag +;Notes: +; The flag PGDFLG defines the letter which toggles the paging +; facility (P in the forms section above) +; The flag PGDFLT determines if TYPE is to page by default +; (PGDFLT=TRUE if TYPE pages by default); combined with +; PGDFLG, the following events occur -- +; If PGDFLT = TRUE, PGDFLG turns OFF paging +; If PGDFLT = FALSE, PGDFLG turns ON paging +; +type: +; +; CHECK FOR WHEEL APPROVAL IF OPTION ENABLED +; +; + call retsave + xor a ; Turn off printer flag +; +; ENTRY POINT FOR CPR LIST FUNCTION (LIST) +; +type0: + if liston + ld (prflg),a ; Set flag + endif ; Liston + + ld a,(fcb2+1) ; Get page flag + ld (pgflg),a ; Save it as a flag + ld a,1 ; Select dir files + call getdir ; Allow ambiguous files (HL points to buffer) + jp z,prfnf ; No files + jr typex2 + + ; Entry point for successive files +typex: + ld hl,(nxtfile) ; Get ptr to next file + ld a,(hl) ; Any files? + or a + jp z,exit + + if liston + ld a,(prflg) ; Check for list output + or a ; 0=type + jr z,typex1 + ld a,cr ; Bol on printer + call lcout + ld a,ff ; Form feed the printer + call lcout + jr typex2 + endif ; Liston + +typex1: +; LDA PAGCNT ; If we've just done so, + push hl + ld hl,(pagcnt) + ld a,(hl) + pop hl + cp nlines-2 ; Don't type another + call nz,pagebreak ; Page break message +typex2: + ld de,fcb1+1 ; Copy into fcb1 + ld b,11 ; 11 bytes + call blkmov + ld (nxtfile),hl ; Set ptr to next file + call initfcb1 ; Init fcb1 + ld c,15 ; Open file + call bdos + inc a ; Set error flag + jp z,prfnf ; Abort if error +; MVI A,NLINES-2 ; Set line count +; STA PAGCNT + ld hl,(pagcnt) + ld (hl),nlines-2 + ld a,cr ; New line + call lcout + ld a,lf + call lcout + ld bc,080h ; Set char position and tab count + ; (b=0=tab, c=080h=char position) +; +; MAIN LOOP FOR LOADING NEXT BLOCK +; +type2: + ld a,c ; Get char count + cp 80h + jr c,type3 +; PUSH H ; Read next block + push bc + ld de,fcb1 ; Pt to fcb + ld c,20 ; Read record + call bdos + or a ; Set flags + pop bc +; POP H + jr nz,typex ; End of file? + ld c,0 ; Set char count + ld hl,tbuff ; Pt to first char +; +; MAIN LOOP FOR PRINTING CHARS IN TBUFF +; +type3: + ld a,(hl) ; Get next char + and 7fh ; Mask out msb + cp 1ah ; End of file (^z)? + jr z,typex ; Next file if so +; +; OUTPUT CHAR TO CON: OR LST: DEVICE WITH TABULATION +; + cp cr ; Reset tab count? + jr z,type4 + cp lf ; Reset tab count? + jr z,type4 + cp tab ; Tab? + jr z,type5 +; +; OUTPUT CHAR AND INCREMENT CHAR COUNT +; + call lcout ; Output char + inc b ; Increment tab count + jr type6 +; +; OUTPUT OR AND RESET TAB COUNT +; +type4: + call lcout ; Output or + ld b,0 ; Reset tab counter + jr type6 +; +; TABULATE +; +type5: + ld a,' ' ; + call lcout + inc b ; Incr pos count + ld a,b + and 7 + jr nz,type5 +; +; CONTINUE PROCESSING +; +type6: + inc c ; Increment char count + inc hl ; Pt to next char + call break ; Check for abort + jp z,typex ; Skip + jr type2 +; +; SEND OUTPUT TO LST: OR CON:, AS PER THE FLAG +; RETURN WITH Z IF ABORT +; +lcout: + push hl ; Save regs + push bc + ld e,a ; Char in e + ld c,2 ; Output to con: + if liston +prflg equ $+1 ; Pointer for in-the-code modification + ld a,0 ; 2nd byte is the print flag + or a ; 0=type + jr z,lc1 + ld c,5 ; Output to lst: + endif ; Liston + +lc1: + push de ; Save char + call bdos ; Output char in e + pop de ; Get char + ld a,e + cp lf + jr nz,lc2 + if liston + ld a,(prflg) ; Output to lst:? + or a ; Nz = yes + jr nz,lc2 + endif ; Liston +; +; CHECK FOR PAGING +; +; LXI H,PAGCNT ; Count down + ld hl,(pagcnt) + dec (hl) + jr nz,lc2 ; Jump if not end of page + ld (hl),nlines-2 ; Refill counter +pgflg equ $+1 ; Pointer to in-the-code buffer + ld a,0 ; 2nd byte is the paging flag + cp pgdflg ; Page default override option wanted? +; + if pgdflt ; If paging is default +; + jr z,lc2 ; Pgdflg means no paging +; + else +; + jr nz,lc2 ; Pgdflg means page +; + endif ; Pgdflt +; + call pagebreak ; Print page break message + jp z,typex ; Z to skip +lc2: + pop bc ; Restore regs + pop hl + ret +; +; PRINT PAGE BREAK MESSAGE AND GET USER INPUT +; ABORT IF ^C, RZ IF ^X +; +pagebreak: + push hl ; Save hl + call print + db cr,lf,' Typing',' '+80h + ld hl,fcb1+1 ; Print file name + call prfn + call dash ; Print dash + call conin ; Get input + pop hl ; Restore hl + push af + call crlf ; New line + pop af + jp break1 +; +; End RCP-LT.Z80 + + endif ;lton + + if renon +; include rcpren ; 'REN' rename command + page + +; RCP-REN.Z80 + +;Section 5E +;Command: REN +;Function: To change the name of an existing file +;Forms: +; REN = Perform function +; +ren: +; +; CHECK FOR WHEEL APPROVAL IF OPTION ENABLED +; + + call retsave +; +; +; STEP 1: CHECK FOR FILE 2 BEING AMBIGUOUS +; + ld hl,fcb2+1 ; Can't be ambiguous + call ambchk1 +; +; STEP 2: LOG INTO USER AREA +; + call logusr ; Log into user area of fcb1 +; +; STEP 3: SEE IF OLD FILE IS R/O +; + ld hl,fcb1 ; Pt to 1st fcb + push hl + ld de,fcb2 ; Pt to 2nd file + push de ; Save ptr + ld a,(hl) ; Get 1st's drive + ld (de),a ; Stuff into second fcb + ld c,17 ; Look for file + call bdos + inc a + jp z,prfnf + call getsbit ; Get ptr to entry in tbuff + ex de,hl ; Hl pts to entry + inc hl ; Pt to fn + call rotest ; See if file is r/o + jp nz,exit +; +; STEP 4: SEE IF NEW FILE ALREADY EXISTS +; EXTEST PERFORMS A NUMBER OF CHECKS: +; 1) AMBIGUITY +; 2) R/O +; 3) IF FILE EXISTS AND NOT R/O, PERMISSION TO DELETE +; + call extest + jp z,exit ; R/o or no permission +; +; STEP 5: EXCHANGE FILE NAME FIELDS FOR RENAME +; + pop de ; Pt to old + pop hl ; Pt to new + push hl ; Save ptr + ld b,12 ; 12 bytes + call iswap1 +; +; STEP 6: RENAME THE FILE +; + pop de ; Get ptr to fcb + ld c,23 ; Rename + call bdos + inc a ; Set zero flag if error + jp z,prfnf ; Print no source file message + jp exit +; +; +; End RCP-REN.Z80 + + endif ;renon + + if proton +; include rcpprot ; 'PROT' file attribute setting command + page + +; RCP-PROT.Z80 + +;Section 5F +;Command: PROT +;Function: To set the attributes of a file (R/O and SYS) +; +;Form: +; PROT afn RSI +;If either R or S are omitted, the file is made R/W or DIR, resp; +;R and S may be in any order. If I is present, Inspection is enabled. + +att: + call retsave + xor a ; Set no inspect + ld (inspect),a + ld hl,0 ; Set r/o and sys attributes off + ld de,fcb2+1 ; Pt to attributes + ld b,3 ; 3 chars max +att1: + ld a,(de) ; Get char + inc de ; Pt to next + cp 'I' ; Inspect? + jr z,atti + cp 'R' ; Set r/o? + jr z,attr + cp 'S' ; Set sys? + jr z,atts +att2: + djnz att1 + jr att3 +atti: + ld (inspect),a ; Set flag + jr att2 +attr: + ld h,80h ; Set r/o bit + jr att2 +atts: + ld l,80h ; Set sys bit + jr att2 +att3: + ld (fatt),hl ; Save file attributes + ld a,1 ; Select dir and sys files + call getdir ; Load directory + jp z,prfnf ; No file error + jr att5 +att4: + ld hl,(nxtfile) ; Pt to next file + ld a,(hl) ; End of list? + or a + jp z,exit + call crlf ; New line +att5: + call break ; Check for possible abort + push hl ; Save ptr to current file + call prfn ; Print its name + ld (nxtfile),hl ; Save ptr to next file + call print + db ' Set to R','/'+80h + ld hl,(fatt) ; Get attributes + ld c,'W' ; Assume r/w + ld a,h ; Get r/o bit + or a + jr z,att6 + ld c,'O' ; Set r/o +att6: + ld a,c ; Get char + call conout + ld a,l ; Get sys flag + or a ; Set flag + jr z,att7 + call print + db ' and SY','S'+80h +att7: +inspect equ $+1 ; Ptr for in-the-code modification + ld a,0 ; Get inspect flag + or a ; Z=no + pop hl ; Get ptr to current file + jr z,att8 + call eraq1 ; Ask for y/n + jr nz,att4 ; Advance to next file if not y +att8: + ld de,fcb1+1 ; Copy into fcb1 + ld b,11 ; 11 bytes + call blkmov +fatt equ $+1 ; Ptr for in-the-code modification + ld hl,0 ; Get attributes + dec de ; Pt to sys byte + dec de + ld a,l ; Get sys flag + call attset ; Set attribute correctly + dec de ; Pt to r/o byte + ld a,h ; Get r/o flag + call attset + ld de,fcb1 ; Pt to fcb + ld c,30 ; Set attributes + call bdos + jr att4 +attset: + or a ; 0=clear attribute + jr z,attst1 + ld a,(de) ; Get byte + or 80h ; Set attribute + ld (de),a + ret +attst1: + ld a,(de) ; Get byte + and 7fh ; Clear attribute + ld (de),a + ret +; +; End RCP-PROT.Z80 + + endif ;proton + + if cpon +; include rcpcp ; 'CP' file copying command + +; RCP-CP.Z80 + +;============================================================================= +; +; F I L E C O P Y C O M M A N D +; +;============================================================================= + +; Command: CP +; Function: Copy a file from one place to another +; Syntax: CP destfile=srcfile +; CP srcfile +; Comments: Both file specifications can include a directory specification. +; If only one file name is given, then the current directory and +; the source file name are assumed for the destination. + +copy: + call retsave + +; If new is blank, make it the same name and type as old + + ld de,fcb1+1 ; Point to destination file name + ld a,(de) ; Get first character + cp ' ' ; If not blank (no name) + jr nz,copy0 ; ..then branch to copy + + ld hl,fcb2+1 ; Copy source name into destination FCB + ld b,11 ; Name and type are 11 bytes + call blkmov + +; See if destination is same as source, and abort if so + +copy0: + ld hl,fcb1 ; Set up pointers to two files + ld de,fcb2 + push hl + push de + inc hl ; Point to names of files + inc de + ld b,13 ; Compare 13 bytes (name, type, and user #) +copy1: + call comp + jr nz,copy2 ; If they differ, go on with copy + + ld c,25 ; Get-current-disk BDOS function + call bdos ; Get it in case no drive given explicitly + inc a ; Shift to range 1..16 + ld b,a ; ..and keep value in B + pop de ; Restore pointers to FCBs + pop hl + ld a,(de) ; Get drive of source file + ld c,a ; ..and save it in C + or a ; Is it default drive? + jr nz,copy1a ; Branch if drive made explicit + ld c,b ; Otherwise, copy default drive into C +copy1a: + ld a,(hl) ; Get drive of destination file + or a ; Is it default drive? + jr nz,copy1b ; Branch if drive made explicit + ld a,b ; Otherwise, get current drive +copy1b: + cp c ; Compare the two drives specified + jr nz,copy3 ; Branch if they are different + jr cperr ; Branch to error code if they are the same + +copy2: + pop de ; Clean up the stack + pop hl + +; Make note of the user numbers of the two files + +copy3: + ld a,(fcb1+13) ; Get destination user number + ld (usrdest),a + ld a,(fcb2+13) ; Get source user number + ld (usrsrc),a + +; Set up new FCB for source file and open the source + + call define ; Define buffer addresses dynamically + ld hl,(srcfcb) ; Get address to use for new source FCB + push hl + ex de,hl ; Copy file data to new FCB + ld b,12 + call blkmov + call logsrc ; Log in user number of source file + pop hl ; Initialize the source file FCB + call initfcb2 + ld c,15 ; Open file + call bdos + inc a ; Check for error + jp z,prfnf ; Branch if file not found + +; Make sure destination file does not already exist + + call logdest ; Log into destination s user area + call extest ; Test for existence of file + jp z,exit ; Branch if it exists + +; Create destination file + + ld de,fcb1 ; Point to destination FCB + ld c,22 ; BDOS make-file function + call bdos + inc a ; Test for error (no directory space) + jr nz,copy5 ; Branch if OK + +; Report file error + +cperr: + call print + db ' Copy','?'+80h + jp exit + +; Copy source to destination with buffering + +;++++++++++ this should be done by changing DMA address to save all the +; buffer swapping + +copy5: + call logsrc ; Log in source user area + ld b,0 ; Initialize counter + ld hl,(cbuff) ; Initialize buffer pointer + +copy5a: + push hl ; Save address and counter + push bc + ld hl,(srcfcb) ; Point to source file FCB + ex de,hl ; Put it in DE for BDOS call + ld c,20 ; BDOS read-sequential function + call bdos + pop bc ; Get counter and address + pop de + or a ; Read Ok? + jr nz,copy5b ; Branch if end of file + + push bc ; Save counter + ld hl,tbuff ; Copy from 80h to buffer + ld b,128 ; 128 bytes + call blkmov + ex de,hl ; HL points to next buffer address + pop bc ; Get counter back + inc b ; Increment it + ld a,b ; See if buffer full + cp cpblocks + jr nz,copy5a ; If not, go back for more + +copy5b: + ld a,b ; Get count of blocks loaded into buffer + or a ; Are there any? + jr z,copy6 ; Branch if not (we are done) + + push bc ; Save count + call logdest ; Log into destination user number +cbuff equ $+1 ; Pointer for in-the-code modification + ld hl,0 ; Point to beginning of copy buffer +copy5c: + ld de,tbuff ; Copy into tbuff + ld b,128 ; 128 bytes + call blkmov + push hl ; Save pointer to next block + ld de,fcb1 ; Point to destination file FCB + ld c,21 ; Write the block + call bdos + or a + jr nz,cperr ; Branch on error (disk full of write error) + pop hl ; Get back pointer to next block + pop bc ; Get count +; djnz copy5 ; Work through the blocks + dec b ; + jr z,copy5 ; + push bc ; Save count + jr copy5c ; Back for another bufferful + +; Close the destination file + +copy6: + call logdest ; Log into destination user number + ld de,fcb1 ; Point to destination FCB + ld c,16 ; Close file + call bdos + call print + db ' Don','e'+80h + + if cpsp and spaceon + jp spaexit ; Report space remaining on destination drive + else + jp exit + endif ;cpsp and spaceon + +; Log into user number of source file + +logsrc: +usrsrc equ $+1 ; Pointer for in-the-code modification + ld a,0 ; Get user number + jr setusrrel ; Local jump to save code + +; Log into user number of destination file + +logdest: +usrdest equ $+1 ; Pointer for in-the-code modification + ld a,0 ; Get user number +setusrrel: + jp setusr + +; End RCP-CP.Z80 + + endif ;cpon + + if peekon or pokeon or porton +; include rcpiom ; 'PEEK', 'POKE', 'PORT' commands + page + +; RCP-IOM.Z80 + +; Command: PEEK +; Function: Display memory contents +; +; Form: +; PEEK startadr 256 bytes displayed +; PEEK startadr endadr range of bytes displayed + + if peekon + +peek: + call retsave + + ld hl,tbuff+1 ; Find first number +nxtpeek equ $+1 ; Pointer for in-the-code modification + ld de,100h ; Default peek address if none + call sksp ; Skip to first token (if any) + call nz,hexnum ; Get start address if any + + push de ; Save starting address + ld bc,255 ; Compute default ending address + ex de,hl + add hl,bc + + if peekchk ; Check for overflow + jr nc,peek0 ; If no overflow past FFFF, go on + ld hl,0ffffh ; Else use FFFF as ending address +peek0: + endif ;peekchk + + ex de,hl ; End address in DE + call sksp ; Skip to next token (if any) + call nz,hexnum ; Get 2nd number in DE (else default) + +peek1: + pop hl ; HL is start address, DE is end address + + if peekhdr + + push hl ; Save starting address again + ld b,8 ; Output leading spaces +peek0a: + call print + db ' '+80h + djnz peek0a + + ld b,16 ; Display 16 column headers +peek0b: + ld a,l + and 03h + call z,spac + + call spac + call spac + ld a,l ; Get low byte of address + and 0fh ; Display low hex digit + call pah + inc hl + djnz peek0b + + if peekbdr + call crlf + ld b,8 +peek0c: + call print + db ' '+80h + djnz peek0c + ld b,16 +peek0d: + ld a,l + and 3 + call z,spac + inc l + call print + db ' -', '-'+80h + djnz peek0d + endif ;peekbdr + + pop hl ; Restore starting address + + endif ;peekhdr + + ld c,0ffh ; Use C as continue flag + call peek2 ; Do peek + ld (nxtpeek),hl ; Set continued peek address + jp exit + +peek2: + ld a,c ; Check continuation flag + or a ; + ret z ; + +; Print line header + +peek2a: + call crlf ; New line + ld a,h ; Print address + call pashc + ld a,l + call pahc + call dash ; Print leader + ld b,16 ; 16 bytes to display + push hl ; Save start address + + ; Print hex values for 16 bytes + +peek3: + ld a,l + and 03h + call z,spac + + ld a,(hl) ; Get next byte + call pashc ; Print with leading space + + ; Check for last address + ; If c is already 0, leave it that way. + ; Otherwise check for end address and if so + ; Set c to zero. + + ld a,c ; See if continue flag already cleared + or a + jr z,peek3a ; If so, skip test + ld a,h + sub a,d ; See if h = d + ld c,a + ld a,l + sub a,e ; See if l = e + or c ; Combine two tests + ld c,a + +peek3a: inc hl ; Pt to next + djnz peek3 + + ; Print ascii equivalents for 16 bytes + + pop hl ; Pt to first address again + ld b,16 ; 16 bytes + call print ; Space and fence + db ' ' + db fence+80h + push bc ; Save flag in c +peek4: + ld a,(hl) ; Get next byte + ld c,'.' ; Assume dot + and 7fh ; Mask it + cp ' ' ; Dot if less than space + jr c,peek5 + cp 7fh ; Don't print del + jr z,peek5 + ld c,a ; Char in c +peek5: + ld a,c ; Get char + call conout ; Send it + inc hl ; Pt to next + djnz peek4 + + call print ; Closing fence + db fence+80h + pop bc ; Get flag in c back + call break ; Allow abort + jr peek2 + + endif ; Peekon +; +; PRINT A AS 2 HEX CHARS +; PASHC - LEADING SPACE +; + if peekon or [pokeon and not pokeq] or porton +pashc: + push af ; Save a + call spac + pop af +pahc: + push af + rrca ; Exchange nybbles + rrca + rrca + rrca + call pah ; Print hex char + pop af +pah: + and 0fh ; Mask + add a,'0' ; Convert to ascii + cp '9'+1 ; Letter? + jr c,pah1 + add a,7 ; Adjust to letter +pah1: + jp conout +; + endif ; Peekon or [pokeon and not pokeq] or porton +; +;Section 5I +;Command: POKE +;Function: Place Values into Memory +; +;Form: +; POKE startadr val1 val2 ... +; + if pokeon +poke: + call retsave + ld hl,tbuff+1 ; Pt to first char + call sksp ; Skip to non-blank + jr z,noargs ; Arg error + call hexnum ; Convert to number + + if not pokeq + call print + db ' Pok','e'+80h + call adrat ; Print at message + endif + +; LOOP FOR STORING HEX VALUES SEQUENTIALLY VIA POKE + +poke1: + push de ; Save address + call sksp ; Skip to non-blank + jp z,exit ; Done + cp '"' ; Quoted text? + jr z,poke2 + call hexnum ; Get number + ld a,e ; Get low + pop de ; Get address + ld (de),a ; Store number + inc de ; Pt to next + jr poke1 +; +; STORE ASCII CHARS +; +poke2: + pop de ; Get next address + inc hl ; Pt to next char +poke3: + ld a,(hl) ; Get next char + or a ; Done? + jp z,exit + ld (de),a ; Put char + inc hl ; Pt to next + inc de + jr poke3 + + endif ; Pokeon +; +; No Argument Error +; + + if pokeon or porton + +noargs: + call print + db ' Arg','?'+80h + jp exit +; + endif ; Pokeon or porton + +; +;Section 5I+ +;Command: PORT +;Function: Display or Set I/O Port Data +; +;Form: +; PORT addr - read port and display value +; PORT addr value - output value to port +; + if porton +port: + call retsave + ld hl,tbuff+1 ; Find first number + call sksp ; Skip to first command-line token + jr z,noargs ; Abort if no port address given + call hexnum ; Get start address into de + push hl ; Save pointer to command tail + ld hl,portaddr ; Modify code + ld (hl),e ; Move specified port addr into place + dec hl ; Point to opcode position + ld (hl),0dbh ; Poke 'in' opcode + ex (sp),hl ; Get tail pointer back while saving this one + call print ; Print header + db ' Por','t'+80h + ld a,e + call pashc ; Print port address + call sksp ; Skip to possible second value + jr z,portin ; Proceed with port input + + call hexnum ; Get 2nd number in de + ex (sp),hl ; Get pointer to opcode back + ld (hl),0d3h ; Poke 'out' opcode + call print + db ': OU','T'+80h + ld a,e ; Get value to output + jr paddr + +portin: call print + db ': I','N'+80h + xor a ; Make sure high port address = 0 (for HD64180) +paddr: ld b,0 ; ..for both IN and OUT instructions +opcode: + db 0 ; Opcode for IN or OUT inserted by code above +portaddr: + db 0 ; Port address inserted by code above + call pashc + pop hl ; Clean up stack + jp exit + + endif ; Porton + +; End RCP-IOM.Z80 + + endif ;peekon or pokeon or porton + + if regon +; include rcpreg ; 'REG' register operation commands + page + +; RCP-REG.Z80 + +; +;Section 5J +;Command: REG +;Function: Manipulate Memory Registers +; +;Forms: +; REG D or REG <-- Display 10 Register Values +; REG Mreg <-- Decrement Register Value +; REG Preg <-- Increment Register Value +; REG Sreg value <-- Set Register Value +; +; Vers 2.1 Joe Wright +; +; REG reg <-- Display a single register value +; +; REG numbers now range from 0 to 31, although only the first ten are +; displayed with REG D. +; +; REG now treats the program error byte as register E. +; +register: + ld de,fcb1+2 ; Pt to first arg + ld a,(de) ; Get possible digit + call regptr ; Pt HL to potential register + dec de ; Point to command + ld a,(de) + cp 'S' ; Set? + jr z,rset + cp 'P' ; Plus? + jr z,rinc + cp 'M' ; Minus? + jr z,rdec + cp ' ' + jr z,rshow + cp 'D' + jr z,rshow + call regptr + jp regout + +; INCREMENT REGISTER VALUE +; HL PTS TO MEMORY REGISTER ON INPUT + +rinc: + inc (hl) ; Increment it + jr regout ; Print result + +; DECREMENT REGISTER VALUE +; HL PTS TO MEMORY REGISTER ON INPUT + +rdec: + dec (hl) ; Decrement value + jr regout ; Print result + +; Show first ten registers and Program Error byte +; +rshow: + call rshow10 + ld hl,z3msg+6 + jp regout + +rshow10: + xor a ; Select register 0 + ld b,a ; Counter set to 0 in B + call regp1 ; HL pts to register 0 +rshow1: + ld a,b ; Get counter value + cp 10 ; First ten registers + ret z ; Exit if done + push bc ; Save counter + push hl ; Save pointer + call regout ; Print register value + pop hl ; Get pointer + pop bc ; Get counter + inc b ; Increment counter + ld a,b ; Check for new line + and 3 + call z,crlf ; Newline after fourth display + inc hl ; Pt to next register + jr rshow1 + +; SET REGISTER VALUE +; HL PTS TO REGISTER ON INPUT + +rset: + ld de,fcb2+1 ; Pt to value + call de2bin ; Eval string at DE to binary in B + ld (hl),b ; Set value + +; Enter with HL pointing to the register. HL is maintained. +; +regout: + call print + db ' Reg',' '+80h + ld de,z3msg+30h ; Register 0 + sbc hl,de ; Register number in HL + ld a,l + cp 32 ; A numbered Register? + jr c,rego0 ; Yep + call print + db ' ','E'+80h + jr rego1 ; Report + +rego0: push hl + push de + ld b,0 ; Suppress zeros + call decdsp2 ; Report register number + pop de + pop hl + +rego1: add hl,de ; HL points to register again + call print + db ' =',' '+80h + ld l,(hl) + xor a + ld h,a + ld b,a ; Suppress leading zeros + jp decdsp3 ; Display value + +; Evaluate decimal string at DE to binary in B +; +de2bin: + ld b,0 ; Init value to zero +de2b: + ld a,(de) ; Get this digit + inc de ; Pt to next + sub '0' ; Convert to binary + ret c ; A space, finished + cp 10 ; Range? + ret nc ; Not decimal, finished + ld c,a ; Digit in c + ld a,b ; Multiply old by 10 + add a,a ; *2 + add a,a ; *4 + add a,b ; *5 + add a,a ; *10 + add a,c ; Add in new digit + ld b,a ; Result in b + jr de2b ; Again + +; SET HL TO POINT TO MEMORY REGISTER WHOSE INDEX IS PTED TO BY HL +; ON INPUT, A CONTAINS REGISTER CHAR +; ON OUTPUT, HL = ADDRESS OF MEMORY REGISTER (REG 0 ASSUMED IF ERROR) + +regptr: + ld hl,z3msg+6 ; The E register + cp 'E' + ret z + push de + call de2bin ; Get register number in B + pop de + ld a,b + cp 32 ; Range 0-31 + ld a,0 + jr nc,regp1 ; Out of range, use 0 + ld a,b ; Value in A +regp1: + ld hl,z3msg+30h ; Pt to memory registers + add a,l ; Pt to proper register + ld l,a + ret ; No chance of crossing page boundary +; +; End RCP-REG.Z80 + + endif ;regon + + if whlon or whlqon +; include rcpwhl ; 'WHL' and 'WHLQ' commands + page +; +;Section 5K +;Command: WHL/WHLQ +;Function: Set the Wheel Byte on or off +; +;If WHLQUIET equate is true, then RCP does not report wheel status with WHL +;command. +; +;Form: +; WHL -- turn Wheel Byte OFF +; WHL password -- turn Wheel Byte ON if password is correct +; WHLQ -- find out status of Wheel Byte +; +; Vers 2.1 Changes the function a little as follows: +; +; WHL -- Report Wheel Status (no WHLQ) +; WHL password -- Set Wheel ON if password is correct +; -- Set Wheel OFF if password incorrect +whl: + ld hl,fcb1+1 ; Pt to first char + ld a,(hl) ; Get it + + if not whlqon + cp ' ' + jr z,whlmsg ; Report wheel status if no password + endif + + ld de,whlpass + ld b,8 ; Check 8 chars + call comp ; Compare + jr nz,whloff ; Set wheel OFF if incorrect password + +; TURN ON WHEEL BYTE + + ld a,0ffh ; Turn on wheel byte + jr whlset + +; TURN OFF WHEEL BYTE + +whloff: + xor a ; Turn off wheel byte +whlset: + ld hl,(z3whl) ; Indirect from z3env + ld (hl),a +whlq: + if whlquiet + ret + endif + +; PRINT WHEEL BYTE MESSAGE + + if not whlquiet + +whlmsg: + call print + dc ' Wheel ' + call whlchk ; Check wheel byte + jr z,offm + call print + dc 'On' + ret +offm: + call print + dc 'Off' + ret + + endif ;[not whlquiet] or whlqon + + +; WHEEL PASSWORD DEFINED FROM SYSRCP.LIB FILE + + db 'Z'-'@' ; Leading ^z to block attempt to type rcp file +whlpass: + wpass ; Use macro +; +; End RCP-WHL.Z80 + + endif ;whlon + + if echoon +; include rcpecho ; 'ECHO' command + page + +; RCP-ECHO.Z80 + +;============================================================================= +; +; E C H O T E X T T O S C R E E N A N D P R I N T E R +; +;============================================================================= + +; Command: ECHO +; Function: Echo text to console or printer + +echo: + xor a ; Lower case flag setting + + if upcase ; If upper case default + dec a + endif ;upcase + + ld (casefl),a ; Store flag in code below + + ld hl,tbuff+1 ; Point to first character + call getchar ; Get first character (should be blank) + ; If none, exit from routine + + if echolst + call getchar ; Get first char after leading blank + ld b,a ; Save first char as list output flag + cp '$' ; Print flag? + jr z,echo2 ; If so, go on + dec hl ; Else backup one character + endif ; Echolst + +; LOOP TO ECHO CHARS + +echo2: call getchar + + if echolst + cp ff ; Form feed? + jr z,echo3 + endif ;echolst + + cp '^' + jr nz,echo2a ; Not control character prefix + call getchar ; Get next character + and 1fh ; Convert to control character + jr echo2d ; Echo it + +echo2a: cp cmdchar ; Case shift prefix? + jr nz,echo2d ; No, normal echo + call getchar ; Get next character + cp ucasechar ; Up-shift character? + jr z,echo2c ; Store non-zero value in case flag + +echo2b: cp lcasechar ; Lower-case character? + jr nz,echo2d ; No, echo the character as is + xor a ; Else, clear case flag +echo2c: ld (casefl),a + jr echo2 ; On to next character + +echo2d: + call echout ; Send char + jr echo2 + +; FORM FEED - SEND NEW LINE FOLLOWED BY FORM FEED IF PRINTER OUTPUT + + if echolst +echo3: + ld a,b ; Check for printer output + cp '$' + jr nz,echoff ; Send form feed normally if not printer + call echonl ; Send new line + ld a,ff ; Send form feed + jr echout + +; SEND FORM FEED CHAR TO CONSOLE + +echoff: + ld a,ff ; Get char + jr echo2d + endif ;echolst + +; END OF PRINT LOOP - CHECK FOR PRINTER TERMINATION + +echo4: + if not echolst + + ret + + else + + ld a,b ; Get list mode flag + cp '$' + ret nz ; Done if no printer output + +; OUTPUT A NEW LINE + +echonl: + ld a,cr ; Output new line on printer + call echout + ld a,lf ; Fall thru to echout + + endif ;not echolst + +; OUTPUT CHAR TO PRINTER OR CONSOLE + +echout: + ld c,a ; Char in c + + cp 'A' ; If less than 'a' + jr c,echouta ; Leave as is + cp 'Z'+1 ; If greater than 'z' + jr nc,echouta ; Leave as is + add 20h ; Else convert to lower case +echouta: + ld d,a ; Save lower case version in d +casefl equ $+1 ; Pointer for in-the-code modification + ld a,0 + or a + jr nz,echoutb ; If upper case selected, go on as is + ld c,d ; Else substitute lower case version +echoutb: + + push hl ; Save hl + push bc ; Save bc + ld de,0ch-3 ; Offset for console output + + if echolst + ld a,b ; Check for printer output + cp '$' + jr nz,echout1 + inc de ; Add 3 for printer offset + inc de + inc de + endif ;echolst + +; OUTPUT CHAR IN C WITH BIOS OFFSET IN DE + +echout1: + call biout ; Bios output + pop bc ; Restore bc,hl + pop hl + ret + +; Get a character from the command tail buffer + +getchar: + ld a,(hl) ; Get character + inc hl ; Point to next one + or a ; Check for end of string + ret nz ; If not end, return + pop hl ; Else, clean up stack + jr echo4 ; And exit from routine + +; OUTPUT CHAR IN C TO BIOS WITH OFFSET IN DE + +biout: + ld hl,(wboot+1) ; Get address of warm boot + add hl,de ; Pt to routine + jp (hl) ; Jump to it + +; End RCP-ECHO.Z80 + + endif ;echoon + +; include rcpsubs ; File of subroutines + page + +; RCPSUBS.Z80 Subroutines for Z33RCP.Z80 + + +;----------------------------------------------------------------------------- + +; Display decimal digit routines + +;-------------------- + +; Display hundreds, tens, and units digits (assumes flag in B has been set) + + if regon or spaceon + +decdsp3: + ld de,100 ; Display hundreds + call decdsp +decdsp2: + ld de,10 ; Display tens + call decdsp + ld a,l ; Get remaining units value + add '0' ; Convert to character + jr conout ; Print it and return + +;-------------------- + +; Routine to print any single digit + +; Actually, this routine displays the value of HL divided by DE and leaves the +; remainder in HL. In computing the character to display, it assumes that the +; result of the division will be a decimal digit. If the result is zero, the +; value in the B register, which is the number of digits already printed, is +; checked. If it is zero, a space is printed instead of a leading '0'. If it +; is not zero, the '0' is printed. Whenever any digit (not a space) is +; printed, the value in B is incremented. + +decdsp: + ld c,'0'-1 ; Initialize digit count + xor a ; Clear carry flag + +decdsp1: + inc c ; Pre-increment the digit + sbc hl,de ; Subtract DE from HL + jr nc,decdsp1 + + add hl,de ; Add back in to produce remainder + ld a,c ; Get decimal digit + cp '0' ; Check for leading 0 + jr nz,decdout ; If not 0, proceed to display it + ld a,b ; Digit printed already? + or a + ld a,' ' ; Possible space for calling routine to print +; ret z ; If no digit printed, return zero flag set + jr z,conout ; Print leading space +decdout: + inc b ; Indicate digit printed + ld a,c ; Else print real digit + ; Fall through to CONOUT + + endif ;regon or spaceon + +;----------------------------------------------------------------------------- + +; Console Output Routine + +conout: + putreg ; Save all register except AF + push af ; Save AF, too + and 7fh ; Mask out MSB + ld e,a ; Transfer character to E + ld c,2 ; BDOS conout function number + call bdos + pop af + getreg ; Restore registers +note: ; Use this RET for NOTE command + ret + + if peekon or [pokeon and not pokeq] or porton +spac: ld a,' ' + jr conout + endif ; peekon or [pokeon and not pokeq] or porton + + +;----------------------------------------------------------------------------- + +; String printing routines + +;-------------------- + +; Print string following call (terminated with null or character with the +; high bit set) + +print: + ex (sp),hl ; Get address + call printhl + ex (sp),hl ; Put address + ret + +;-------------------- + +; Print string pointed to by HL (terminated with null or character with the +; high bit set) + +printhl: + ld a,(hl) ; Get next character + inc hl ; Point to following one + or a ; See if null terminator + ret z ; If so, we are done + call conout ; Display the character + ret m ; We are done if MSB is set (negative number) + jr printhl ; Back for more + +;----------------------------------------------------------------------------- + +; OUTPUT NEW LINE TO CON: + +crlf: + call print + db cr,lf+80h + ret + +; CONSOLE INPUT + + if eraon or lton or proton or renon or cpon + +conin: + push hl ; Save regs + push de + push bc + ld c,1 ; Input + call bdos + pop bc ; Get regs + pop de + pop hl + and 7fh ; Mask msb + cp 61h + ret c + and 5fh ; To upper case + ret + + endif ; Eraon or lton or proton or renon or cpon +; +; SAVE RETURN ADDRESS +; +retsave: + pop de ; Get return address + pop hl ; Get return address to zcpr3 + ld (z3ret),hl ; Save it + push hl ; Put return address to zcpr3 back + push de ; Put return address back + ret + +; + if spaceon and [dirsp or cpsp or erasp] +spaexit: + call crspace ; Show space remaining + endif ; Spaceon and [dirsp or cpsp or erasp] +; +; EXIT TO ZCPR3 +; +exit: +z3ret equ $+1 ; Pointer to in-the-code modification + jp 0 ; Return address + +; +; PRINT A DASH +; + if lton or peekon +dash: + call print + db ' -',' '+80h + ret +; + endif ; Lton or peekon +; +; PRINT ADDRESS MESSAGE +; PRINT ADDRESS IN DE +; + if peekon or pokeon + if not pokeq +adrat: + call print + db ' at',' '+80h + ld a,d ; Print high + call pahc + ld a,e ; Print low + jp pahc + + endif ; Not pokeq + endif ; Peekon or pokeon + + if peekon or pokeon or porton +; +; EXTRACT HEXADECIMAL NUMBER FROM LINE PTED TO BY HL +; RETURN WITH VALUE IN DE AND HL PTING TO OFFENDING CHAR +; +hexnum: + ld de,0 ; De=accumulated value +hnum1: + ld a,(hl) ; Get char + cp ' '+1 ; Done? + ret c ; Return if space or less + inc hl ; Pt to next + sub '0' ; Convert to binary + jr c,numerr ; Return and done if error + cp 10 ; 0-9? + jr c,hnum2 + sub 7 ; A-f? + cp 10h ; Error? + jr nc,numerr +hnum2: + push hl ; Save pointer + ex de,hl + add hl,hl + add hl,hl + add hl,hl + add hl,hl ; DE x16 to HL + ld e,a + ld d,0 + add hl,de + ex de,hl ; DE = DE * 16 + A + pop hl ; Get the pointer + jr hnum1 ; Try again +; +; NUMBER ERROR +; +numerr: + call print + db ' Num','?'+80h + jp exit +; +; SKIP TO NEXT NON-BLANK +; +sksp: + ld a,(hl) ; Get char + inc hl ; Pt to next + cp ' ' ; Skip spaces + jr z,sksp + dec hl ; Pt to good char + or a ; Set eol flag + ret +; + endif ; Peekon or pokeon or porton + +;----------------------------------------------------------------------------- + +; Test File in FCB for unambiguity and existence, ask user to delete if so +; Return with Z flag set if R/O or no permission to delete +; + if renon or cpon +extest: + call ambchk ; Ambiguous file names not allowed + call searf ; Look for specified file + jr z,exok ; Ok if not found + call getsbit ; Position into dir + inc de ; Pt to file name + ex de,hl ; Hl pts to file name + push hl ; Save ptr to file name + call prfn ; Print file name + pop hl + call rotest ; Check for r/o + jr nz,exer + call eraq ; Erase? + jr nz,exer ; Restart as error if no + ld de,fcb1 ; Pt to fcb1 + ld c,19 ; Delete file + call bdos +exok: + xor a + dec a ; Nz = ok + ret +exer: + xor a ; Error flag - file is r/o or no permission + ret + +; +; CHECK FOR AMBIGUOUS FILE NAME IN FCB1 +; RETURN Z IF SO +; +ambchk: + ld hl,fcb1+1 ; Pt to fcb +; +; CHECK FOR AMBIGUOUS FILE NAME PTED TO BY HL +; +ambchk1: + push hl + ld b,11 ; 11 bytes +amb1: + ld a,(hl) ; Get char + and 7fh ; Mask + cp '?' + jr z,amb2 + inc hl ; Pt to next + djnz amb1 + dec b ; Set nz flag + pop de + ret +amb2: + pop hl ; Pt to file name + call prfn + call print + db ' is AF','N'+80h + jp exit +; + endif ; Renon or cpon +; +; TEST FILE PTED TO BY HL FOR R/O +; NZ IF R/O +; + if renon or cpon or eraon +; +rotest: + push hl ; Advance to r/o byte + ld bc,8 ; Pt to 9th byte + add hl,bc + ld a,(hl) ; Get it + and 80h ; Mask bit + push af + ld hl,romsg + call nz,printhl ; Print if nz + pop af ; Get flag + pop hl ; Get ptr + ret +romsg: + db ' is R/','O'+80h +; +; CHECK USER TO SEE IF HE APPROVES ERASE OF FILE +; RETURN WITH Z IF YES +; +eraq: + call print + db ' - Eras','e'+80h + endif ; Renon or cpon or eraon + + if renon or cpon or eraon or proton +eraq1: + call print + db ' (Y/N/Q)?',' '+80h + call conin ; Get response + cp 'Q' ; Quit command? + jp z,exit + cp 'Y' ; Key on yes + ret +; + endif ; Renon or cpon or eraon or proton +; +; INIT FCB1, RETURN WITH DE PTING TO FCB1 +; + if eraon or lton or cpon +initfcb1: + ld hl,fcb1 ; Pt to fcb +initfcb2: + push hl ; Save ptr + ld bc,12 ; Pt to first byte + add hl,bc + ld b,24 ; Zero 24 bytes + xor a ; Zero fill + call fillp ; Fill memory + pop de ; Pt to fcb + ret +; + endif ; Eraon or lton or cpon +; + if eraon or lton or cpon or diron + +fillp: + ld (hl),a ; Store byte + inc hl ; Pt to next + djnz fillp ; Count down + ret +; + endif ; Eraon or lton or cpon or diron + +; +; CHECK FOR USER INPUT; IF ^C, RETURN WITH Z +; + if diron or lton or eraon or proton or peekon + +break: + push hl ; Save regs + push de + push bc + ld c,11 ; Console status check + call bdos + or a + ld c,1 ; Get char if any + call nz,bdos + pop bc ; Restore regs + pop de + pop hl +break1: cp ctrlc ; Check for abort + jp z,exit ; Exit + cp ctrlx ; Skip? + ret + endif ; Diron or lton or eraon or proton or peekon + +; AFTER A SEARCH, RETURN NZ SET IF DESIRED TYPE OF FILE FOUND, Z IF NOT +; THIS ALGORITHM LOOKS AT THE SYSTEM BIT OF THE LOCATED FILE; THIS +; BIT IS SET TO 1 IF THE FILE IS A SYSTEM FILE AND 0 IF NOT A SYSTEM +; FILE. THE FOLLOWING EXCLUSIVE OR MASKS ARE APPLIED TO RETURN Z OR NZ +; AS REQUIRED BY THE CALLING PROGRAM: +; +; SYSTEM BYTE: X 0 0 0 0 0 0 0 (AFTER 80H MASK, X=1 IF SYS, 0 IF DIR) +; +; SYS-ONLY : 0 0 0 0 0 0 0 0 (XOR 0 = 0 if X=0, = 80H if X=1) +; DIR-ONLY : 1 0 0 0 0 0 0 0 (XOR 80H = 80h if X=0, = 0 if X=1) +; BOTH : 0 0 0 0 0 0 0 1 (XOR 1 = 81H or 1H, NZ in both cases) + + if diron or eraon or lton or proton or cpon or renon + +getsbit: + dec a ; Adjust to returned value + rrca ; Convert number to offset into tbuff + rrca + rrca + and 60h + ld de,tbuff ; Pt to buffer + add a,e ; Add entry offset to base addr + ld e,a ; Result in e + push de ; Save ptr in de + add 10 ; Add offset of 10 to pt to system byte + ld e,a ; Set address + ld a,(de) ; Get byte + pop de ; Get ptr in de + and 80h ; Look at only system bit +systst equ $+1 ; In-the-code variable + xor 0 ; If systst=0, sys only; if systst=80h, dir + ; Only; if systst=1, both sys and dir + ret ; Nz if ok, z if not ok +; +; +; COPY HL TO DE FOR B BYTES +; +blkmov: + ld a,(hl) ; Get + ld (de),a ; Put + inc hl ; Pt to next + inc de + djnz blkmov ; Loop + ret + +; +; PRINT FILE NOT FOUND MESSAGE +; +prfnf: + call print + db ' No File','s'+80h + jp exit + +; LOG INTO USER AREA CONTAINED IN FCB1 +; +logusr: + ld a,(fcb1+13) ; Get user number +setusr: + ld e,a + ld c,32 ; Use bdos fct + jp bdos + +; +; PRINT FILE NAME PTED TO BY HL +; +prfn: + call print ; Leading space + db ' '+80h + ld b,8 ; 8 chars + call prfn1 + call print + db '.'+80h ; Dot + ld b,3 ; 3 chars +prfn1: + ld a,(hl) ; Get char + inc hl ; Pt to next + call conout ; Print char + djnz prfn1 ; Count down + ret + +; +; SEARCH FOR FIRST +; +searf: + push bc ; Save counter + push hl ; Save hl + ld c,17 ; Search for first function +searf1: + ld de,fcb1 ; Pt to fcb + call bdos + inc a ; Set zero flag for error return + pop hl ; Get hl + pop bc ; Get counter + ret + + endif ; Diron or eraon or lton or proton or cpon or renon + +;----------------------------------------------------------------------------- + +; Define buffers as high as possible in TPA for the following groups +; of commands: +; COPY needs SRCFCB and CBUFF +; LIST/TYPE needs PAGCNT and DIRBUF +; ERA, PROT, and DIR commands. needs DIRBUF +; If DIRBUF is defined, its value is in HL on return from this code. The DE +; register pair is not changed by the code, but the BC pair is affected. + +dirbufon equ lton or diron or eraon or proton + + if dirbufon +dirbuf: ds 2 ; Address for directory buffer + endif ;dirbufon + + if cpon +srcfcb: ds 2 ; Address of source file FCB (CBUFF address + ; ..is in the code) + endif ;cpon + + if lton +pagcnt: ds 2 ; Address for page counter + endif ;lton + + + if cpon or lton or eraon or proton or diron + +define: + push de + ld hl,(bdos+1) ; Get bottom of BDOS + ex de,hl ; ..into DE + ld hl,(1) ; Get BIOS warmboot address into HL + ld bc,-[0e00h+800h+3] ; Offset to command processor address + add hl,bc + +; Now we have to compare and pick the lower address as the top of TPA + + push hl ; Save CPR address while comparing + xor a ; Clear the carry flag + sbc hl,de ; Compute (CPR-BDOS) + pop hl ; Restore CPR address + jr c,define1 ; Branch if BDOS address is higher (use CPR) + ex de,hl ; Otherwise use BDOS address +define1: + + if lton + dec hl ; Put PAGCNT in first free byte at top of TPA + ld (pagcnt),hl + endif ;lton + + if cpon + ld de,-36 ; Calculate place for SRCFCB for copy command + add hl,de + ld (srcfcb),hl + if dirbufon + push hl ; Save if needed below + endif ;dirbufon + ld de,-[cpblocks*128] ; CBUFF can use same space as DIRBUF + add hl,de + ld (cbuff),hl + if dirbufon + pop hl + endif ;dirbufon + endif ;cpon + + if dirbufon + ld de,-[maxdirs*11] ; Space for directory buffer + add hl,de + ld (dirbuf),hl + endif + + pop de + ret + + endif ;cpon or dirbufon + +;----------------------------------------------------------------------------- + +; +; SEARCH FOR NEXT +; + if diron or eraon or lton or proton + +searn: + push bc ; Save counter + push hl ; Save hl + ld c,18 ; Search for next function + jr searf1 + +; LOAD DIRECTORY AND SORT IT +; ON INPUT, A=SYSTST FLAG (0=SYS, 1=DIR, 80H=BOTH) +; DIRECTORY IS LOADED INTO BUFFER AT TOP OF TPA +; RETURN WITH ZERO SET IF NO MATCH AND HL PTS TO 1ST ENTRY IF MATCH + +direrr: + call print + db 'DIR Ovf','l'+80h + jp exit + +getdir: + ld (systst),a ; Set system test flag + call logusr ; Log into user area of fcb1 + +; LXI H,DIRBUF ; Pt to dir buffer + call define ; Define buffer addresses + ld (hl),0 ; Set empty + ld bc,0 ; Set counter + call searf ; Look for match + ret z ; Return if not found +; +; STEP 1: LOAD DIRECTORY +; +gd1: + push bc ; Save counter + call getsbit ; Check for system ok + pop bc + jr z,gd2 ; Not ok, so skip + push bc ; Save counter + inc de ; Pt to file name + ex de,hl ; Hl pts to file name, de pts to buffer + ld b,11 ; Copy 11 bytes + call blkmov ; Do copy + pop bc ; Get counter + inc bc ; Increment counter + ld hl,maxdirs-1 ; See if count equals or exceeds MAXDIRS + ld a,b ; Check high bytes + sub a,h + jr c,gd1a ; If carry set, we are OK + ld a,c ; Check low bytes + sub a,l + jr nc,direrr ; If no carry, jump to error message +gd1a: + ex de,hl ; Hl pts to next buffer location +gd2: + call searn ; Look for next + jr nz,gd1 + ld (hl),0 ; Store ending 0 +; LXI H,DIRBUF ; Pt to dir buffer + ld hl,(dirbuf) ; Pt to dir buffer + ld a,(hl) ; Check for empty + or a + ret z +; +; STEP 2: SORT DIRECTORY +; + if sorton + push hl ; Save ptr to dirbuf for return + call diralpha ; Sort + pop hl + endif + xor a ; Set nz flag for ok + dec a + ret + +; +; DIRALPHA -- ALPHABETIZES DIRECTORY IN DIRBUF; BC CONTAINS +; THE NUMBER OF FILES IN THE DIRECTORY +; +diralpha: +; +; SHELL SORT -- +; THIS SORT ROUTINE IS ADAPTED FROM "SOFTWARE TOOLS" +; BY KERNIGAN AND PLAUGHER, PAGE 106. COPYRIGHT, 1976, ADDISON-WESLEY. +; + ld h,b ; Hl=bc=file count + ld l,c + ld (n),hl ; Set "N" + ld (gap),hl ; Set initial gap to n for first division by 2 + +; FOR (GAP = N/2; GAP > 0; GAP = GAP/2) +srtl0: + or a ; Clear carry +gap equ $+1 ; Pointer for in-the-code modification + ld hl,0 ; Get previous gap + ld a,h ; Rotate right to divide by 2 + rra + ld h,a + ld a,l + rra + ld l,a + +; TEST FOR ZERO + or h + ret z ; Done with sort if gap = 0 + + ld (gap),hl ; Set value of gap + ld (ii),hl ; Set ii=gap for following loop + +; FOR (II = GAP + 1; II <= N; II = II + 1) +srtl1: +ii equ $+1 ; Pointer for in-the-code modification + ld hl,0 ; Add 1 to ii + inc hl + ld (ii),hl + +; TEST FOR II <= N + ex de,hl ; Ii is in de +n equ $+1 ; Pointer for in-the-code modification + ld hl,0 ; Number of items to sort + ld a,l ; Compare by subtraction + sub a,e + ld a,h + sbc a,d ; Carry set means ii > n + jr c,srtl0 ; Don't do for loop if ii > n + + ex de,hl ; Set jj = ii initially for first subtraction of gap + ld (jj),hl + +; FOR (JJ = II - GAP; JJ > 0; JJ = JJ - GAP) +srtl2: + ld hl,(gap) ; Get gap + ex de,hl ; In de +jj equ $+1 ; Pointer for in-the-code modification + ld hl,0 ; Get jj + ld a,l ; Compute jj - gap + sub a,e + ld l,a + ld a,h + sbc a,d + ld h,a + ld (jj),hl ; Jj = jj - gap + jr c,srtl1 ; If carry from subtractions, jj < 0 and abort + or l ; Jj=0? + + jr z,srtl1 ; If zero, jj=0 and abort + +; SET JG = JJ + GAP + ex de,hl ; Jj in de + ld hl,(gap) ; Get gap + add hl,de ; Jj + gap + ld (jg),hl ; Jg = jj + gap + +; IF (V(JJ) <= V(JG)) + call icompare ; J in de, jg in hl + +; ... THEN BREAK + jr c,srtl1 + +; ... ELSE EXCHANGE + ld hl,(jj) ; Swap jj, jg + ex de,hl +jg equ $+1 ; Pointer for in-the-code modification + ld hl,0 + call iswap ; Jj in de, jg in hl + +; END OF INNER-MOST FOR LOOP + jr srtl2 + +; +; SWAP (Exchange) the elements whose indexes are in HL and DE +; +iswap: + call ipos ; Compute position from index + ex de,hl + call ipos ; Compute 2nd element position from index + ld b,11 ; 11 bytes to flip + endif ; Diron or eraon or lton or proton + + if diron or eraon or lton or proton or renon +iswap1: + ld a,(de) ; Get bytes + ld c,(hl) + ld (hl),a ; Put bytes + ld a,c + ld (de),a + inc hl ; Pt to next + inc de + djnz iswap1 + ret + endif ; Diron or eraon or lton or proton or renon + + if diron or eraon or lton or proton +; +; ICOMPARE compares the entry pointed to by the pointer pointed to by HL +; with that pointed to by DE (1st level indirect addressing); on entry, +; HL and DE contain the numbers of the elements to compare (1, 2, ...); +; on exit, Carry Set means ((DE)) < ((HL)), Zero Set means ((HL)) = ((DE)), +; and Non-Zero and No-Carry means ((DE)) > ((HL)) +; +icompare: + call ipos ; Get position of first element + ex de,hl + call ipos ; Get position of 2nd element + ex de,hl +; +; COMPARE DIR ENTRY PTED TO BY HL WITH THAT PTED TO BY DE; +; NO NET EFFECT ON HL, DE; RET W/CARRY SET MEANS DE/>> to indicate state) + ^W - recall command lines from history stack + ^E - recall history in reverse direction + + +Installation: + + The installation program, CLEDINST, allows you to set up the editor +to your preference. The RCP can be installed directly in memory or in a disk +file -- RCPxxx.ZRL or a system configuration saved with SNAP or NZBLITZ. +CLEDINST also serves as a "help" utility by displaying the current command +bindings. Type "CLEDINST //" for help with CLEDINST.COM. + + +History Tool: + + CLEDSAVE writes the contents of the history stack to a text file on disk. +The file can be reloaded later (CLEDSAVE L), or composed in advance +with a text editor, then loaded. If the file is too large for the history +stack, as many commmand lines as fit are loaded. + + CLEDSAVE is useful as in a startup alias to load frequently used command +lines from an easily altered file. For example: + +Alias: START + +A15: +CLEDSYS <- load SNAP image of ENV,TCAP,RCP,FCP,NDR,QUIET,&PATH +CLEDSAVE CLED.VAR L <- load precomposed command lines into CLED +CLED <- turn the shell on +... <- rest of startup line runs before CLED gets control + +For help with CLEDSAVE.COM, type "CLEDSAVE //". + + You may also use SNAP or NZBLITZ to save the system segment image with +command lines already loaded (turn SAVE OFF first.) + + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpcled.lib b/Source/BPBIOS/Z34RCP11/rcpcled.lib new file mode 100644 index 00000000..6c2879a7 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpcled.lib @@ -0,0 +1,1327 @@ + page + +; Library: RCPCLED for Z34RCP +; Author: Carson Wilson (modifications only) +; Version: 1.3b +; Date: October 8, 1989 +; Changes: Civilian time now prints "12" instead of "0" for midnight hour. +; Put Z3PLUS time display capability back in. +; Z3PLUS time display disabled if date = 01/01/78 (no clock). +; Added Rob Friefeld's fix to SAVE_LINE. +; +; Author: rdf +; Version: 1.3a +; Date: October 2, 1989 +; Changes: Changed order of installable highlight codes and history buffer +; addresses to simplify installing a ZRL file. Changed version +; number to guarantee compatibility with accessory programs. +; Option to print user number zero at prompt. +; Time string separator installable. + +; Author: Carson Wilson (modifications only) +; Version: 1.2 B +; Date: September 29, 1989 +; Changes: Made ">>" the prompt when previous commands are NOT being +; overwritten. +; Changed line edit commands to be closer to CP/M Plus. +; Shortened the clock read code (ZSDOS allows straight +; DateStamper calls). +; Uses DEFINE from RCPSUBS.LIB to save space. +; Does not print prompt user number if at user zero. +; Optional installable highlight codes for time in prompt. +; Time prompt shortened to "hh.mm" for easier reading. + +; Author: Rob Friefeld +; Version: 1.2 a +; Date: September 20, 1989 + +; Syntax: CLED [/] if "/", then run for one command line only +; e.g. from a shell like ZFILER + +; +; ===== D E F I N I T I O N S S E C T I O N ===================== +; + +clver equ 13 ; Version number (Install program compat.) +clrev equ 'b' ; Revision (Does not affect config.) + +del equ 7fh ; not in sysdef.lib + +; +;===== C O N F I G U R A T I O N A R E A ======================== +; + +; The installation program and buffer loader depend on the configuration +; of this data structure. It should not be changed. + +shname: db 'CLED',0 ; Name put on shell stack +versid: db clver ; CLED version +ddsep: db dudir_sep ; DU:DIR separator char +ins_flag: db clins ; Yes = insert mode +minsave: db clmin ; Discard line =< this +era_flag: db clera ; Erase stored line on exit +save_flag: db clsav ; Save command lines +tim_sep: db timesep ; Time string separator + +; --------------------------- + +; Command list for RCPCLED, Version 1.3 +; Set bit 7 to use a command with meta key + +cmd_list: + db 'Q' ; Meta key 1 + db 'H' ; Backspace + db 'S' ; Cursor left + db 'D' ; Cursor right + db 'A' ; Word left + db 'F' ; Word right + db 'B' ; Line end/ start + dc 'S' ; Line start + dc 'D' ; Line end + db 'G' ; Delete char + db DEL ; Delete left + db 'T' ; Delete word + db 'L' ; Delete word left + db 'X' ; Delete to SOL + db 'Y' ; Delete line + db 'K' ; Delete to EOL + db 'V' ; Toggle insert + db 'P' ; Enter control + db 'W' ; Recall line + db 'E' ; Recall reverse + db 'M' ; Execute line + db '[' ; ESC menu + +cmdlen equ $ - cmd_list + +; --------------------------- +; Highlight on/off codes for time display (installable) +; +stndout: db 0,0,0,0 ; Must terminate with hibit or binary 0 +stndend: db 0,0,0,0 ; Ditto + +; --------------------------- +; 4 bytes are used here for the information of the history save/load tool, +; CLEDSAVE + +histaddr: dw history ; Pointer to history buffer +histsz: dw histsize ; Buffer size + +; +;===== M A I N C O D E S E C T I O N ======================== +; +cled: + call define ; Set pointer to free mem + ld de,-lbufwid-1 + add hl,de + ld (line),hl ; Set line buffer location + xor a + ld (hl),a ; Zero line + ld hl,history ; History stack + ld (recall_ptr),hl ; Init position pointer to start + + ld a,(fcb+1) ; Check command line option + cp '/' + jr z,cledit ; Go right to editing + + ld a,(z3msg+3) ; QSHELL + dec a ; <> 1 on manual invocation + jr z,cledit ; Skip installation + +; +;===== S H E L L I N S T A L L A T I O N ========================= +; +sh_inst: + ld hl,shname + call shpush ; Z = OK + ret z + call print ; Complain about stack and cancel + dc cr,lf,'SH STK' ; Full or non-existent + ret + +; +;===== L I N E E D I T =========================================== +; + +; This is the main entry point for the shell +; 1 - Display prompt +; 2 - Get user input +; 3 - Reset shell bit +; 4 - Run command line + + +; Subtask 1 -- +cledit: + call prompt ; Display system prompt + +;---------------------------------------- +; Subtask 2 -- + +; The editor returns NZ if the shell pop command has been given. If not, it +; returns the character count of the command line in B. + + call EDIT + +;---------------------------------------- +; Subtask 3 -- + push af ; Save return code + xor a + ld (z3msg+3),a ; PUTCST + pop af + +;---------------------------------------- +; Subtask 4 -- + jp nz,shpop ; Quit shell + +; Here we load the MCL directly from the line buffer. On OVFL, loop to edit. + +loadcl: + ld a,(z3cl+2) ; MCL size + inc b ; B contains line count, include terminating 0 + cp b ; Compare to line size + jr nc,loadcl1 ; OK + +mclerr: + call print + dc cr,lf,'OVFL',cr,lf + jp cledit + +loadcl1: + ld de,z3cl+4 ; Set MCL pointer to start + ld (z3cl),de + + ld hl,(line) + ld c,b + ld b,0 + ldir ; Move line buff to MCL + ret ; Run it + + +; +;===== S U B R O U T I N E S ======================================= +; + +; Prompt -- PRINT a DU:DIR prompt. +; +prompt: + if systime + call print_time + endif + + ld bc,(cusr) ; GDEFDU + ld a,b ; Drive + add a,'A' ; Make it a letter + call conout ; Write it + ld a,c ; Get user + + if puser0 + call pusr ; Write it + else + or a + call nz,pusr ; Write it IF NONZERO + endif ;puser0 + + call dutdir ; Get the ndr + jr z,prompt1 + ld a,(ddsep) ; DU:DIR separator + call conout + ld b,8 ; Eight chars max +nameloop: + ld a,(hl) ; Get the first char + cp ' ' + call nz,conout ; Write it if not blank + inc hl + djnz nameloop + +prompt1: + call prompt2 + ld a,(save_flag) ; If save is OFF, prompt is >> + or a + ret nz +prompt2: + call print + dc '>' + ret + +; PUSR -- Convert user # in A to decimal and print +; +pusr: + ld hl,10 shl 8 + '0'-1 ; H=10, L='0'-1 + cp h ; User < 10 ? + jr c,pusr1 +pusr0: + inc l ; Advance character for user number tens digit + sub h + jr nc,pusr0 + add a,h + ld h,a ; Keep low digit of user number in H + ld a,l ; Display tens digit + call conout + ld a,h ; Ready to process units digit +pusr1: + jp decout ; Routine in RCPSUBS.LIB +; add '0' +;pusr2: +; jp conout + +; Console input without echo + +cin: + push hl + push de + push bc +cin1: ld c,dirconf ; DCIO + ld e,-1 + call bdos + or a + jr z,cin1 + pop bc + pop de + pop hl + ret + +; +;===== E D I T O R S E C T I O N ================================ +; + +; Date: October 2, 1989 + +; Entry is EDIT +; Return Z = Execute command line, NZ = Quit shell, B = char count of line + +; Initialize to on-line environment. +; While editing, HL -> current position in LINE, B = char count, +; C = cursor position (0 .. count), DE = scratch + +edit: + ld hl,(line) ; Init to start of line + xor a + ld b,a ; Line count = 0 + ld c,a ; Cursor pos = 0 + + push hl ; There may already be a line here + dec b ; Accumulate possible char count in B +edit1: + inc b + cp (hl) ; A = 0 + inc hl + jr nz,edit1 ; Loop until 0 terminator +edit2: + pop hl ; Point to line again + call zline ; Zero the remainder of LINE buffer + call ptail ; Print the line from cursor position + +;-------------------------------------------------------------------- + +; EDIT COMMAND LOOP + +; Get a char. If it is text, enter it. If it is a control, scan the +; CMD_LIST for a match. If found, compute offset into jump table and go. +; A "shifted" key (high bit set in table) is matched after the "meta-key" +; has been entered. + +ecmd: + exx ; Main regs must be preserved + ld hl,ecmd ; Save address so a return comes back here + push hl + +no_match: + call cin ; Next key... + cp 'C'-'@' ; Warm boot? + jp z,0000h + + ld hl,meta_flag ; Shift flag + or (hl) ; Mask in possible high bit + ld (hl),0 ; Reset flag + exx ; Recover main regs + + cp 20h ; Test key + jr c,control_key ; Not text + cp del ; This control char > text chars + jp c,enter ; Input text + +control_key: + call menuinp ; Convert control char to cap + + exx ; Must preserve main regs + ld hl,cmd_list ; Scan command list + ld bc,cmdlen + cpir + jr nz,no_match + ld hl,cmd_vector + ld a,cmdlen-1 ; Point to address in vector table + sub c + add a,a + ld c,a + add hl,bc + ld c,(hl) + inc hl + ld b,(hl) + ld (cjump),bc ; Address to jump to + exx ; Restore regs! + +cjump equ $+1 + jp 0 + + +; Convert a control key entry to cap char + +menuinp: + push af + and 80h ; Keep high bit + ld e,a + pop af + and 7fh + call ucase + or e ; Restore high bit + ret + +; Mark meta-key flag + +meta_key1: + ld a,10000000b + ld (meta_flag),a + ret + +meta_flag: db 0 ; Initial value 0 = no shift + + +; Jump table for commands + +cmd_vector: + dw meta_key1 ; Shift key + dw bsp ; Backspace + dw bsp ; Cursor left + dw fsp ; Cursor right + dw bwrd ; Left word + dw fwrd ; Right word + dw linend ; To EOL + dw linbeg ; To SOL + dw linend1 ; To EOL/SOL + dw delete ; Delete char + dw delft ; Delete char left + dw delwrd ; Delete word right + dw delwlft ; Delete word left + dw delsol ; Delete to start of line + dw dline ; Delete line + dw deleol ; Delete to end of line + dw instog ; Toggle insert + dw ctl_entry ; Enter control char + dw recall_back ; Scroll back in history + dw recall_fwrd ; Scroll ahead in history + dw eds$ex ; Execute line + dw esc_menu ; Submenu + +;-------------------------------------------------------------------- + +; ON-LINE ROUTINES, EDITING CURRENT LINE IN LINE BUFFER + +; WHILE ON LINE: +; B = CHAR COUNT (0..lbufwid) C = CURSOR POSITION (0..lbufwid) +; HL = MEM POSITION + + +; Backspace +; Return Z = backspace not done, NZ = all OK + +bsp: + xor a + cp c ; Cursor pos + ret z ; At start + dec hl ; Back up in mem + dec c ; Cursor pos back +bspace: + ld a,bs ; Back up on screen + or a ; Must ret nz + jp conout + + +; Forward space +; Return Z = not done + +fsp: + ld a,(hl) + or a + ret z ; At EOL + inc hl + inc c + jp pctl ; Screen advance by reprinting char + + +; Back word + +bwrd: + call bsp ; Backspace + ret z ; Nowhere to go + ld a,(hl) + cp ' ' + jr z,bwrd ; Backspace over blanks + dec hl ; Now backspace until next wordsep + call wrdsep ; Look at char before this position + inc hl + jr nz,bwrd + ret + +; Forward word + +fwrd: + call wrdsep ; Are we on a word separator? + jr z,fwrd1 ; Yes + call fsp ; No, advance until we find one + jr fwrd + +fwrd1: call fsp ; Word sep found, advance 1 more space + ld a,(hl) ; Are we on a blank? + cp ' ' + jr z,fwrd1 ; Don't quit on a blank + ret + + +; Delete char left + +delft: + call bsp ; Backspace and fall through to delete + + +; Delete char + +delete: + call delmem ; In memory + jp ptail ; Refresh screen from cursor position + +; Delete to start of line + +delsol: + ld a,c ; Get cursor pos + or a + ret z ; Already at start + cp b + jr z,dline ; At end, so delete entire line (quicker) + ld e,a ; Cursor pos = # chars to delete + call linbeg ; Go to start +delcmd1: + call delmem ; Delete first char in memory + dec e ; Loop counter + jr nz,delcmd1 + jp ptail ; Now update screen + + +; Delete word left + +delwlft: + call bwrd ; Back a word and fall thru ... + + +; Delete word right + +delwrd: + call wrdsep ; On a word sep? + jr z,delete ; Yes, kill it + ld a,b ; Compare line count to cursor pos + cp c + jr z,delete ; On last char of line +delwrd1: + call delmem ; Delete in mem, let screen catch up later + jr delwrd ; Go until word sep found + + +; Delete line + +dline: + call linbeg ; Position at line start and fall thru ... + +; Delete to eoln + +deleol: + call ereol ; Clear on screen + ld b,c ; Char count = current position + jp zline ; Zero line tail in mem + +; Insert/overwrite toggle + +instog: + ld a,(ins_flag) ; Flag 0 -> owrt + cpl + ld (ins_flag),a + ret + +; Enter a control + +ctl_entry: + call cin + and 1fh ; Fall thru to normal char entry + +; Enter a char + +enter: + ex af,af' ; Save char + ld a,b ; At eoln? + cp c + jr z,ovrwrt ; Yes, no need for insert mode + ld a,(ins_flag) ; Which mode are we in? + or a ; 0 = overwrite, nz = insert + jr nz,insert + + +; Enter char in overwrite mode + +ovrwrt: + ld a,b ; Char count + cp lbufwid-2 ; Line full? + jr c,ovr1 ; No + cp c ; At EOLN? + ret z ; Accept no more chars + +ovr1: ex af,af' ; Recover char + ld (hl),a ; Put char in place + call fsp ; Advance by printing it + ld a,b ; Char count -> a + cp c + ret nc ; No need to incr char count inside line + inc b ; Else add to count + ret + +; Enter char in insert mode + +insert: + ld a,b ; Line full? + cp lbufwid-2 + ret nc + +insrt: + ld a,b ; At eoln? + sub c ; A = # chars to eoln + jr z,ovr1 ; Yes, really want overwrite + call insmem ; Push chars down to make room + ex af,af' ; Recover new char + ld (hl),a ; Place char in line + call ptail ; Reprint entire line from here + inc b ; Inc char count + jp fsp ; Advance cursor + + +; Line end/start toggle + +linend: ; Go to eoln or, if there, to start of line + ld a,b + cp c + jr z,linbeg + +linend1: + call fsp ; Print ahead until EOL + jr nz,linend1 + ret + +linbeg: + call bsp ; Backspace until start + jr nz,linbeg + ret + + +; Compare current char to list of word separators + +wrdsep: + push hl + push bc + ld bc,wrdseplen + ld a,(hl) + ld hl,wrdseps + cpir + pop bc + pop hl + ret + +wrdseps: + db 0,' ,;:.' ; Punctuation word separators +wrdseplen equ $ - wrdseps + + +; Delete current char from line + +delmem: + ld (hl),0 ; Terminal 0 or char to be deleted + ld a,b + sub c ; A = (count-position) = chars from end + ret z ; At eoln, no char + dec b + ret z ; Single char line + dec a + ret z ; On last char, just deleted it +delmem1: + inc a ; To move terminal 0 in + push hl + push de + push bc + ld d,h ; Dest is current pos + ld e,l + inc hl ; Source, terminal 0 + ld c,a ; Count, line tail + ld b,0 + ldir ; Block move + pop bc + pop de + pop hl + ret + +; Insert a char in line + +insmem: + push bc ; Make room for char in line + push de + ld c,a ; Bc = # chars to move + ld b,0 + add hl,bc ; Dest is new eoln + ld d,h ; Now in DE + ld e,l + dec hl ; Source is current eoln + lddr ; Tail move + pop de + pop bc ; Recover char count, crs pos info + inc hl ; Hl to next char + ret + + +; Print line tail from cursor position, return to position + +ptail: + push hl ; Save mem pos + push bc ; Save screen pos + call linend1 ; Print ahead to end of line + call ereol ; Clean off danglers +ptail1: + ld a,c ; End of line cursor pos + pop bc + pop hl + sub c ; Current cursor pos + ret z ; At end of line already + ld e,a ; Loop counter +ptail2: + call bspace ; Else back up to where we were + dec e + jr nz,ptail2 + ret + + +; Print a char, turn a control char into a cap char + +pctl: + push af + cp 20h + jr nc,pctl1 + add '@' +pctl1: call conout + pop af + ret + + +; Convert char or control key to upper case + +ucase: + cp ' ' + jr nc,notctl + add '@' +notctl: cp 'a' + ret c ; Not a lowercase + cp 'z'+1 + ret nc ; Not a lowercase + sub ' ' ; Yes, a lowercase + ret + + +; Zero line tail + +zline: + push hl + push bc + ld hl,(line) ; HL -> start of line + ld c,b ; BC = char count + ld b,0 + add hl,bc ; HL -> EOLN + ld a,lbufwid + sub c + dec a + jr z,zline0 + ld b,a ; # of 0's + xor a +zline1: + ld (hl),a + inc hl + djnz zline1 +zline0: + pop bc + pop hl + ret + + +; ESC key pressed - get submenu command + +esc_menu: + call cin + call ucase + cp 'Q' ; Quit + jr z,edquit + cp 'S' ; Toggle Save + ret nz ; Loop if none of these + +; Toggle recording state +; - Alter line prompt to > if save ON, >> if save OFF + +save_tog: + ld a,(save_flag) ; Flip flag byte + cpl + ld (save_flag),a + call crlf + call prompt ; Print new prompt string + pop af ; Lift ecmd from stack + jp edit ; Restart + + +; Exit editor + +eds$ex: + pop af ; Lift ECMD from stack + ld a,(save_flag) ; Are we recording? + or a + ret z ; Nope + + ld a,(minsave) ; Is line worth keeping? + cp b + push bc + call c,save_line + pop bc + +edn$ex: + xor a ; Return Z + ret + + +; Exit and pop shell + +edquit: + pop af ; Lift ECMD from stack + xor a ; Return NZ + dec a + ret + + +; --------------------------- + +; HISTORY STACK ROUTINES for RCPCLED, Version 1.2 + +;Each command line is pushed onto the history stack before execution. As the +;older ones overflow, they are eliminated. The last character of each line +;has the high bit set. The buffer terminates with a 0. +;The history stack is internal to the RCP, but could be implemented in an RSX + +; Save new line to stack +; - This routine called only on exit, so on-line regs not preserved. +; - Push contents down by size of current line +; - Move line buffer to start of stack +; - Terminate line with high bit set +; - Terminate history with 0 after last complete line +; - If current line is too big for buffer size chosen, do nothing + +save_line: + ld c,b ; Line size from b to bc + xor a + ld b,a + push bc + ld hl,HISTSIZE + sbc hl,bc ; Buffer size - line length + jr z,savel_err ; Not enough room + jr c,savel_err ; Definitely not enough room! + push hl + ld hl,hbuf_top + push hl + sbc hl,bc ; hl -> bufftop - line size + pop de ; de -> bufftop + pop bc ; bc = buffsize - line size + lddr ; tail move + + pop bc ; Recover line size in bc + ex de,hl + inc de ; de -> buffstart + ld hl,(line) ; Move in line + ldir + dec de + ex de,hl + set 7,(hl) ; Tag line terminus + + ld hl,hbuf_top ; Terminate history after last complete line +savel1: + dec hl ; Back up to EOLN + bit 7,(hl) + jr z,savel1 ; Loop until hi-bit encountered + inc hl + ld (hl),0 + ret + +savel_err: + pop af ; Lift BC push + ret + +; Recall command history, newest -> oldest +; - recall_ptr is init to start of buffer on each CLED invocation +; - return with pointer updated to next line + +recall_back: + call check_recall ; Is there anything in buffer? + ret nc ; No + +; Transfer from recall pointer to line buffer +; - enter hl @ recall_ptr +; - return ptr -> start of next command if no OVFL + +rc_back1: + ld de,(line) ; Destination for move +rc_back1a: + ld a,(hl) + or a + jr z,recall_quit ; Buff end + ldi + bit 7,a + jr z,rc_back1a + + ld (recall_ptr),hl ; Update ptr now + ex de,hl ; Point to end of line in line buffer + ld (hl),0 ; Terminate it + dec hl + res 7,(hl) ; Fix high bit from storage + + pop af ; Lift ecmd from stack + jp edit ; Restart on this line + +recall_quit0: + pop af ; Lift subroutine call from stack +recall_quit: + exx ; Recover main regs + ret ; Back to editing + + +; Recall command history, oldest -> newest + +recall_fwrd: + call check_recall ; Anything in buffer? + ret nc ; No + call rc_fwrd1 ; Move to previous line + call rc_fwrd1 ; Don't repeat line on direction rev + jr rc_back1 ; Now same code as recall_back + +rc_fwrd1: + dec hl ; Initially, HL -> next line to recall + ld de,history ; Underflow address +rc_fwrd1a: + push hl ; Compute position relative to top + xor a + sbc hl,de + pop hl + ret z ; Quit when start of buff reached + jr c,recall_quit0 ; Underflow + dec hl ; Going backwards in buffer + bit 7,(hl) + jr z,rc_fwrd1a + inc hl ; Point to char past command terminator + ret + + +; Check to see if anything in recall buffer yet +; - Ret NC = no, main regs preserved +; - Else switch main regs to alt, ret HL @ recall buffer line + +check_recall: + ld a,(history) ; Is anything in buffer yet? + or a + ret z ; Nope + call linbeg + exx + ld hl,(recall_ptr) + scf + ret + +; --------------------------- + +; Routine: EREOL function for Z34RCP +; Author: Rob Friefeld +; Version: 1.0 +; Date: September 19, 1989 +; +; Entry: EREOL +; Function: To clear to end of line +; Comments: The setting of the ERLTCAP equate determines whether this +; command uses the TCAP information or not. If not, it uses the +; ereol string passed in macro CLR_EOL. That string should +; end with the high bit set. The setting of the ERLQUICK equate +; determines whether to simply output the TCAP string for this +; function or to interpret it as does Rick Conn's VLIB version. +; Uses RCPSUBS.LIB routines CONOUT and PRINTHL. + + +; ------------------------------------------------------------------- + + if [not erltcap] +; Erase to end of line. Return NZ. + +ereol: call print + clr_eol +; or -1 ; For VLIB compatibility + ret + + + else + if erlquick + +; --------------------------- + +; This version just prints the EREOL string: no delay, no interpretation. + +ereol: + push hl + ld hl,z3tcap+17h ; CLS string + xor a ; Skip to EREOL string +ereol1: cp (hl) ; Skip once + inc hl + jr nz,ereol1 +ereol2: cp (hl) ; Skip twice + inc hl + jr nz,ereol2 + call printhl ; Print it + pop hl + ret + +; --------------------------- + +; This is a disassembly of EREOL from VLIB + else + +ereol: + push bc + push de + push hl + ld hl,z3tcap+16h ; Point to ereol delay + ld d,(hl) + inc hl + call vidskp + call vidskp + call vidout + pop hl + pop de + pop bc + xor a + dec a + ret + +vidskp: + ld a,(hl) + inc hl + or a + ret z + cp '\' + jr nz,vidskp + inc hl + jr vidskp + +vidout: + ld a,(hl) + or a + jr z,vid2 + inc hl + cp '\' + jr nz,vid1 + ld a,(hl) +vid1: + call conout + jr vidout + +vid2: + ld a,d + or a + ret z + ld c,a + ld hl,z3env+2bh ; Processor speed + ld a,(hl) + or a + jr nz,vidl1 + ld a,4 +vidl1: + ld b,a + push bc + call vdelay + pop bc + dec c + jr nz,vidl2 + ret +vdelay: + call vdel1 + djnz vdelay + ret +vdel1: + ld c,20 +vdel1a: + ex (sp),hl + ex (sp),hl + dec c + jr nz,vdel1a + ret + + endif ;erlquick + endif ;not erltcap + +; +;===== Z 3 L I B R O U T I N E S ================================ +; + +; Disassembly of Z3LIB routines DUTDIR, SHPUSH, SHPOP +; For use with CLED RCP segment ONLY +; Does not save regs as does Z3LIB, and has less env error checking +; rdf 10/2/89 + +DUTDIR: + ld a,z3ndirs ; No NDR + or a + ret z + + ld hl,(z3ndir) + inc b +dutdir1: + ld a,(hl) + or a + jr nz,dutdir2 + dec b + xor a + ret +dutdir2: + cp b + inc hl + jr nz,dutdir3 + ld a,(hl) + cp c + jr nz,dutdir3 + inc hl + dec b + xor a + dec a + ret +dutdir3: + push bc + ld bc,11h + add hl,bc + pop bc + jr dutdir1 + + +shpop: + +; *** +;Special function for RCPCLED -- null saved command line + ld a,(era_flag) ; Erase? + or a + jr z,eflag1 ; Z = NO + xor a + ld (history),a +eflag1: +; *** + +;shpop: + call getsh ; HL -> stack, DE = size, B = entries + ret z ; No stack + ld c,e ; Entry size + ld a,(hl) + or a + ret z ; Empty + ex de,hl + add hl,de ; HL -> next entry, DE -> first entry + xor a +shpop1: + ld (de),a ; Zero entry + dec b + ret z ; Successful exit, no more entries + push bc ; Pop next entry + ld b,0 + ldir + pop bc + jr shpop1 + + +shpush: + push hl ; Save string pointer + call getsh + jr z,shpush_err1 ; No stack + +shpush3: + ld a,(hl) ; Look for free entry + or a + jr z,shpush4 + add hl,de + djnz shpush3 + jr shpush_err2 ; Stack full +shpush4: + call getsh ; Point to top of stack + push bc +shpush5: + dec b + jr z,shpush6 + add hl,de + jr shpush5 +shpush6: + pop bc + ld c,e + dec hl + ex de,hl + add hl,de ; HL -> (entry-1) + size, DE -> (entry-1) + ex de,hl +shpush7: + ld a,b + cp 1 + jr z,shpush8 + dec b + push bc + ld b,0 + lddr + pop bc + jr shpush7 +shpush8: + call getsh + pop de + ex de,hl +shpush9: + ld a,(hl) + ldi + or a + jr nz,shpush9 + +shpushx: + ret + +shpush_err1: +; ld a,1 ; No stack +; jr shpush_err + +shpush_err2: + ld a,2 ; Stack full +shpush_err: + pop hl + or a + ret + + +; Get shell stack entry +; Return HL -> top of stack +; DE = entry size +; C = unchanged +; B = # entries +; A = # entries +; Z = no entries +getsh: +getsh2: + ld hl,(z3env+1eh) ; Stack + ld a,(z3env+20h) ; # entries + ld b,a + ld de,(z3env+21h) ; Entry size in E + ld d,0 + or a + ret + +; +;===== C L O C K R E A D I N G ================================== +; + + if systime + +; Print system time from DateStamper, ZS/ZDDOS/Z3PLUS clock + +; Entry point +; Print the string with leading '0' suppression +; Format: "h.mm " or "hh.mm " + +print_time: + +; 1. Test for DateStamper/ZSDOS/Z3PLUS and read clock if present + + ld c,12 ; Return version + ld e,'D' ; DateStamper test + call bdos + ld a,l ; Version # + cp 30h ; Z3PLUS? + jr nc,time1 ; Yes + ld a,h + cp 'D' + ret nz ; No clock + +; 2. Get time + + ld hl,time2 + push hl ; Return address on stack + push de ; Clock address on stack + ld hl,dtbuf ; Point to buffer + ret ; Call clock, return to time2 +time1: ; Z3PLUS entry point + ld c,105 ; CP/M Plus get time + ld de,dtbuf+1 + push de + call bdos + pop hl + ld a,(hl) + inc hl + ld b,(hl) + dec a + or b + ret z ; No clock if date = 0001 +time2: + +; 3. Turn highlight on, if present + + ld hl,stndout + call printhl + + ld hl,dtbuf+3 ; Point to hours + +; 4. Convert military time to civilian, if selected + + if civtim + ld a,(hl) ; Hours + or a ; Midnight? + jr nz,time3 ; No + ld a,24h ; Yes, say "12" +time3: sub 13h ; Time past 12:59 pm? + jr c,time4 ; No, don't change + daa ; Decimal adjust + inc a ; Yes, xlate to 12-hour + daa + ld (hl),a ; ..and patch in. + endif ; civtim + +; 5. Display time + +time4: + xor a + call pmbcd ; Print hours as 1 or 2 digits + ld a,(tim_sep) ; Print separator between hours, minutes + call conout + inc hl ; Point to minutes + ld a,80h ; Say print leading 0 + call pmbcd ; Print minutes as 2 digits + +; 2. Turn highlight off, if present + + ld hl,stndend + call printhl + jp spac ; Space before rest of prompt + +; +;===== D A T A ==================================================== +; + +; Buffer for date/time for read/write system clock + +dtbuf: ds 6 + + endif ;systime + + +line ds 2 ; Pointer to line buffer +recall_ptr ds 2 ; History position pointer +history ds HISTSIZE,0 ; History buffer +hbuf_top: equ $-1 + +; End RCPCLED.LIB + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpcls.lib b/Source/BPBIOS/Z34RCP11/rcpcls.lib new file mode 100644 index 00000000..e6264137 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpcls.lib @@ -0,0 +1,35 @@ + page + +; Library: RCPCLS for Z34RCP +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; +; Command: CLS +; Function: To clear the CRT screen +; Comments: The setting of the CLSTCAP equate determines whether this +; command uses the TCAP information or not. If not, it uses the +; clear-screen string passed in macro CLSSTR. That string should +; end with the high bit set. + +cls: + if clstcap ; If using TCAP for clear screen string + ld a,(z3tcap) ; TCAP address from Z34CMN.LIB + cp ' '+1 ; See if blank + jr nc,cls1 ; If not, go to clear screen code + jp crlf ; If blank, just do CRLF +cls1: + ld hl,clrscr ; Address from Z34CMN.LIB + jp printhl ; Display it + + else ; Not using tcap + + call print + clsstr ; String from Z34RCP.LIB + ret + + endif ;clstcap + +; End RCPCLS.LIB + + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpcmd.lib b/Source/BPBIOS/Z34RCP11/rcpcmd.lib new file mode 100644 index 00000000..58a54e78 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpcmd.lib @@ -0,0 +1,56 @@ + page + +; Library: RCPCMD for Z34RCP +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; Description: Command Table and Custom Commands for Z34-RCP + +; This is the customization file for the ZCPR Version 3.4 resident command +; package. Use it to add your own custom commands to the RCP. + +; Give each of your custom commands a unique label and command name. Then +; add the command name(s) to the macro CUSTTBL, below, and append the code +; for the commands to the end of this file. + +; CUSTTBL - Custom commands table. +; +; Add one line to CUSTTBL for each of your custom commands. Each line +; must be of the form: +; +; COMMAND name, TRUE, wheel, label +; +; Where COMMAND and TRUE appear as-is, and "name", "wheel", and "label" +; are created by the user. "Name" is a name of four characters or less used +; to invoke the command, in upper case. "Wheel" is "true" to protect the +; command from non-wheel users, "false" otherwise. "Label" marks the +; beginning of the custom code. For example, to create a wheel- +; protected command named "DIR" which calls the code at label "directory", +; CUSTTBL appears as follows: +; +;custtbl macro +; command DIR, true, true, directory +; endm + +custtbl macro +;; command ____, true, ____, _______ ; Template for custom commands + endm + +; ------------------------------ + +; RCP command dispatch table + + db cmdsize ; Length of each command name + cmdtbl ; Dispatch table from Z34RCP.LIB + custtbl ; Optional custom commands + db 0 ; Marks end of command jump table + +; -------------------------------------------------- + +; Insert label(s) and code for custom RCP commands here: + +; + +; END RCPCMD.LIB + + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpcp.lib b/Source/BPBIOS/Z34RCP11/rcpcp.lib new file mode 100644 index 00000000..6ec69f48 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpcp.lib @@ -0,0 +1,245 @@ + page + +; Library: RCPCP for Z34RCP +; Author: Carson Wilson +; Version: 1.3 +; Date: August 11, 1989 +; Changes: Responds dynamically to QUIET flag, eliminating "noise." + +; Version: 1.2 +; Date: December 30, 1988 +; Changes: Now works properly with CP/M Plus. +; Moved SETDMA to common routines. + +; Author: Carson Wilson +; Version: 1.1 +; Date: August 4, 1988 +; Changes: Now initializes FCB1 before calling SetFStp, allowing +; stamp setting of multiple-extent files. +; +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; +; Command: CP +; Function: Copy a file from one place to another +; Syntax: If FCBSWAP false: +; CP destfile=srcfile, CP =srcfile +; If FCBSWAP true: +; CP srcfile destfile, CP srcfile +; If TESTEQ and FCBSWAP true: +; Both of the above forms work +; +; Comments: Both file specifications can include a directory specification. +; If only one file name is given, then the current directory and +; the source file name are assumed for the destination. + +; +; New ZSDOS/DosDisk BDOS standard functions: +; +GetFStp equ 102 ; Get file stamp function +SetFStp equ 103 ; Set file stamp function + +copy: + call retsave + call dirchek ; Test bad directory + + if leftright + call fcbswap ; Exchange fcb1 with fcb2 + endif + +; If new is blank, make it the same name and type as old + + ld de,fcb1+1 ; Point to destination file name + ld a,(de) ; Get first character + cp ' ' ; If not blank (no name) + jr nz,copy0 ; ..then branch to copy + ld hl,fcb2+1 ; Copy source name into destination FCB + ld b,11 ; Name and type are 11 bytes + call blkmov + +; See if destination is same as source, and abort if so + +copy0: + ld hl,fcb1 ; Set up pointers to two files + ld de,fcb2 + push hl + push de + inc hl ; Point to names of files + inc de + ld b,13 ; Compare 13 bytes (name, type, and user #) +copy1: call comp + jr nz,copy2 ; If they differ, go on with copy + ld a,(cdrv) ; ZCPR current drive + inc a ; Shift to range 1..16 + ld b,a ; ..and keep value in B + pop de ; Restore pointers to FCBs + pop hl + ld a,(de) ; Get drive of source file + ld c,a ; ..and save it in C + or a ; Is it default drive? + jr nz,copy1a ; Branch if drive made explicit + ld c,b ; Otherwise, copy default drive into C +copy1a: ld a,(hl) ; Get drive of destination file + or a ; Is it default drive? + jr nz,copy1b ; Branch if drive made explicit + ld a,b ; Otherwise, get current drive +copy1b: cp c ; Compare the two drives specified + jr nz,copy3 ; Branch if they are different + jp duperr ; Tell EH duplicate filespecs +copy2: + pop de ; Clean up the stack + pop hl + +; Make note of the user numbers of the two files + +copy3: + ld a,(fcb1+13) ; Get destination user number + ld (usrdest),a + ld a,(fcb2+13) ; Get source user number + ld (usrsrc),a + +; Set up new FCB for source file and open the source + + call define ; Define buffer addresses dynamically +srcfcb equ $+1 + ld hl,0 ; Get address to use for new source FCB + push hl + ex de,hl ; Copy file data to new FCB + ld b,12 + call blkmov + call logsrc ; Log in user number of source file + pop hl ; Initialize the source file FCB + call initfcb2 + ld c,15 ; Open source file + call bdos + inc a ; Check for error + jp z,noflerr ; File not found error handler + + if StpCall + call cpmver + jr nc,copy4 ; Don't do this if CP/M Plus +stpbuf equ $+1 + ld de,0 + call setdma ; Set DMA to date buffer + ld de,(srcfcb) + ld c,GetFStp ; Get stamp (if any) to DMA + call bdos + ld (gotstp),a ; Store result + ld de,tbuff ; Restore DMA + call setdma ; ..for search +copy4: + endif ; StpCall + +; Make sure destination file does not already exist + + call logdest ; Log into destination user area + call extest ; Test for existence of file in fcb1 + jp z,exit ; Branch if it exists and user says no + +; Create destination file + + ld de,fcb1 ; Point to destination FCB + ld c,22 ; BDOS make-file function + call bdos + inc a ; Test for error (no directory space) + jp z,fulerr ; Invoke EH if not OK + +; Copy source to destination + +copy5: call logsrc ; Log in source user area + ld b,0 ; Initialize counter + ld de,(cbuff) ; Initialize buffer pointer +copy5a: push de ; Save address and counter + push bc + call setdma ; Set DMA to cbuff+(b*128) + ld de,(srcfcb) ; Point to source file FCB + ld c,20 ; BDOS read-sequential function + call bdos + pop bc ; Get counter and address + pop de + or a ; Read Ok? + jr nz,copy5b ; Branch if end of file + ld hl,128 ; Point DE to next buffer address + add hl,de + ex de,hl + inc b ; Increment counter + ld a,b ; See if buffer full + cp cpblocks + jr nz,copy5a ; If not, go back for more +copy5b: ld a,b ; Get count of blocks loaded into buffer + or a ; Are there any? + jr z,copy6 ; Branch if not (we are done) + push bc ; Save count + call logdest ; Log into destination user number +cbuff equ $+1 ; Pointer for in-the-code modification + ld de,0 ; Point to beginning of copy buffer +copy5c: push de ; Save buffer address + call setdma ; Set dma to buffer + ld de,fcb1 ; Point to destination file FCB + ld c,21 ; Sequential write the block + call bdos + or a ; Get result + jp nz,fulerr ; Invoke EH (disk full or write error) + pop de ; Get buffer address & balance stack + pop bc ; Get count + dec b ; Buffer empty? + jr z,copy5 ; Yes. Back for refill + push bc ; No. Save count + ld hl,128 + add hl,de + ex de,hl ; DE points to next buffer address + jr copy5c ; Back for another sector to write + +; Close the destination file + +copy6: call logdest ; Log into destination user number + ld de,fcb1 ; Point to destination FCB + ld c,16 ; Close file + call bdos + inc a ; 0ffh --> 0 if error + jp z,fulerr ; Invoke EH + + if StpCall +gotstp equ $+1 + ld a,0 ; File had stamp? + dec a ; 1 --> 0 = yes + jr nz,noset ; No + ld de,(stpbuf) ; Point to buffer + call setdma ; Set DMA + call initfcb1 ; Init. for SetFStp, point to dest. + ld c,SetFStp ; Set file's stamp + call bdos ; CCP restores DMA +noset: + endif ; StpCall + + ld a,(quiet) + or a + jr nz,qcpdone + call print + db ' Don','e'+80h +qcpdone: + if cpsp and spaceon + jp spaexit ; Report space remaining on destination drive + else + jp exit + endif ;cpsp and spaceon + +; Log into user number of source file + +logsrc: +usrsrc equ $+1 ; Pointer for in-the-code modification + ld a,0 ; Get user number + jr setusrrel ; Local jump to save code + +; Log into user number of destination file + +logdest: +usrdest equ $+1 ; Pointer for in-the-code modification + ld a,0 ; Get user number +setusrrel: + jp setusr + +; End RCPCP.LIB + + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpdir.lib b/Source/BPBIOS/Z34RCP11/rcpdir.lib new file mode 100644 index 00000000..76c08bc3 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpdir.lib @@ -0,0 +1,137 @@ + page + +; Library: RCPDIR for Z34RCP +; Author: Carson Wilson +; Version: 1.0 +; Date: August 6, 1989 +; Changes: Now allows "DIR [dir:].aft" as well as "DIR [dir:]*.aft" +; to show all files of a given file extentsion, +; e.g., "d .?80" gives all .Z80 and .180 files. +; +; Version: 1.0 +; Date: June 15, 1988 +; +; Command: DIR +; Function: Display a directory of the files on disk +; Syntax: DIR [dir:afn] Displays the DIR files +; DIR [dir:afn] S Displays the SYS files +; DIR [dir:afn] A Display both DIR and SYS files +; +; If slashchk is true: +; +; DIR [dir:]/S Equivalent to DIR [dir:]*.* S +; DIR [dir:]/A Equivalent to DIR [dir:]*.* A + +dir: + call retsave ; Save return address and set stack + +; See if FCB should be made wild (all '?') + + ld hl,fcb1+1 ; Point to file name in FCP + ld a,(hl) ; Get first character of filename + + if slashchk ; Allow "DIR /S" and "DIR /A" formats + cp '/' ; If name does not start with '/' + jr nz,dir01 ; ..branch and process normally + inc hl ; Point to second character + ld a,(hl) ; Get option character after slash + ld (fcb2+1),a ; ..and put it into second FCB + dec hl ; Back to first character + ld a,' ' ; Simulate empty FCB + endif ;slashchk + +dir01: + cp ' ' ; See if no file spec given + jr nz,dir02 ; Spec given + + ld b,8 ; Wildcard name + ld a,(fcb1+9) + cp ' ' ; Wildcard type? + jr nz,dir01a ; No + ld b,11 ; Yes. Fill name and type. +dir01a: ld a,'?' ; Get ready to fill with '?' + call fillp ; ..carry out fill +dir02: + if nosys ; Suppress-SYS-file-if-no-wheel option + call getwhl ; Get wheel status + jr z,dirnly ; If wheel off, ignore options + endif + + ld a,(fcb2+1) ; Get first char of 2nd file name + ld b,1 ; Set for both dir and sys files + cp allflag ; SYS and DIR flag specifier? + jr z,dirpr ; Got system specifier + dec b ; B=0 for sys files only + cp sysflag ; SYS only? + jr z,dirpr + +dirnly: ld b,80h ; Must be dir-only selection + +; DIRECTORY PRINT ROUTINE +; On entry, B reg is set as follows: +; 0 for only system files, 80h for only dir files, 1 for both +; +dirpr: + ld a,b ; Get systst flag + call getdir ; Load and sort directory + jp z,prfnf ; Print no file message + if wide + ld e,5 + else + ld e,4 ; Count down to 0 + endif ; wide +; +; ENTRY PRINT LOOP +; On entry, HL pts to files selected (terminated by 0) +; and E is entry counter +; +dir3: + ld a,(hl) ; Check for done + or a + if dirsp and spaceon + jp z,spaexit ; Show space when done + else + jp z,exit ; Exit if done + endif ; Dirsp and spaceon + ld a,e ; Get entry counter + or a ; Output CRLF if 4 or 5 entries printed in line + jr nz,dir3a ; Continue + call crlf ; New line + if wide + ld e,5 + else + ld e,4 ; Reset entry count + endif ; wide + + ld a,e ; Get entry count +dir3a: + if wide + cp 5 + else + cp 4 ; First entry? + endif ; wide + + jr z,dir4 + call print +; + if wide +; + db ' ' ; 2 spaces + db ' '+80h ; Then 1 more space +; + else +; + db ' ' ; Space + db fence+80h ; Then fence char +; + endif ; Wide +; +dir4: + call prfn ; Print file name + call break ; Check for abort + dec e ; Decrement entry counter + jr dir3 + +; End RCPDIR.LIB + + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpecho.lib b/Source/BPBIOS/Z34RCP11/rcpecho.lib new file mode 100644 index 00000000..ffe56183 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpecho.lib @@ -0,0 +1,162 @@ + page + +; Library: RCPECHO for Z34RCP +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; +; Command: ECHO +; Function: Echo text to console or printer + +echo: + xor a ; Lower case/printer off flag setting + ld (crtfl),a ; Printer off by default + + if upcase ; If upper case default + dec a + endif ;upcase + + ld (casefl),a ; Store flag in code below + ld hl,tbuff+1 ; Point to first character + call getchar ; Get first character (should be blank) + ; If none, exit from routine + +; Loop to echo chars + +echo2: call getchar + + if echolst + cp ff ; Form feed? + jr z,echo3 + endif ;echolst + + cp '^' + jr nz,echo2a ; Not control character prefix + call getchar ; Get next character + and 1fh ; Convert to control character + jr echo2e ; Echo it +echo2a: + cp cmdchar ; Case shift prefix? + jr nz,echo2e ; No, normal echo + call getchar ; Get next character + + if echolst + cp prtchar ; Turn printer on? + jr z,echo2b ; Store non-zero in crt flag + cp crtchar ; Turn printer off? + jr nz,echo2c ; No, test for shift characters + xor a ; Yes, clear crt flag +echo2b: ld (crtfl),a + jr echo2 ; On to next character +echo2c: + endif ; echolst + + cp ucasechar ; Up-shift character? + jr z,echo2d ; Store non-zero value in case flag + cp lcasechar ; Lower-case character? + jr nz,echo2e ; No, echo the character as is + xor a ; Else, clear case flag +echo2d: ld (casefl),a + jr echo2 ; On to next character +echo2e: + call echout ; Send char + jr echo2 + +; Form feed - send new line followed by form feed if printer output + + if echolst +echo3: + ld a,(crtfl) ; Check for printer output + or a ; Non-zero? + jr z,echoff ; No, send form feed normally + call echonl ; Send new line + ld a,ff ; Send form feed + jr echout + +; Send form feed char to console + +echoff: + ld a,ff ; Get char + jr echo2e + endif ;echolst + +; End of print loop - check for printer termination + +echo4: + if not echolst + ret + + else + ld a,(crtfl) ; Get list mode flag + or a + ret z ; Done if no printer output + +; Output a new line + +echonl: + ld a,cr ; Output new line on printer + call echout + ld a,lf ; Fall thru to echout + endif ; not echolst + +; Output char to printer or console + +echout: + ld c,a ; Char in c + cp 'A' ; If less than 'A' + jr c,echouta ; Leave as is + cp 'Z'+1 ; If greater than 'Z' + jr nc,echouta ; Leave as is + add 20h ; Else convert to lower case +echouta: + ld d,a ; Save lower case version in d +casefl equ $+1 ; Pointer for in-the-code modification + ld a,0 + or a ; Upper case? + jr nz,echoutb ; If upper case selected, go on as is + ld c,d ; Else substitute lower case version +echoutb: + + push hl ; Save hl + push bc ; Save bc + ld de,0ch-3 ; Offset for BIOS console output + + if echolst + +crtfl equ $+1 + ld a,0 + or a ; Printer? + jr z,echout1 ; No + inc de ; Offset for BIOS printer output + inc de + inc de + endif ;echolst + +; Output char in C with BIOS offset in DE + +echout1: + call biout ; Bios output + pop bc ; Restore bc,hl + pop hl + ret + +; Get a character from the command tail buffer + +getchar: + ld a,(hl) ; Get character + inc hl ; Point to next one + or a ; Check for end of string + ret nz ; If not end, return + pop hl ; Else, clean up stack + jr echo4 ; And exit from routine + +; Output char in C to BIOS with offset in DE + +biout: + ld hl,(wboot+1) ; Get address of warm boot + add hl,de ; Pt to routine + jp (hl) ; Jump to it + +; End RCPECHO.LIB + + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpera.lib b/Source/BPBIOS/Z34RCP11/rcpera.lib new file mode 100644 index 00000000..776becb8 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpera.lib @@ -0,0 +1,76 @@ + page + +; Library: RCPERA for Z34RCP +; Author: Carson Wilson +; Version: 1.2 +; Date: Sept. 15, 1989 +; Changes: Chains to error handler with flag set to invoke transient ERA +; if a read only file is encountered. Propose error code +; 17 decimal for "file read only" error. +; +; Version: 1.1 +; Date: August 12, 1989 +; Changes: Now responds dynamically to QUIET flag, eliminating "noise". +; NOTE: if QUIET is active, using the inspect option or trying +; to erase R/O files will give meaningless messages. Use +; the PROT command to set R/O files to R/W first in order to +; erase them quietly. +; +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; +; Command: ERA +; Function: Erase files +; Forms: +; ERA Erase Specified files and print their names +; ERA o Erase Specified files and print their names, but ask +; for verification before Erase is done + +era: + call retsave + ld a,(fcb2+1) ; Get eraflg if it's there + ld (eraflg),a ; Save it as a flag + ld a,1 ; Dir files only + call getdir ; Load directory of files + jp z,prfnf ; Abort if no files +; +; Main erase loop +; +era1: call break ; See if user wants to stop + call qplug ; Turn of output if quiet + push hl ; Save ptr to file + call prfn ; Print its name + ld (nxtfile),hl ; Save ptr to next file + pop hl ; Get ptr to this file + call unplug ; Turn output on + call rotest ; Test file pted to by hl for r/o + ld a,17 ; Proposed file R/O error code + ld b,00010000b ; EH flag to invoke transient + jp nz,errex1 ; Chain to transient if R/O +eraflg equ $+1 ; Address of flag + ld a,0 ; 2nd byte is flag + cp ' ' ; Is it an inspect option? + jr z,era2 ; Skip prompt if it is not + call eraq ; Erase? + jr nz,era3 ; Skip if not +era2: ld de,fcb1+1 ; Copy into fcb1 + ld b,11 ; 11 bytes + call blkmov + call initfcb1 ; Init fcb + ld c,19 ; Delete file + call bdos +era3: ld hl,(nxtfile) ; Hl pts to next file + ld a,(hl) ; Get char + or a ; Done? + if erasp and spaceon + jp z,spaexit + else + jp z,exit + endif ; Erasp and spaceon + call crlf ; New line + jr era1 + +; End RCPERA.LIB + + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcph.lib b/Source/BPBIOS/Z34RCP11/rcph.lib new file mode 100644 index 00000000..34cae928 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcph.lib @@ -0,0 +1,119 @@ + page + +; Library: RCPH for Z34RCP +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; +; Command: HELP +; Function: This command displays a list of all resident commands that +; are supported, including those in the CPR (command processor), +; RCP, and FCP. +; +; Syntax: H + +clist: + +; Print the FCP-resident command names + + if listfcp + call print ; Print header for FCP + db lf + db 'FC','P'+80h + ld hl,(fcp) ; Get FCP address dynamically from ENV + ld a,h ; See if still there + or l + jr z,nofcp ; FCP has been removed + ld bc,5 ; Calculate address of FCP command table + add hl,bc + call cmdlist ; Display list of commands +nofcp: + endif ;listfcp + +; Print the CPR-resident command names + + if listcpr + call print ; Print "CPR" + db cr,lf ; Need CR if no FCP + db 'CP','R'+80h + ld hl,(ccp) ; Get CCP address from ENV + ld bc,offcmd ; Point to command table in CPR + add hl,bc + call cmdlist ; Display the list of commands + endif ;listcpr + +; Print the RCP-resident command names + + call crlf ; Skip a line + ld hl,rcpname ; Print RCP name + call printhl + ld hl,RCPbegin+5 ; Point to RCP command table + ; Fall through to CMDLIST + +;---------------------------------------- + +; Subroutine to display list of commands in a command table (code above +; falls through to this routine -- do not move it). The commands are +; displayed 5 per line with 8 character spaces allowed for each command +; (subject to equates below). + +cmdlist: + call crlf ; Start with new line + ld e,(hl) ; Get size of each command name into DE + ld d,0 + inc hl ; Point to name of first command + ld c,cmdsline ; Set names-per-line value +cmdlist1: + ld a,(hl) ; Get first character of the command name + or a ; See if it is null + jr nz,cmdlist1a ; If not, continue + ld a,cmdsline ; See if we are already on a new line + cp c + call nz,crlf ; If not, skip a line + ret + +cmdlist1a: + if noshow ; Option to suppress wheel-limited cmds + rla ; Shift high bit of name into carry bit + jr nc,cmdlist2 ; If not restricted, go on + call getwhl ; Otherwise, check wheel byte + or a + jr nz,cmdlist2 ; If wheel set, continue as usual + add hl,de ; Otherwise skip this command + jr cmdlist5 + endif + +; Print leading spaces between names + +cmdlist2: + ld a,cmdspace ; Spacing between command names + sub e ; Less length of each command name + ld b,a +cmdlist3: + call spac + djnz cmdlist3 + +; Print name of command + + ld b,e ; Length of each name into B +cmdlist4: + ld a,(hl) ; Get command name character + call conout + inc hl ; Point to next + djnz cmdlist4 + + dec c ; Decrement count of names on this line + jr nz,cmdlist5 ; Branch if room for more names + call crlf ; Otherwise, end this line and + ld c,cmdsline ; ..reset count for another line of commands + +; Skip to next command name + +cmdlist5: + inc hl ; Skip jump vector + inc hl + jr cmdlist1 ; Back to process next name + +; End RCPH.LIB + + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpid.lib b/Source/BPBIOS/Z34RCP11/rcpid.lib new file mode 100644 index 00000000..dfbacaf2 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpid.lib @@ -0,0 +1,64 @@ + page + +; Library: RCPID for Z34RCP +; Author: Carson Wilson +; Version: 1.0 +; Date: September 29, 1989 +; Changes: ID appended with 'C' if CLED is present +; +; Author: Carson Wilson +; Version: 1.0 +; Date: September 14, 1988 +; +; Function: Build the name of the RCP and append it at the end of the +; file at label RCPID. This serves two purposes: first it +; allows the 'H' command to describe which RCP is active, +; second, the string identifies the actual end of the RCP +; in memory. +; +; ------------------------------------------------------------------------ + +; Macros to build RCP ID for Help command + +; Under SLR and compatible assemblers, the RCP name reflects the +; RCP size, e.g., "RCP-21F" means the RCP occupies 21 records. + + if SLR +rcpid macro +rcplen defl RCPend - RCPbegin +fulrecs equ rcplen / 128 ; Full records +lastr equ [rcplen mod 128 + 127]/128 ; Last record, if any +ttlrecs equ fulrecs + lastr + + db 'RCP-' + db ttlrecs / 10 + '0' ; Tens of records + db ttlrecs mod 10 + '0' ; Ones + db rcptype ; 'F', 'H', etc. from RCP.LIB + if cledon + db 'C' ; Distinguish CLED versions + endif + db 0 ; ID string terminator + endm + + else ; Non-SLR +rcpid macro + db 'RCP-' + db rcptype + db 0 + endm + endif ; SLR + +; ----------------------------------------------------------------------- + +; Name of RCP + +; This block allows the 'H' command and/or the SHOW utility to display a name +; and version number for this RCP as well as the commands that are supported. +; It also generates a unique string marking the end of the RCP module. + +rcpname: + rcpid + +; End of RCPID.LIB + + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpiom.lib b/Source/BPBIOS/Z34RCP11/rcpiom.lib new file mode 100644 index 00000000..d85e553d --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpiom.lib @@ -0,0 +1,296 @@ + page + +; Library: RCPIOM for Z34RCP +; Author: Carson Wilson +; Version: 1.1 +; Date: August 12, 1989 +; Changes: POKE and PORT now respond dynamically to QUIET flag, +; eliminating "noise." + +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; +; Commands: PEEK, POKE, and PORT + +; ------------------------------------------------------------------ +; +; Command: PEEK +; Function: Display memory contents +; +; Form: +; PEEK startadr 128 bytes displayed +; PEEK startadr endadr Range of bytes displayed + + if peekon +peek: + call retsave + ld hl,tbuff+1 ; Find first number +nxtpeek equ $+1 ; Pointer for in-the-code modification + ld de,100h ; Default peek address if none + call sksp ; Skip to first token (if any) + call nz,hexnum ; Get start address if any + push de ; Save starting address + ld bc,peeklen ; Compute default ending address + ex de,hl + add hl,bc + + if peekchk ; Check for overflow + jr nc,peek0 ; If no overflow past FFFF, go on + ld hl,0ffffh ; Else use FFFF as ending address +peek0: + endif ;peekchk + + ex de,hl ; End address in DE + call sksp ; Skip to next token (if any) + call nz,hexnum ; Get 2nd number in DE (else default) +peek1: pop hl ; HL is start address, DE is end address + + if peekhdr + push hl ; Save starting address again + ld b,8 ; Output leading spaces +peek0a: call spac + djnz peek0a + ld b,16 ; Display 16 column headers +peek0b: ld a,l ; Get low byte of address + and 0fh ; Display low hex digit + call pashc + inc hl + djnz peek0b +; +; Display header for ASCII area +; + call print + db ' ',' '+80H ; Space over to ASCII area + pop hl ; Get address + push hl + ld b,16 ; Display 16 chars. ASCII header +peek0b1:ld a,l ; Get byte + and 0fh ; Mask + call pah ; Print ASCII char. + inc hl ; Next byte + djnz peek0b1 + + if peekbdr + call crlf + ld b,8 +peek0c: call spac ; Print leading spaces + djnz peek0c + ld b,16 +peek0d: call print + db ' -','-'+80h + djnz peek0d + +; Print border at ASCII area + + call print + db ' ',' '+80h ; Space to ASCII border + ld b,16 ; 16 dashes +peek0e: call print + db '-'+80h + djnz peek0e + endif ;peekbdr + + pop hl ; Restore starting address + endif ;peekhdr + + ld c,0ffh ; Use C as continue flag + call peek2 ; Do peek + ld (nxtpeek),hl ; Set continued peek address + jp exit +peek2: + ld a,c ; Check continuation flag + or a + ret z + +; Print line header + +peek2a: call crlf ; New line + ld a,h ; Print address + call pashc + ld a,l + call pahc + call dash ; Print leader + ld b,16 ; 16 bytes to display + push hl ; Save start address + +; Print hex values for 16 bytes + +peek3: ld a,(hl) ; Get next byte + call pashc ; Print with leading space + +; Check for last address. If C is already 0, leave it that way. +; Otherwise check for end address and if so set C to zero. + + ld a,c ; See if continue flag already cleared + or a + jr z,peek3a ; If so, skip test + ld a,h + sub a,d ; See if h = d + ld c,a + ld a,l + sub a,e ; See if l = e + or c ; Combine two tests + ld c,a +peek3a: inc hl ; Pt to next + djnz peek3 + +; Print ASCII equivalents for 16 bytes + + pop hl ; Pt to first address again + ld b,16 ; 16 bytes + call print ; Space and fence + db ' ' + db fence+80h + push bc ; Save flag in c +peek4: ld a,(hl) ; Get next byte + ld c,'.' ; Assume dot + and 7fh ; Mask it + cp ' ' ; Dot if less than space + jr c,peek5 + cp 7fh ; Don't print del + jr z,peek5 + ld c,a ; Char in c +peek5: ld a,c ; Get char + call conout ; Send it + inc hl ; Pt to next + djnz peek4 + call print ; Closing fence + db fence+80h + pop bc ; Get flag in c back + call break ; Allow abort + jr peek2 + endif ; Peekon + +; PAHC - Print A as 2 hex chars +; PASHC - With leading space + + if peekon or pokeon or porton +pashc: + push af ; Save A + call spac + pop af +pahc: push bc ; Save bc + ld c,a ; Byte in c + rrca ; Exchange nybbles + rrca + rrca + rrca + call pah ; Print hex char + ld a,c ; Get low + pop bc ; Restore bc and fall thru to pah +pah: and 0fh ; Mask + add '0' ; Convert to ascii + cp '9'+1 ; Letter? + jr c,pah1 + add 7 ; Adjust to letter +pah1: jp conout + endif ; Peekon or pokeon or porton + +; -------------------------------------------------------------------- +; +; Command: POKE +; Function: Place Values into Memory +; +; Form: +; POKE startadr val1 val2 ... +; + if pokeon +poke: + call retsave + ld hl,tbuff+1 ; Pt to first char + call sksp ; Skip to non-blank + jp z,numerr ; Numerical error + call hexnum ; Convert to number + call qplug ; Shut off output if quiet + call print + db ' Pok','e'+80h + call adrat ; Print at message (quiet sensitive) + call unplug ; Turn on output + +; Loop for storing hex values sequentially via POKE + +poke1: push de ; Save address + call sksp ; Skip to non-blank + jp z,exit ; Done + cp '"' ; Quoted text? + jr z,poke2 + call hexnum ; Get number + ld a,e ; Get low + pop de ; Get address + ld (de),a ; Store number + inc de ; Pt to next + jr poke1 + +; Store ASCII chars. + +poke2: pop de ; Get next address + inc hl ; Pt to next char +poke3: ld a,(hl) ; Get next char + or a ; Done? + jp z,exit + ld (de),a ; Put char + inc hl ; Pt to next + inc de + jr poke3 + + endif ; Pokeon + +; ------------------------------------------------------------------- +; +; Command: PORT +; Function: Display or Set I/O Port Data +; +; Forms: +; PORT addr - Read port and display value +; PORT addr value - Output value to port +; + if porton +port: + call retsave + ld hl,tbuff+1 ; Find first number + call sksp ; Skip to first command-line token + jp z,numerr ; Numerical error + call hexnum ; Get start address into de + push hl ; Save pointer to command tail + ld hl,portadl ; Modify code + ld (hl),e ; Move specified port addr into place + dec hl ; Point to opcode position + ld (hl),0dbh ; Poke 'in' opcode + dec hl + ld (hl),d ; Save MSB for port address + inc hl + ex (sp),hl ; Get tail pointer back while saving this one + call qplug ; Shut off output if quiet + call print ; Print header + db ' Por','t'+80h + ld a,e + call pashc ; Print port address + call sksp ; Skip to possible second value + jr z,portin ; Proceed with port input + call hexnum ; Get 2nd number in de + ex (sp),hl ; Get pointer to opcode back + ld (hl),0d3h ; Poke 'out' opcode + call print + db ': OU','T'+80h + ld a,e ; Get value to output + jr paddr +portin: + call print + db ': I','N'+80h + xor a ; Make sure high port address = 0 (for hd64180) +portadh equ $+1 +paddr: ld b,0 ; ..for both in and out instructions +opcode: + db 0 ; Opcode for in or out inserted by code above +portadl: + db 0 ; Port address inserted by code above + call pashc + call unplug ; Turn on output + pop hl ; Clean up stack + jp exit + + endif ; Porton + +; End RCPIOM.Z80 + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcplt.lib b/Source/BPBIOS/Z34RCP11/rcplt.lib new file mode 100644 index 00000000..98d6b0ba --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcplt.lib @@ -0,0 +1,256 @@ + page + +; Library: RCPLT for Z34RCP +; Author: Carson Wilson +; Version: 1.1 +; Date: August 26, 1989 +; Changes: Some WordStar characters caused garbage to appear on the +; screen. Now filters control characters other than CR, +; TAB, and LF. Thanks to Gene Pizzetta for this suggestion. +; +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; +; Commands: LIST and TYPE + +; ---------------------------------------------------------------- +; +; Command: LIST +; Function: Print out specified file on the LST: Device +; Forms: +; LIST Do form feed +; LIST Print file(s) (NO Paging) +; Notes: +; The flags which apply to TYPE do not take effect with LIST +; The tab expansion code is required for LST: output. + + if liston +list: + ld a,(fcb1+1) ; Get filename or ' ' from command + ld (prflg),a ; List flag (A can't be 0) + cp ' ' ; Null command? + jp z,lstff ; Yes, do form feed and return + jr type0 ; No, send file to LST: + endif ;liston + +; -------------------------------------------------------------------- +; +; Command: TYPE +; Function: Print out specified file on the CON: Device +; Forms: +; TYPE Print file +; TYPE P Print file with paging flag +; Notes: +; The flag PGDFLG defines the letter which toggles the paging +; facility (P in the forms section above) +; The flag PGDFLT determines if TYPE is to page by default +; (PGDFLT=TRUE if TYPE pages by default); combined with +; PGDFLG, the following events occur -- +; If PGDFLT = TRUE, PGDFLG turns OFF paging +; If PGDFLT = FALSE, PGDFLG turns ON paging + +type: + if liston + xor a ; Turn off printer flag + ld (prflg),a ; Set flag + endif ; Liston + +; Entry point for list function (LIST) + +type0: call retsave ; Save return address + ld a,(fcb2+1) ; Get page flag from command + ld (pgflg),a ; Store it + ld a,1 ; Select dir files + call getdir ; Allow ambiguous files (HL points to buffer) + jp z,noflerr ; EH no files error + ld a,(lins) ; Set line count + ld (pagcnt),a + jr typex1 + +; Entry point for successive files + +typex0: pop hl ; Balance stack for skip + pop hl ; ..to next file command (^X) +typex: ld hl,(nxtfile) ; Get ptr to next file + ld a,(hl) ; Any files? + or a + jp z,exit + + if liston + ld a,(prflg) ; Check for lst: output + or a ; 0=type + jr z,typex1 + ld a,cr ; Bol on printer + call lcout + call lstff ; Form feed the printer +; fall thru + endif ; Liston + + ; Entry point for 1st file +typex1: ld de,fcb1+1 ; Copy into fcb1 + ld b,11 ; 11 bytes + call blkmov + ld (nxtfile),hl ; Set ptr to next file + call initfcb1 ; Init fcb1 + ld c,15 ; Open file + call bdos + inc a ; Set error flag + jp z,noflerr ; EH no files error + ld a,cr ; New line + call lcout + ld a,lf + call lcout + ld bc,080h ; Set char position and tab count + ; (b=0=tab, c=080h=char position) + +; Main loop for loading next block + +type2: ld a,c ; Get char count + cp 80h + jr c,type3 + call break + push bc ; Read next block + ld de,fcb1 ; Pt to fcb + ld c,20 ; Read record + call bdos + or a ; Set flags + pop bc + jr nz,typex ; End of file? + ld c,0 ; Set char count + ld hl,tbuff ; Pt to first char + +; Main loop for printing chars in tbuff + +type3: ld a,(hl) ; Get next char + and 7fh ; Mask out msb + cp 1ah ; End of file (^z)? + jr z,typex ; Next file if so + +; Output char to CON: or LST: device with tabulation + + cp cr ; Reset tab count? + jr z,type4 + cp lf ; Reset tab count? + jr z,type4 + cp tab ; Tab? + jr z,type5 + cp ' ' ; Skip other ctls. + jr c,type6 + +; Output char and increment char count + + call lcout ; Output char + inc b ; Increment tab count + jr type6 + +; Output or and reset tab count + +type4: call lcout ; Output or + ld b,0 ; Reset tab counter + jr type6 + +; Tabulate + +type5: ld a,' ' ; + call lcout + inc b ; Incr pos count + ld a,b + and 7 + jr nz,type5 + +; Continue processing + +type6: + inc c ; Increment char count + inc hl ; Pt to next char + jr type2 + +; Send a formfeed to LST:. Assumes PRFLG <> 0. + +lstff: + ld a,ff ; formfeed +; fall thru + +; Send output to LST: or CON:, as per the flag +; Return with Z if abort + +lcout: push hl ; Save regs + push bc + ld e,a ; Char in e + ld c,2 ; Output to con: + + if liston +prflg equ $+1 ; Pointer for in-the-code modification + ld a,0 ; 2nd byte is the print flag + or a ; 0=type + jr z,lc1 + ld c,5 ; Output to lst: + endif ; Liston + +lc1: push de ; Save char + call bdos ; Output char in e + pop de ; Get char + ld a,e + cp lf ; New line? + jr nz,lc2 ; No, return + call break ; Check for abort + jp z,typex0 ; Skip if ^X + + if liston + ld a,(prflg) ; Output to lst:? + or a ; Nz = yes + jr nz,lc2 + endif ; Liston + +; New line, so check for paging + + ld hl,pagcnt + dec (hl) + jr nz,lc2 ; Jump if not end of page + ld a,(lins) + ld (hl),a ; Reset counter +pgflg equ $+1 ; Pointer to in-the-code buffer + ld a,0 ; 2nd byte is the paging flag + cp pgdflg ; Page default override option wanted? + + if pgdflt ; If paging is default + jr z,lc2 ; Pgdflg means no paging + else + jr nz,lc2 ; Pgdflg means page + endif ; Pgdflt + + push hl ; Save hl + call print + db cr,lf,' Typing',' '+80h + ld hl,fcb1+1 ; Print file name + call prfn + call dash ; Print dash + call conin ; Get input + pop hl ; Restore hl + call break1 ; Set Z flag or abort + push af ; Save results + + if typecls and clson + call cls ; Clear between screens + else + call crlf + endif + + pop af ; Get results + jp z,typex0 ; Control-X, so skip to next file + cp ctrlz ; If Control-Z, + jr nz,lc2 + ld a,pgdflg ; Switch to non-default + ld (pgflg),a ; ..paging mode +lc2: pop bc ; Restore regs + pop hl + ret + +; Storage for line counter + +pagcnt: + ds 1 + +; End RCPLT.LIB + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpprot.lib b/Source/BPBIOS/Z34RCP11/rcpprot.lib new file mode 100644 index 00000000..4e8a5479 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpprot.lib @@ -0,0 +1,130 @@ + page + +; Library: RCPPROT for Z34RCP +; Author: Carson Wilson +; Version: 1.1 +; Date: August 12, 1989 +; Changes: Now responds dynamically to QUIET flag, eliminating "noise." + +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; +; Command: PROT +; Function: To set the attributes of a file (R/O, SYS, and ARC) +; Form: PROT afn RSAI +; Comments: If R, S, or A are omitted, the file is made R/W, DIR, or +; modified, respectively. R, S, A, and I may be in any order. +; If I is present, Inspection is enabled. + +att: + call retsave + call dirchek ; Test bad directory + xor a ; Set no inspect + ld (inspect),a + ld hl,0 ; Set r/o and sys attributes off + ld c,0 ; Set Arc attribute off + ld de,fcb2+1 ; Pt to attributes + ld b,4 ; 4 chars max +att1: + ld a,(de) ; Get char + inc de ; Pt to next + cp 'I' ; Inspect? + jr z,atti + cp 'R' ; Set r/o? + jr z,attr + cp 'S' ; Set sys? + jr z,atts + cp 'A' + jr z,atta +att2: + djnz att1 + jr att3 +atti: + ld (inspect),a ; Set flag + jr att2 +attr: + ld h,a ; Save R/O flag + jr att2 +atts: + ld l,a ; Save SYS flag + jr att2 +atta: + ld c,a ; Save ARC + jr att2 +att3: + ld (fatt2),hl ; Save file attributes + ld a,c + ld (fatt1),a ; Save Arc attribute + ld a,1 ; Select dir and sys files + call getdir ; Load directory + jp z,noflerr ; Tell error handler no file + jr att5 +att4: + ld hl,(nxtfile) ; Pt to next file + ld a,(hl) ; End of list? + or a + jp z,exit + call crlf ; New line +att5: call break ; Check for possible abort + call qplug ; Turn off output if quiet + push hl ; Save ptr to current file + call prfn ; Print its name + ld (nxtfile),hl ; Save ptr to next file + call print + db ' Set to R','/'+80h + ld hl,(fatt2) ; Get attributes + ld c,'W' ; Assume r/w + ld a,h ; Get r/o bit + or a + jr z,att6 + ld c,'O' ; Set r/o +att6: ld a,c ; Get char + call conout + ld a,l ; Get sys flag + or a ; Set flag + jr z,att7 + call print + db ', SY','S'+80h +att7: ld a,(fatt1) + or a + jr z,att7a + call print + db ', AR','C'+80h +att7a: call unplug ; Turn output on +inspect equ $+1 ; Ptr for in-the-code modification + ld a,0 ; Get inspect flag + or a ; Z=no + pop hl ; Get ptr to current file + jr z,att8 + call eraq1 ; Ask for y/n + jr nz,att4 ; Advance to next file if not y +att8: ld de,fcb1+1 ; Copy into fcb1 + ld b,11 ; 11 bytes + call blkmov + ex de,hl + dec hl ; Pt to archive byte +fatt1 equ $+1 + ld a,0 + call attset + dec hl ; Pt to sys byte +fatt2 equ $+1 ; Ptr for in-the-code modification + ld de,0 ; Get attributes + ld a,e ; Get sys flag + call attset ; Set attribute correctly + dec hl ; Pt to r/o byte + ld a,d ; Get r/o flag + call attset + ld de,fcb1 ; Pt to fcb + ld c,30 ; Set attributes + call bdos + jp att4 +attset: + res 7,(hl) ; Clear attribute + or a + ret z ; 0=clear attribute + set 7,(hl) ; Set attribute + ret + +; End RCPPROT.LIB + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpquiet.lib b/Source/BPBIOS/Z34RCP11/rcpquiet.lib new file mode 100644 index 00000000..9269d241 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpquiet.lib @@ -0,0 +1,49 @@ + page + +; Library: RCPQUIET for Z34RCP +; Author: Carson Wilson +; Version: 1.1 +; Date: August 6, 1989 +; Changes: Now "Q R[eset]" and "Q S[et]" just reset and set quiet status +; without showing it, and Q alone (or with any other character, +; e.g., "Q ?") always just shows status. +; Forms: +; Q - Display quiet flag (always) +; Q s - Set quiet flag ON +; Q r - Set quiet flag OFF +; +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; +; Command: Q +; Function: Set the Quiet flag on or off +; Comments: If QQUIET equate is true, then RCP does not report +; quiet status with the Q command. +; Forms: +; Q - Display quiet flag (if QQUIET false) +; Q s - Set quiet flag ON +; Q r - Set quiet flag OFF + +quset: + ld a,(fcb1+1) ; Get first char + ld b,1 ; Prepare to turn on + cp 'S' ; S-et quiet (ON) + jr z,quset1 + ld b,0 ; Prepare to turn off + cp 'R' ; R-eset quiet (OFF) + jr nz,qmsg ; Neither S nor R, so display +quset1: + ld a,b + ld (quiet),a + ret + +qmsg: +; Print Quiet Flag Message + + ld a,(quiet) + jp tella ; Say " On" or " Off" and return + +; End RCPQUIET.LIB + + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpr.lib b/Source/BPBIOS/Z34RCP11/rcpr.lib new file mode 100644 index 00000000..e29a4f37 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpr.lib @@ -0,0 +1,108 @@ + page + +; Library: RCPR for Z34RCP +; Author: Carson Wilson +; Version: 1.5 +; Date: August 30, 1989 +; Changes: Minor bug fix as suggested by Howard Goldstein. +; +; Version: 1.4 +; Date: August 26, 1989 +; Changes: Now performs reset 37 for ALL drives under CP/M 2.2 or ZRDOS +; to ensure that fast fixed disks are reset under ZRDOS, then +; exits with reset 13 to compensate for bugs in ZRDOS/CP/M 2.2 +; reset 37. Still resets individual drives selectively under +; CP/M Plus and ZS/ZDDOS. + +; Version: 1.3 +; Date: August 11, 1989 +; Changes: Now responds dynamically to QUIET flag, eliminating "noise." +; Now performs reset 37 in ALL cases, reset 13 as well if not +; CP/M Plus or ZSDOS (per suggestion by Howard Goldstein). + +; Version: 1.2 +; Date: December 30, 1988 +; Changes: Now resets single drives under Z3PLUS. + +; Version: 1.1 +; Date: September 11, 1988 +; Changes: Fixed bug which failed to detect ZRDOS. ZRDOS' function +; 37, like CP/M's, is not reliable. + +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; +; Command: RESET +; Function: Reset the disk system +; Forms: R [d: or dir:] - ZSDOS or CP/M Plus +; R - CP/M 2.2 or ZRDOS +; Comments: ZRDOS does not require a disk system reset when disks are +; changed, but directory programs will not show the correct +; size if this is not done. It is also good practice. Since +; no warm boot is performed, the disk in drive A need not have +; the operating system on it. +; +; Under ZSDOS or CP/M Plus, individual drives may be reset, and +; if RESETSP is true, space remaining is also given. If +; the fast hard disk reset capability is enabled under ZSDOS, +; any "fast" fixed disks are also reset at this time. +; +; NOTE: It is necessary to reset a legal directory or DU when there are +; protected directories. + +reset: ld a,(quiet) + or a ; Skip message if quiet + jr nz,QReset + call print ; Report action + db ' Rese','t'+80h +QReset: call cpmver ; CP/M Plus? + jr nc,reset0 ; Yes + ld c,48 + call bdos ; ZRDOS or CP/M? + ld a,h + or a + jr nz,reset0 ; No, assume bug-free F37 + ld de,0ffffh ; Yes, reset ALL drives, both ways + ld c,37 + call bdos + ld c,13 + jp bdos ; Yes, do regular reset + +; Reset individual drive(s) + +reset0: call dirchek ; Abort with error if illegal drivespec + ld a,(fcb1) ; Use default drive? + or a + jr nz,reset1 ; No, use drive from FCB1 + ld a,(cdrv) ; Yes, get ZCPR 3.3 current drive byte + inc a ; Shift range to 1..16 +reset1: ld hl,1 ; Map drive "A:" +reset2: dec a ; Done yet? + jr z,reset3 ; Yes + add hl,hl ; No, shift vector to next drive + jr reset2 + +; Check for fixed disks + +reset3: push hl ; Save current or specified vector + ld c,39 + call bdos ; ZSDOS return fixed disks in HL + pop de ; Restore vector + ld a,d + or h ; Add any fixed disks + ld d,a + ld a,e + or l + ld e,a + ld c,37 ; Reset individual drive(s) + + if spaceon and resetsp + call bdos + jp crspace ; Show space remaining (QUIET sensitive) + else + jp bdos ; Do reset and return + endif + +; End RCPR.LIB + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpreg.lib b/Source/BPBIOS/Z34RCP11/rcpreg.lib new file mode 100644 index 00000000..d73eee05 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpreg.lib @@ -0,0 +1,165 @@ + page + +; Library: RCPREG for Z34RCP +; Version: 1.1 +; Date: August 11, 1989 +; Changes: Register Set, Decrement, and Increment commands now respond +; dynamically to QUIET flag, eliminating "noise." + +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; +; Command: REG +; Function: Manipulate Memory Registers +; +; Forms: +; REG D or REG <-- Display 10 Register Values +; REG Mreg <-- Decrement Register Value +; REG Preg <-- Increment Register Value +; REG Sreg value <-- Set Register Value +; +; Vers 2.1 Joe Wright +; +; REG reg <-- Display a single register value +; +; REG numbers now range from 0 to 31, although only the first ten are +; displayed with REG D. +; +; REG now treats the program error byte as register E. +; +register: + ld de,fcb1+2 ; Pt to first arg + ld a,(de) ; Get possible digit + call regptr ; Pt hl to potential register + dec de ; Point to command + ld a,(de) + cp 'S' ; Set? + jr z,rset + cp 'P' ; Plus? + jr z,rinc + cp 'M' ; Minus? + jr z,rdec + cp ' ' + jr z,rshow + cp 'D' + jr z,rshow + call regptr + jp regout + +; Increment register value +; HL pts to memory register on input + +rinc: inc (hl) ; Increment it + jr Qregout ; Print result + +; Decrement register value +; HL pts to memory register on input + +rdec: dec (hl) ; Decrement value + jr Qregout ; Print result + +; Show first ten registers and Program Error byte + +rshow: call rshow10 + ld hl,z3msg+6 + jp regout + +rshow10:xor a ; Select register 0 + ld b,a ; Counter set to 0 in b + call regp1 ; Hl pts to register 0 +rshow1: ld a,b ; Get counter value + cp 10 ; First ten registers + ret z ; Exit if done + push bc ; Save counter + push hl ; Save pointer + call regout ; Print register value + pop hl ; Get pointer + pop bc ; Get counter + inc b ; Increment counter + ld a,b ; Check for new line + and 3 + call z,crlf ; Newline after fourth display + inc hl ; Pt to next register + jr rshow1 + +; Set register value +; HL pts to register on input + +rset: + ld de,fcb2+1 ; Pt to value + call de2bin ; Eval string at de to binary in b + ld (hl),b ; Set value + +; Enter with HL pointing to the register. HL is maintained. +; +qregout:ld a,(quiet) + or a + ret nz +regout: call print + db ' Reg',' '+80h + ld de,z3msg+30h ; Register 0 + sbc hl,de ; Register number in hl + ld a,l + cp 32 ; A numbered register? + jr c,rego0 ; Yep + call print + db ' ','E'+80h + jr rego1 ; Report + +rego0: push hl + push de + ld b,0 ; Suppress zeros + call decdsp2 ; Report register number + pop de + pop hl +rego1: add hl,de ; Hl points to register again + call print + db ' =',' '+80h + ld l,(hl) + xor a + ld h,a + ld b,a ; Suppress leading zeros + jp decdsp3 ; Display value + +; Evaluate decimal string at DE to binary in B + +de2bin: ld b,0 ; Init value to zero +de2b: ld a,(de) ; Get this digit + inc de ; Pt to next + sub '0' ; Convert to binary + ret c ; A space, finished + cp 10 ; Range? + ret nc ; Not decimal, finished + ld c,a ; Digit in c + ld a,b ; Multiply old by 10 + add a,a ; *2 + add a,a ; *4 + add a,b ; *5 + add a,a ; *10 + add a,c ; Add in new digit + ld b,a ; Result in b + jr de2b ; Again + +; Set HL to point to memory register whose index is pted to by HL +; On input, A contains register char +; On output, HL = address of memory register (reg 0 assumed if error) + +regptr: ld hl,z3msg+6 ; The e register + cp 'E' + ret z + push de + call de2bin ; Get register number in b + pop de + ld a,b + cp 32 ; Range 0-31 + ld a,0 + jr nc,regp1 ; Out of range, use 0 + ld a,b ; Value in a +regp1: ld hl,z3msg+30h ; Pt to memory registers + add a,l ; Pt to proper register + ld l,a + ret ; No chance of crossing page boundary + +; End RCPREG.LIB + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpren.lib b/Source/BPBIOS/Z34RCP11/rcpren.lib new file mode 100644 index 00000000..6947bad2 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpren.lib @@ -0,0 +1,100 @@ + page + +; Library: RCPREN for Z34RCP +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; +; Command: REN +; Function: To change the name of an existing file +; Forms: +; LEFTRIGHT false: +; REN = +; LEFTRIGHT true: +; REN +; LEFTRIGHT and TESTEQ both true: +; Either of the above forms may be used. + +ren: + call retsave + call dirchek ; Test bad dirspec + + if leftright + call fcbswap ; Exchange command line fcb's + endif +; +; STEP 1: See if old name is ambiguous +; + ld hl,fcb2+1 ; Can't be ambiguous + call ambchk1 +; +; STEP 2: Log into user area +; +; If dirspec given at old name, use it +; else use dirspec (or default) given at new name. + + ld hl,fcb1 ; Pt to new name + push hl + ld de,fcb2 ; Pt to old name + push de ; Save ptr + ld a,(de) ; Test if dirspec issued + or a ; ..at old name + jr z,ren1 ; No, use user at new name + ld (hl),a ; Stuff drive into new file + ld a,(fcb2+13) ; Yes, log to user area + call setusr ; ..of old name + jr ren2 +; +; Use dirspec at new name (none given at old name) +; +ren1: + ld a,(hl) ; Stuff drive of new name + ld (de),a ; ..into old name + call logusr ; Log to user at new name +ren2: +; +; STEP 3: See if old file is R/O +; + pop de ; Restore ptr to old FCB + push de ; Save it again + ld c,17 ; Look for old file + call bdos + inc a + jr z,rnxit + call getsbit ; Match found, get ptr to entry in tbuff + ex de,hl ; Hl pts to entry + inc hl ; Pt to fn + call rotest ; See if file is r/o + jr nz,rnxit1 ; Abort if so +; +; STEP 4: See if new file already exists +; EXTEST performs a number of checks: +; 1) Ambiguity +; 2) R/O +; 3) If file exists and not R/O, permission to delete +; + call extest + jr z,rnxit1 ; R/o or no permission +; +; STEP 5: Exchange file name fields for rename +; + pop de ; Pt to old + pop hl ; Pt to new + push hl ; Save ptr + ld b,12 ; 12 bytes + call iswap1 +; +; STEP 6: Rename the file +; + pop de ; Get ptr to FCB + ld c,23 ; Rename + call bdos + inc a ; Set zero flag if error +rnxit: + jp z,noflerr ; EH print no source file message +rnxit1: + jp exit + +; End RCPREN.LIB + + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpsp.lib b/Source/BPBIOS/Z34RCP11/rcpsp.lib new file mode 100644 index 00000000..0fc5ecf6 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpsp.lib @@ -0,0 +1,163 @@ + page + +; Library: RCPSP for Z34RCP +; Author: Carson Wilson +; Version: 1.2 +; Date: August 11, 1989 +; Changes: CRSPACE does nothing if QUIET is true. +; +; Author: Carson Wilson +; Version: 1.1 +; Date: December 30, 1988 +; Changes: Calls CPMVER common routine. +; Sets DMA to TBUFF for CP/M Plus in case we chained to SP. + +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; +; Command: SP +; Function: Shows space remaining on designated drive +; Syntax: SP [DIR:|DU:] +; Comments: This code can be called by several other RCP commands so that +; they can show the space remaining on the disk after their +; operation. + +; Now works with CP/M Plus. + + if [erasp or cpsp or dirsp] +crspace: ; Used to call space after other subroutines + ld a,(quiet) + or a ; Skip if we're being quiet + ret nz + call crlf ; Start new line + jr space0 ; Skip directory check + endif ;[erasp or cpsp or dirsp] +space: + call dirchek ; Abort to EH on bad dirspec +space0: ld a,(fcb1) ; Determine requested drive + or a ; If drive explicitly selected + jr nz,space1 ; ..then skip + ld a,(cdrv) ; Get current drive from ZCPR 3.4 + inc a ; Shift to range 1..16 + +space1: + dec a ; Shift to range 0..15 + ld e,a ; Save in E for selecting disk below + add a,'A' ; Convert to letter and + ld (seldrv),a ; save in message string below + ld c,14 ; BDOS select disk function + call bdos ; Not needed if no drive selected, but smallest + ; ..possible code size this way. + +; Here we extract the following disk parameter information from the disk +; parameter block (DPB): +; BLKSHF: block shift factor (1 byte) +; BLKMAX: max number of blocks on disk (2 bytes) + + call cpmver ; What BDOS is running? + jr nc,isplus + ld c,31 ; BDOS get disk parameters function + call bdos + inc hl ; Advance to block shift factor byte + inc hl + ld a,(hl) ; Get value and + ld (blkshf),a ; ..save it in code below + inc hl ; Advance to max block number word + inc hl + inc hl + ld e,(hl) ; Get value into HL + inc hl + ld d,(hl) + inc de ; Add 1 for max number of blocks + +; Compute amount of free space left on disk + +dfree: + ld c,27 ; BDOS get allocation vector function + push de ; Save BLKMAX value + call bdos ; Get allocation vector into HL + ld b,h ; Copy allocation vector to BC + ld c,l + pop hl ; Restore BLKMAX value to HL + ld de,0 ; Inititialize count of free blocks + +; At this point we have +; BC = allocation vector address +; DE = free block count +; HL = number of blocks on disk + +free1: push bc ; Save allocation address + ld a,(bc) ; Get bit pattern of allocation byte + ld b,8 ; Set to process 8 blocks +free2: rla ; Rotate allocated block bit into carry flag + jr c,free3 ; If set (bit=1), block is allocated + inc de ; If not set, block is not allocated, so + ; ..increment free block count +free3: ld c,a ; Save remaining allocation bits in C + dec hl ; Count down number of blocks on disk + ld a,l ; See if we are down to zero + or h + jr z,free4 ; Branch if no more blocks to check + ld a,c ; Get back current allocation bit pattern + djnz free2 ; Loop through 8 bits + pop bc ; Get pointer to allocation vector + inc bc ; Point to next allocation byte + jr free1 ; Continue by processing next allocation byte + +free4: pop bc ; Clean up stack + ex de,hl ; Free block count to HL +blkshf equ $+1 ; Pointer for in-the-code modification + ld a,0 ; Get block shift factor + sub 3 ; Convert to log base 2 of K per block + jr z,free6 ; Done if single density (1k per block) + +; Convert for blocks of more than 1K each + +free5: add hl,hl + dec a + jr nz,free5 + +; At this point HL = amount of free space on disk in K + +; Display decimal value of HL + +free6: ld b,0 ; Initialize count of digits already printed + ld de,10000 ; Divisor in DE + call decdsp ; Print digit (or space if leading '0') + ld de,1000 + call decdsp + call decdsp3 ; Display hundreds, tens, and units + + call print + db 'k free on ' +seldrv: db 0 ; Modified above to contain drive letter + db ':'+80h + ret + +; CP/M Plus free space calculation + +isplus: ld de,tbuff ; We may have chained to SP + call setdma + ld a,(seldrv) ; Get drive letter + sub 'A' ; Convert to BDOS call value + ld e,a + ld c,2eh ; Get free space call + call bdos + +; Convert 3 byte count of records to K + + ld b,3 ; Total amount to shift +c3fre1: ld hl,tbuff+2 ; Point to buffer start + rr (hl) + dec hl + rr (hl) + dec hl + rr (hl) + djnz c3fre1 + ld hl,(tbuff) ; Get free mod 65536k + jr free6 + +; End RCPSP.LIB + + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/rcpsubs.lib b/Source/BPBIOS/Z34RCP11/rcpsubs.lib new file mode 100644 index 00000000..3879e9a2 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/rcpsubs.lib @@ -0,0 +1,1057 @@ + page + +; Library: RCPSUBS for Z34RCP +; Author: Carson Wilson +; Version: 1.4 +; Date: October 4, 1989 +; Changes: Added modifications by Rob Friefeld for CLED. +; +; Version: 1.3 +; Date: Sept. 8, 1989 +; Changes: Added ERREX1 routine. RCP now sets bit 4 of the command +; status flag on ambiguous file name errors. Intelligent +; error handlers can interpret this bit and chain to +; more sophisticated transient programs of the same name, +; much the same as CP/M Plus. +; +; Version: 1.2 +; Date: August 12, 1989 +; Changes: Added QPLUG and UNPLUG routines for QUIET operation. + +; Author: Carson Wilson +; Version: 1.1 +; Date: December 30, 1988 +; Changes: Added CPMVER routine to detect CP/M Plus. +; Expanded ZSDOS datestamp buffer to full 128 bytes. +; Now gets CCP address from Z34CMN.LIB instead of calculating +; it from BIOS address, since NZCOM allows nonstandard length +; CCP segments (suggested by Howard Goldstein). + +; Author: Carson Wilson +; Version: 1.0 +; Date: June 15, 1988 +; +; Subroutines for Z34RCP.Z80 + +; ---------------------------------------- + +; Routines to chain to Error Handler (EH) + +; 1. Error codes (from ZCPR34.LBR) + +; ZCPR34 uses the error byte at the beginning of the message buffer as a flag +; to show what kind of error occurred. Advanced error handlers will be able +; to help the user further by suggesting the possible cause of the error. + +ecbaddir equ 2 ; Bad directory specification -- logging of + ; ..user number beyond legal range, + ; ..nonexistent named directory +ecambig equ 8 ; Ambiguous file specification where not + ; ..allowed (SAVE, GET, REN) +ecbadnum equ 9 ; Bad numerical value -- not a number where + ; ..number expected, number out of range +ecnofile equ 10 ; File not found -- REN, TYPE, LIST could not + ; ..find a specified file +ecdiskfull equ 11 ; Disk directory or data area full + ; ..(DOS write error) +ectpafull equ 12 ; TPA overflow error +ecdupspec equ 16 ; Duplicate filespecs (COPY, RENAME) + +; 2. Error Routines + + if cpon or renon or lton or proton +NoFlErr: ; File missing + ld a,ecnofile ; File not found error + jr errexit ; Chain to error handler + endif ; cpon or renon or lton or proton + + if cpon +FulErr: ; Disk or directory full (BDOS write error) + ld a,ecdiskfull ; Disk or data area full + jr errexit ; Chain to error handler +DupErr: ; Duplicate file specs + ld a,ecdupspec ; Duplicate filespec error + jr errexit ; Chain to error handler + endif ; cpon +; +; Check for illegal directory specification under ZCPR 3.4. +; DirChek assumes that FCB's have not been altered since they were +; set by the CCP. Therefore DirChek is called before other BDOS calls. + + if cpon or lton or renon or diron or eraon or proton or reson or spaceon +DirChek: + ld a,(fcb1+15) ; Z34 sets these to non zero + ld hl,fcb2+15 ; ..if illegal dirspecs. found + or (hl) + ret z ; Return if OK + ld a,ecbaddir ; Bad dir. error code +; fall thru + endif +; +; Set error type, then set error, ECP, and external program bits of command +; status flag to tell CCP to go straight to EH. + +ErrExit: + ld b,0 +ErrEx1: ld ix,z3msg ; Point to message buffer + ld (ix+0),a ; First set error type in error byte + ld a,00001110b ; Tell CCP External, No ECP, Error, No shell + or b ; Add any specified bits + ld (ix+3),A ; Set bits in command status flag + jp exit ; Return to CCP + +; ------------------------------------------------------------- + +; Routine to get wheel byte - Returns wheel in A with flags set + +getwhl: + push hl + ld hl,(z3whl) ; Get wheel address from ENV + ld a,(hl) ; Read wheel byte + and a ; Set flags + pop hl + ret + +; ------------------------------ + + if systime ; Time in CLED prompt +; +; PMBCD - Print byte at (HL) to console as 1 or 2 BCD digits. +; +; Entry: A = 80h for leading zero's +; A = 0h for no leading zero's +; +pmbcd: + rld ; Get hi nibble (HL) to low nibble A + or a ; For HD64180 + call nz,decout ; Display if low or high of A not zero + rld ; Get low nibble (HL), from above +decout: + and 00001111b ; Mask high nibble + add a,'0' ; Convert to character + jr conout + endif ; systime + +; Display decimal digit routines + +; Display hundreds, tens, and units digits (assumes flag in B has been set) + + if regon or spaceon +decdsp3: + ld de,100 ; Display hundreds + call decdsp +decdsp2: + ld de,10 ; Display tens + call decdsp + ld a,l ; Get remaining units value +decdsp4: + add a,'0' ; Convert to character + jr conout ; Print it and return + +; --------------------------------- + +; Routine to print any single digit + +; Actually, this routine displays the value of HL divided by DE and leaves the +; remainder in HL. In computing the character to display, it assumes that the +; result of the division will be a decimal digit. If the result is zero, the +; value in the B register, which is the number of digits already printed, is +; checked. If it is zero, a space is printed instead of a leading '0'. If it +; is not zero, the '0' is printed. Whenever any digit (not a space) is +; printed, the value in B is incremented. + + +decdsp: + ld c,'0'-1 ; Initialize digit count + xor a ; Clear carry flag + +decdsp1: + inc c ; Pre-increment the digit + sbc hl,de ; Subtract DE from HL + jr nc,decdsp1 + + add hl,de ; Add back in to produce remainder + ld a,c ; Get decimal digit + cp '0' ; Check for leading 0 + jr nz,decdout ; If not 0, proceed to display it + ld a,b ; Digit printed already? + or a + jr z,spac ; Print leading space if not +decdout: + ld a,c ; Else print real digit + inc b ; Indicate digit printed + jr conout + endif ; regon or spaceon + +; ------------ + +; Print a dash + + if lton or peekon +dash: call print + db ' -',' '+80h + ret + endif ; Lton or peekon + +; ------------------------- + +; Shut off output in QUIET is set. Return Z if QUIET, NZ otherwise. +; Uses: AF + +qplug: + ld a,(quiet) + or a + ret z + ld a,0C9h ; "RET" +qplug1: ld (plug),a + ret + +; -------------------------- + +; Turn on output + +unplug: xor a + jr qplug1 + + +; ------------- + +; Print a space + +spac: ld a,' ' + +; fall thru + +; Console Output Routine + +conout: +plug: db 00h ; Goes to 0C9h to turn off output + putreg ; Save all registers except AF + push af ; Save AF, too + and 7fh ; Mask out MSB + ld e,a ; Transfer character to E + ld c,2 ; BDOS conout function number + call bdos + pop af + getreg ; Restore registers +note: ; Use this RET for NOTE command + ret + +; ------------------------ + +; String printing routines + +; Print string following call (terminated with null or character with the +; high bit set) + +print: + ex (sp),hl ; Get address + call printhl + ex (sp),hl ; Put address + ret + + if whlon or quieton +; +; Routine to say "On" if A is non-zero or "Off" if A is zero. +; Called by WHL and Q commands. + +onmsg: + dc ' On' +offmsg: + dc ' Off' +tella: + ld hl,offmsg ; Prepare to say Off + or a + jr z,printhl ; Say it if A=0 + ld hl,onmsg ; Say On + +; fall thru ; Display message and return + + endif ; whlon or quieton + +; Print string pointed to by HL (terminated with null or character with the +; high bit set) + +printhl: + ld a,(hl) ; Get next character + inc hl ; Point to following one + or a ; See if null terminator + ret z ; If so, we are done + call conout ; Display the character + ret m ; We are done if MSB is set (negative number) + jr printhl ; Back for more + +; ------------------------ + +; Output new line to CON: + +crlf: + call print + db cr,lf+80h + ret + +; Console input + + if eraon or lton or proton or renon or cpon +conin: + push hl ; Save regs + push de + push bc + ld c,1 ; Input + call bdos + pop bc ; Get regs + pop de + pop hl + and 7fh ; Mask msb + cp 61h + ret c + and 5fh ; To upper case + ret + endif ; Eraon or lton or proton or renon or cpon + +; ------------------- + +; Save return address + +retsave: + pop de ; Get return address + pop hl ; Get return address to zcpr3 + ld (z3ret),hl ; Save it + push hl ; Put return address to zcpr3 back + push de ; Put return address back + ret + +; ------------------------------- + +; Test file pted to by HL for R/O +; NZ if R/O + + if renon or cpon or eraon +rotest: + push hl ; Advance to r/o byte + ld bc,8 ; Pt to 9th byte + add hl,bc + ld a,(hl) ; Get it + and 80h ; Mask bit + push af + ld hl,romsg + call nz,printhl ; Print if nz + pop af ; Get flag + pop hl ; Get ptr + ret +romsg: + db ' is R/','O'+80h + +; ----------------------------------------------- + +; Check user to see if he approves erase of file +; Return with Z if yes + +eraq: + call print + db ' - Eras','e'+80h + endif ; Renon or cpon or eraon + + if renon or cpon or eraon or proton +eraq1: + call print + db ' (Y/N/Q)? N',88h ; 88h = backspace + call conin ; Get response + cp 'Q' ; Quit command? + jr z,exit + cp 'Y' ; Key on yes + ret + endif ; Renon or cpon or eraon or proton + +; ------------------------------------- + +; Give space on current disk and return + + if spaceon and [dirsp or cpsp or erasp or resetsp] +spaexit: + call crspace ; Show space remaining +; fall thru + endif ; spaceon and [dirsp or cpsp or erasp or resetsp] + +; +; Exit to ZCPR3 +; +exit: +z3ret equ $+1 ; Pointer to in-the-code modification + ld hl,0 ; Return address + jp (hl) ; Goto ZCPR3 + +; -------------------------------------------------------- + +; Check for user input; if ^X, return with Z, abort if ^C + + if diron or lton or eraon or proton or peekon +break: + push hl ; Save regs + push de + push bc + ld c,11 ; Console status check + call bdos + or a + ld c,1 ; Get char if any + call nz,bdos + pop bc ; Restore regs + pop de + pop hl +break1: cp ctrlc ; Check for abort + jr z,exit ; Exit + cp ctrlx ; Skip? + ret + endif ; Diron or lton or eraon or proton or peekon + +; --------------------- + +; Print address in DE + + if peekon or pokeon +adrat: + call print + db ' at',' '+80h + ld a,d ; Print high + call pahc + ld a,e ; Print low + jp pahc + endif ; Peekon or pokeon + +; -------------------------------------------------- + +; Extract hexadecimal number from line pted to by HL +; Return with value in DE and HL pting to offending char + + if peekon or pokeon or porton +hexnum: + ld de,0 ; De=accumulated value +hnum1: + ld a,(hl) ; Get char + cp ' '+1 ; Done? + ret c ; Return if space or less + inc hl ; Pt to next + sub '0' ; Convert to binary + jr c,numerr ; Return and done if error + cp 10 ; 0-9? + jr c,hnum2 + sub 7 ; A-f? + cp 10h ; Error? + jr nc,numerr +hnum2: + push hl ; Save pointer + ex de,hl + add hl,hl + add hl,hl + add hl,hl + add hl,hl ; De x16 to hl + ld e,a + ld d,0 + add hl,de + ex de,hl ; De = de * 16 + a + pop hl ; Get the pointer + jr hnum1 ; Try again +; +; Number error +; +numerr: + ld a,ecbadnum ; Numeric error + jp errexit ; Chain to error handler + +; Skip to next non-blank + +sksp: + ld a,(hl) ; Get char + inc hl ; Pt to next + cp ' ' ; Skip spaces + jr z,sksp + dec hl ; Pt to good char + or a ; Set eol flag + ret + endif ; Peekon or pokeon or porton + +; ------------------------------------------------------------------------ + +; Test File in FCB for unambiguity and existence, ask user to delete if so +; Return with Z flag set if R/O or no permission to delete + + if renon or cpon +extest: + call ambchk ; Ambiguous file names not allowed + call searf ; Look for specified file + jr z,exok ; Ok if not found + call getsbit ; Position into dir + inc de ; Pt to file name + ex de,hl ; Hl pts to file name + push hl ; Save ptr to file name + call prfn ; Print file name + pop hl + call rotest ; Check for r/o + jr nz,exer + call eraq ; Erase? + jr nz,exer ; Restart as error if no + ld de,fcb1 ; Pt to fcb1 + ld c,19 ; Delete file + call bdos +exok: xor a + dec a ; Nz = ok + ret +exer: + xor a ; Error flag - file is r/o or no permission + ret + +; Check for ambiguous file name in FCB1 +; Return z if so + +ambchk: + ld hl,fcb1+1 ; Pt to fcb + +; Check for ambiguous file name pted to by HL + +ambchk1: + push hl + ld b,11 ; 11 bytes +amb1: ld a,(hl) ; Get char + and 7fh ; Mask + cp '?' + jr z,amb2 + inc hl ; Pt to next + djnz amb1 + dec b ; Set nz flag + pop de + ret +amb2: + pop hl ; Pt to file name + ld a,ecambig ; Ambiguous name error + ld b,00010000b ; Bit 4 tells EH to load transient + jp ErrEx1 ; Chain to error handler + endif ; Renon or cpon + +; --------------------------------------- + +; Init FCB1, return with DE pting to FCB1 + + if eraon or lton or cpon +initfcb1: + ld hl,fcb1 ; Pt to fcb +initfcb2: + push hl ; Save ptr + ld bc,12 ; Pt to first byte + add hl,bc + ld b,24 ; Zero 24 bytes + xor a ; Zero fill + call fillp ; Fill memory + pop de ; Pt to fcb + ret + endif ; Eraon or lton or cpon + +; Fill a region with byte in A + + if eraon or lton or cpon or diron +fillp: + ld (hl),a ; Store byte + inc hl ; Pt to next + djnz fillp ; Count down + ret + endif ; Eraon or lton or cpon or diron + +; --------------------------------------------------------------------- + +; After a search, return NZ set if desired type of file found, Z if not. +; This algorithm looks at the system bit of the located file; this +; bit is set to 1 if the file is a system file and 0 if not a system +; file. The following exclusive or masks are applied to return Z or NZ +; as required by the calling routine: +; +; SYSTEM BYTE: X 0 0 0 0 0 0 0 (AFTER 80H MASK, X=1 IF SYS, 0 IF DIR) +; +; SYS-ONLY : 0 0 0 0 0 0 0 0 (XOR 0 = 0 if X=0, = 80H if X=1) +; DIR-ONLY : 1 0 0 0 0 0 0 0 (XOR 80H = 80h if X=0, = 0 if X=1) +; BOTH : 0 0 0 0 0 0 0 1 (XOR 1 = 81H or 1H, NZ in both cases) + + if diron or eraon or lton or proton or cpon or renon +getsbit: + dec a ; Adjust to returned value + rrca ; Convert number to offset into tbuff + rrca + rrca + and 60h + ld de,tbuff ; Pt to buffer + add a,e ; Add entry offset to base addr + ld e,a ; Result in e + push de ; Save ptr in de + add a,10 ; Add offset of 10 to pt to system byte + ld e,a ; Set address + ld a,(de) ; Get byte + pop de ; Get ptr in de + and 80h ; Look at only system bit +systst equ $+1 ; In-the-code variable + xor 0 ; If systst=0, sys only; if systst=80h, dir + ; Only; if systst=1, both sys and dir + ret ; Nz if ok, z if not ok + +; Log into user area contained in FCB1 + +logusr: + ld a,(fcb1+13) ; Get user number +setusr: + ld e,a + ld c,32 ; Use bdos fct + jp bdos + +; Print file name pted to by HL + +prfn: + call spac ; Leading space + ld b,8 ; 8 chars + call prfn1 + call print + db '.'+80h ; Dot + ld b,3 ; 3 chars +prfn1: ld a,(hl) ; Get char + inc hl ; Pt to next + call conout ; Print char + djnz prfn1 ; Count down + ret + +; Search for first + +searf: + push bc ; Save counter + push hl ; Save hl + call dirchek ; Check bad dirspec + ld c,17 ; Search for first function +searf1: ld de,fcb1 ; Pt to fcb + call bdos + inc a ; Set zero flag for error return + pop hl ; Get hl + pop bc ; Get counter + ret + endif ; Diron or eraon or lton or proton or cpon or renon + +; ------------------------- + +; Copy HL to DE for B bytes + + if diron or eraon or lton or proton or cpon +blkmov: + ld a,(hl) ; Get + ld (de),a ; Put + inc hl ; Pt to next + inc de + djnz blkmov ; Loop + ret + endif ; Diron or eraon or lton or proton or cpon + +; ----------------------------- + +; Print file not found message + + if diron or eraon +prfnf: + call print + db ' No File','s'+80h + jp exit + endif ; Diron or eraon + +; ------------------------------------------------------------------ + +; Define buffers as high as possible in TPA for the following groups +; of commands: +; +; COPY needs SRCFCB, CBUFF, STPBUF (if STPCALL) +; ERA, PROT, LIST/TYPE need DIRBUF and NXTFILE +; DIR needs DIRBUF +; +; If DIRBUF is defined, its value is in HL on return from this code. The DE +; register pair is not changed by the code, but the BC pair is affected. + +dirbufon equ lton or eraon or proton or diron + + if dirbufon +dirbuf: ds 2 ; Address for directory buffer + endif ; dirbufon + + if lton or eraon or proton +nxtfile: ds 2 ; Ptr to next file in list + endif ; eraon or lton or proton + + if cpon or dirbufon or cledon +define: + push de + ld hl,(bdos+1) ; Get bottom of BDOS + ex de,hl ; ..into DE + ld hl,(ccp) ; From Z34CMN.LIB + +; Now we have to compare and pick the lower address as the top of TPA + + push hl ; Save CPR address while comparing + xor a ; Clear the carry flag + sbc hl,de ; Compute (CPR-BDOS) + pop hl ; Restore CPR address + jr c,define1 ; Branch if BDOS address is higher (use CPR) + ex de,hl ; Otherwise use BDOS address +define1: + if cpon + ld de,-36 ; Calculate place for SRCFCB for copy command + add hl,de + ld (srcfcb),hl + + if stpcall + ld de,-128 + add hl,de ; Buffer for datestamps + ld (stpbuf),hl + endif ; stpcall + + if dirbufon + push hl ; Save if needed below + endif ; dirbufon + + ld de,-[cpblocks*128] ; CBUFF can use same space as DIRBUF + add hl,de + ld (cbuff),hl + + if dirbufon + pop hl + endif ; dirbufon + + endif ; cpon + + if dirbufon + ld de,-[maxdirs*11] ; Space for directory buffer + add hl,de + ld (dirbuf),hl + endif ; dirbufon + + pop de + ret + + endif ; cpon or dirbufon or cledon + +; --------------- + +; Search for next + + if diron or eraon or lton or proton +searn: + push bc ; Save counter + push hl ; Save hl + ld c,18 ; Search for next function + jr searf1 + +; Load directory and sort it +; On input, A=SYSTST flag (0=SYS, 1=DIR, 80H=BOTH) +; Directory is loaded into buffer at top of TPA +; Return with ZERO set if no match and HL pts to 1st entry if match + +direrr: + ld a,ectpafull ; Chain to error handler + jp errexit + +getdir: + ld (systst),a ; Set system test flag + call logusr ; Log into user area of fcb1 + call define ; Define buffer addresses + ld (hl),0 ; Set empty + ld bc,0 ; Set counter + call searf ; Look for match + ret z ; Return if not found + +; Step 1: Load directory + +gd1: + push bc ; Save counter + call getsbit ; Check for system ok + pop bc + jr z,gd2 ; Not ok, so skip + push bc ; Save counter + inc de ; Pt to file name + ex de,hl ; Hl pts to file name, de pts to buffer + ld b,11 ; Copy 11 bytes + call blkmov ; Do copy + pop bc ; Get counter + inc bc ; Increment counter + ld hl,maxdirs-1 ; See if count equals or exceeds MAXDIRS + ld a,b ; Check high bytes + sub h + jr c,gd1a ; If carry set, we are OK + ld a,c ; Check low bytes + sub l + jr nc,direrr ; If no carry, jump to error message +gd1a: ex de,hl ; Hl pts to next buffer location +gd2: call searn ; Look for next + jr nz,gd1 + ld (hl),0 ; Store ending 0 + ld hl,(dirbuf) ; Pt to dir buffer + ld a,(hl) ; Check for empty + or a + ret z + +; Step 2: Sort directory + + if dsort + push hl ; Save ptr to dirbuf for return + call diralpha ; Sort + pop hl + endif + + xor a ; Set nz flag for ok + dec a + ret + + +; DIRALPHA -- Alphabetizes directory in DIRBUF; BC contains +; the number of files in the directory +; + if dsort +diralpha: +; +; SHELL SORT -- +; This sort routine is adapted from "Software Tools" +; by Kernigan and Plaugher, page 106. Copyright, 1976, Addison-Wesley. +; + ld h,b ; Hl=bc=file count + ld l,c + ld (num),hl ; Set "NUM" + ld (gap),hl ; Set initial gap to n for first division by 2 + +; For (gap = num/2; gap > 0; gap = gap/2) + +srtl0: +gap equ $+1 ; Pointer for in-the-code modification + ld hl,0 ; Get previous gap + or a ; Clear carry + rr h ; Rotate right to divide by 2 + rr l + + ld a,l ; Test for zero + or h + ret z ; Done with sort if gap = 0 + + ld (gap),hl ; Set value of gap + push hl + pop ix ; Set ix=gap for following loop + +; For (ix = gap + 1; ix <= num; ix = ix + 1) + +srtl1: + inc ix ; Add 1 to ix + push ix + pop de ; IX is in DE + +; Test for ix <= num + +num equ $+1 ; Pointer for in-the-code modification + ld hl,0 ; Number of items to sort + ld a,l ; Compare by subtraction + sub e + ld a,h + sbc a,d ; Carry set means ix > n + jr c,srtl0 ; Don't do for loop if ix > n + ex de,hl ; Set jj = ix initially for first + ; ..subtraction of gap + ld (jj),hl + +; For (jj = ix - gap; jj > 0; jj = jj - gap) + +srtl2: + ld de,(gap) +jj equ $+1 ; Pointer for in-the-code modification + ld hl,0 ; Get jj + + sbc hl,de ; Compute JJ - GAP + ld a,h + ld (jj),hl ; Jj = jj - gap + jr c,srtl1 ; If carry from subtractions, jj < 0 and abort + or l ; Jj=0? + jr z,srtl1 ; If zero, jj=0 and abort + +; Set iy = jj + gap + + ex de,hl ; Jj in de + ld hl,(gap) ; Get gap + add hl,de ; Jj + gap + push hl + pop iy ; IY = iy + gap + +; If (v(jj) <= v(iy)) + + call icompare ; JJ in de, iy in hl + +; ...then break + + jr c,srtl1 + +; ...else exchange + + ld de,(jj) ; Swap jj, iy + push iy + pop hl + call iswap ; Jj in de, iy in hl + +; End of inner-most for loop + + jr srtl2 + +; +; SWAP (Exchange) the elements whose indexes are in HL and DE +; +iswap: + call ipos ; Compute position from index + ex de,hl + call ipos ; Compute 2nd element position from index + ld b,11 ; 11 bytes to flip + endif ; Dsort + endif ; Diron or eraon or lton or proton + + if diron or eraon or lton or proton or renon or (cpon and leftright) +iswap1: + ld a,(de) ; Get bytes + ld c,(hl) + ex de,hl + ld (de),a ; Put bytes + ld (hl),c + inc hl ; Pt to next + inc de + djnz iswap1 + ret + endif ; Diron or eraon or lton or proton or renon + ; ..or (cpon and leftright) + + if leftright and (cpon or renon) +; +; FCBSWAP exchanges 16 byte FCB1 with FCB2 from the command line. +; This allows COPY and REN commands to execute left to right, +; e.g., "cp source dest". Costs 10 bytes. +; +; If TESTEQ is true, then commands containing '=' still execute right +; to left, e.g., "cp dest=source". Costs 11 bytes additional. +; +fcbswap: + if testeq + xor a ; Zero B + ld b,a + ld hl,80h ; Point to command line, length + ld c,(hl) ; Set up for CPIR + ld a,'=' ; Search for '=' + cpir ; '=' found in command? + ret z ; Yes, don't swap + endif ; Testeq + + ld de,fcb1 ; Point to command fcb's + ld hl,fcb2 + ld b,16 ; Exchange them + jr iswap1 + endif ; leftright and (cpon or renon) + + if diron or eraon or lton or proton + if dsort +; +; ICOMPARE compares the entry pointed to by the pointer pointed to by HL +; with that pointed to by DE (1st level indirect addressing); on entry, +; HL and DE contain the numbers of the elements to compare (1, 2, ...); +; on exit, Carry Set means ((DE)) < ((HL)), Zero Set means ((HL)) = ((DE)), +; and Non-Zero and No-Carry means ((DE)) > ((HL)) +; +icompare: + call ipos ; Get position of first element + ex de,hl + call ipos ; Get position of 2nd element + ex de,hl +; +; Compare dir entry pted to by HL with that pted to by DE; +; No net effect on HL, DE; ret w/CARRY set means DE +; Version: 1.2 +; Date: 16 June 1988 +; Changes: Renamed CRT0 to LINS for NZCOM compatibility. +; Added CUSR " " " + +; Author: Carson Wilson +; Version: 1.1 +; Date: 12 June 1988 +; Changes: Added CDRV for various Z34RCP commands. +; Added CRT0 for Z34RCP TYPE command. +; Added Z3TCAP and CLRSCR for Z34RCP CLS command. +; Added QUIET for ZCPR34 time in prompt. +; Added RSDMSG for ZCPR34 time in prompt. + +; Library: Z34CMN.LIB +; Author: Joe Wright +; Date: 23 March 1988 + +; As a replacement for Z3BASE.LIB, some usual equates. + +base equ 0 ; Base Page + +false equ 0 +true equ not false + +no equ false +yes equ true + +off equ false +on equ true + +; Named COMMON declarations start here. For compatibility, these +; are the same names used by Bridger Mitchell's JetLDR. + + common /_ENV_/ +z3env: ; Z3 Environment descriptor +z3envs equ yes ; There is one + +expath equ z3env+9 ; Address of External Path +expaths equ 10 ; Maximum 10 elements for MPATH + +rcp equ z3env+0ch ; Address of RCP +rcps equ yes ; Used as existence test, not size + +fcp equ z3env+12h ; Address of FCB +fcps equ yes ; Used as existence test, not size + +z3ndir equ z3env+15h ; Address of NDR +z3ndirs equ yes ; Used as existence test, not size + +quiet equ z3env+28h ; Quiet flag address + +z3whl equ z3env+29h ; Wheel byte address +z3whls equ yes ; There is a wheel + +lins equ z3env+33h ; CRT text lines address + +ccp equ z3env+3fh ; CCP entry +ccps equ z3env+41h ; Size + +dos equ z3env+42h ; DOS entry (+6) +doss equ z3env+44h ; Size + +bio equ z3env+45h ; BIO entry + +z3tcap equ z3env+80h ; TCAP address + +clrscr equ z3env+97h ; Clear screen string address + + common /_SSTK_/ +shstk: ; Top of Shell stack +shstks equ yes ; There is a shell stack + + common /_MSG_/ +z3msg: ; Message buffer +z3msgs equ yes ; There is one + +cusr equ z3msg+2eh ; Current user +cdrv equ z3msg+2fh ; Current drive + +rsdmsg equ z3msg+3ah ; Reserved bytes + + common /_FCB_/ +extfcb: ; External file control block +extfcbs equ yes ; There is one + + common /_MCL_/ +z3cl: ; Multiple command line +z3cls equ yes ; There is one + + common /_XSTK_/ +extstk: ; External stack +extstks equ yes ; There is one + + common /_BIOS_/ +bios: + + cseg ; Select Code Segment + +; End of Z34CMN.LIB + + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/z34defn.lib b/Source/BPBIOS/Z34RCP11/z34defn.lib new file mode 100644 index 00000000..683fc322 Binary files /dev/null and b/Source/BPBIOS/Z34RCP11/z34defn.lib differ diff --git a/Source/BPBIOS/Z34RCP11/z34mac.lib b/Source/BPBIOS/Z34RCP11/z34mac.lib new file mode 100644 index 00000000..8ab3ffbe --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/z34mac.lib @@ -0,0 +1,122 @@ + +; Z33MAC.LIB : Macros for use with ZCPR33 + +; General purpose macros + +putreg macro + push hl ; Save registers in order + push de + push bc + endm + +getreg macro + pop bc ; Restore registers in order + pop de + pop hl + endm + +swap macro + rrca ; Exchange nibbles + rrca + rrca + rrca + endm + +;---------------------------------------- + +; Macro for forming option bytes + +; This macro generates a byte with bits corresponding to up to 8 option +; flags. The bits are filled in the order of the parameters and are right +; justified in the byte. + +optflag macro f1,f2,f3,f4,f5,f6,f7,f8 + +flag defl 0 ;; initial value + + irp temp, + + if not nul temp +flag defl flag shl 1 + if temp +flag defl flag or 1 + endif ;;temp + endif ;;not nul temp + + endm ;; irp + + defb low flag + + endm ;; optflag + +;----------------------------------------------------------------------------- + +; Command table entry definition macro + +; Macro to form an entry for one command in the table. The first parameter is +; the name to be used for the command (no quotes); the second parameter is the +; flag that indicates whether or not the command is to be enabled; the third +; parameter is the wheel control flag; and the last parameter is the jump +; address to the code that carries out the command. The command names are +; automatically padded out to the correct length (they will be truncated and +; an error message will result if a command name is too long). The characters +; in the command name are automatically converted to upper case. + +command macro cmdname,enableflag,wheelflag,address + + if enableflag ;; Generate command only if enabled + +whlmask defl wheelflag ;; Initialize variables +count defl cmdsize ;; Initialize to size of each command name + + irpc char,cmdname ;; Repeat over letters in command name + +count defl count - 1 ;; Count down characters in name + + if [ count lt cmdsize ] + + ;; If character is lower case, convert to upper case + + if [ '&char' ge 'a' ] and [ '&char' le 'z' ] + + if whlmask + defb [ '&char' and 5fh ] + 80h + else ;;not whlmask + defb [ '&char' and 5fh ] + endif ;;whlmask + + else ;;not lower case + + if whlmask + defb '&char' + 80h ;; If controlled by wheel, set high bit + else ;;not whlmask + defb '&char' ;; If not restricted, leave high bit clear + endif ;;whlmask + + endif ;;lower case + + endif ;;[ count lt cmdsize ] + +whlmask defl false ;; Turn off high-bit setting after first char + + endm ;irpc + + ;; Pad command name with blanks + + if [ count gt cmdsize ] ;; If we underflowed + *** Command name "&cmdname" is too long / truncated *** + else + rept count + defb ' ' + endm + endif ;[ count gt cmdsize ] + + dw address ;; Dispatch address for command + + endif ;enable + + endm ;command + +; End Z33MAC.LIB + + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/z34rcp.lib b/Source/BPBIOS/Z34RCP11/z34rcp.lib new file mode 100644 index 00000000..74908d54 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/z34rcp.lib @@ -0,0 +1,515 @@ + +; File: Z34RCP.LIB +; Description: Resident Command Package (RCP) for ZCPR34 +; Version: 1.4 +; Author: Carson Wilson +; Date: October 4, 1989 +; Changes: Added PUSER0 equate suggested by Rob Friefeld. +; +; Version: 1.3 +; Author: Carson Wilson +; Date: September 29, 1989 +; Changes: Added CLED (Command Line Editor Shell) command options +; and equates. +; +; Version: 1.2 +; Author: Carson Wilson +; Date: August 11, 1989 +; Changes: Removed several "quiet" conditional assembly equates as +; we are now sensing the QUIET flag instead. +; Changed default of WHLQUIET to false. +; +; Version: 1.1 +; Author: Carson Wilson +; Date: July 10, 1989 +; Changes: Changed 'S' command name to 'SP'. +; Removed wheel protection from 'Q' command and set QQUIET +; on by default. +; Changed command order. + +; Version: 1.0 +; Author: Carson Wilson +; Date: June 15, 1988 + +; This is the configuration file for the ZCPR Version 3.4 resident command +; package. You should read through this file and set the equates according +; to the features you want to implement. Since most systems have a limited +; amount of space for the resident command package, it will probably be +; impossible to include all features. + +; If you are using an SLR or equivalent assembler, you will be prompted +; at assembly time for which commands to include in the RCP. + +;============================================================================= +; +; A S S E M B L Y O P T I O N S +; +;============================================================================= + +; SLR controls whether special pseudo-ops such as ".printx" and ".accept", +; and complex macros are used at assembly time. For SLR Systems or compatible +; macro assemblers, set this equate to true. + +SLR equ false + +; RCPTYPE is the final character of the RCP name in the H(elp) command, +; and determines the order in which commands are added. + + if SLR +Y equ true +N equ false +H equ 'H' +F equ 'F' + +.accept 'Enter "F" for floppy, "H" for fixed disk version: ',rcptype + else +rcptype equ 'H' ; Floppy disk version + endif ; SLR + +; ZRL controls whether named common labels are used in the code. To create +; absolute code, modify RCPBASE.LIB to reflect your system's addresses, set +; ZRL to false and instruct your assembler to generate absolute code (either +; a .COM or .HEX file). +; +; To create a relocatable file for use with Bridger Mitchell's JetLDR or +; Joe Wright's NZCOM, you must use an assembler which recognizes named +; common blocks. If your assembler has this capability, set ZRL to true +; and instruct the assembler to generate relocatable (.REL) code. + +ZRL equ true + +;============================================================================= +; +; M A C R O S +; +;============================================================================= + +; SLR assemblers allow selection of options during assembly. + + if SLR + +select macro option default comment + .accept comment,option ; Prompt user for selection + endm + + .printx + .printx Answer "Y" to include, "N" to exclude commands: + .printx + + else + +select macro option default comment +option equ default ; Use selections from file + endm + endif ; SLR + +;============================================================================= +; +; R E S I D E N T C O M M A N D S S E L E C T I O N +; +;============================================================================= + +; Commands to include in the resident command package + +; There are some interrelations between a number of the possible resident +; commands. Some are so close (LIST and TYPE) that a single equate controls +; both functions. Others like DIR and ERA share code (both display file +; listings). It is efficient to select or deselect them together. + +; If you are using an SLR or compatible assembler, you will be prompted +; at assembly time for command selections. Otherwise, select commands to +; be included by setting each SELECT macro below to yes or no. + +select CLEDON no 'CLED - command line editor shell? ' +select CLSON yes 'CLS - clear screen? ' +select RESON yes 'R - reset disk system? ' +select SPACEON yes 'SP - show space remaining on disk? ' + + if RCPTYPE='F' ; Assemble in floppy order +select DIRON yes 'D - disk directory? ' +select ERAON yes 'ERA - erase files? ' +select ECHOON yes 'ECHO - send text to screen or printer? ' +select RENON yes 'REN - rename files? ' +select CPON yes 'CP - copy file? ' +select LTON yes 'TYPE - display file on console? ' +select POKEON yes 'POKE - set memory? ' +select PEEKON yes 'PEEK - view memory? ' + else ; Assemble in fixed-disk order +select ECHOON yes 'ECHO - send text to screen or printer? ' +select POKEON yes 'POKE - set memory? ' +select PEEKON yes 'PEEK - view memory? ' +select QUIETON yes 'Q - set or clear quiet status? ' +select DIRON yes 'D - disk directory? ' +select ERAON no 'ERA - erase files? ' +select RENON no 'REN - rename files? ' +select CPON no 'CP - copy file? ' +select LTON yes 'TYPE - display file on console? ' + endif ; RCPTYPE='F' + + if LTON +select LISTON no 'LIST - list to printer? ' + else ; allowed only if TYPE is enabled +LISTON equ no + endif + + if RCPTYPE='F' ; Assemble in floppy order +select QUIETON yes 'Q - set or clear quiet status? ' + endif + +select PROTON no 'PROT - set file attributes? ' +select NOTEON no 'NOTE - command-line comment? ' +select REGON no 'REG - set and display user registers? ' +select WHLON no 'WHL - set or clear wheel status? ' +select PORTON no 'PORT - view and set I/O ports? ' + +;============================================================================= +; +; W H E E L P R O T E C T I O N +; +;============================================================================= + +; To prevent unauthorized users from performing certain dangerous or sensitive +; operations on the computer, ZCPR34 provides the capability of disabling the +; operation of certain commands when the wheel byte is off. In ZCPR30, an +; attempt to use one of these wheel-protected commands when the wheel byte was +; off resulted in an error message. In ZCPR34 things work differently. In the +; same situation, the command simply disappears. In this way a transient +; program or extended command processor function can take over and deal with +; the attempt to use the command in a much more flexible way. +; +; Wheel-protected commands in ZCPR30 had extra code to intercept the function +; and disable it. In ZCPR34, wheel protection is enabled and disabled in a +; different way. To wheel-protect a command the high bit of the first +; character in the command name is set. The command table scanner in ZCPR34 +; will not recognize these commands when the wheel byte is off. Since the same +; command scanner is used to scan the commands in the RCP and FCP (flow control +; package), commands there can be wheel protected in the same way. For skilled +; computer operators it is very easy to use a debugger, file patcher, or disk +; utility to enable and disable wheel protection without having to reassemble +; the CPR, RCP, or FCP. +; +; Because of the way the command scanner works, once wheel protection is +; implemented in the CCP, there is no further code penalty in protecting RCP +; commands. Therefore, we recommend protecting all possibly sensitive +; commands or none. + +wcled equ no ; CLED +wcp equ yes ; CP +wdir equ no ; DIR +wera equ yes ; ERA +wlist equ yes ; LIST +wpeek equ yes ; PEEK +wpoke equ yes ; POKE +wport equ yes ; PORT +wprot equ yes ; PROT +wquiet equ no ; QUIET +wreg equ yes ; REG +wren equ yes ; REN +wspop equ yes ; SPOP +wtype equ yes ; TYPE +whrc equ no ; H + +wheel defl wcp or wdir or wera or wlist or wpeek or wpoke or wport +wheel defl wheel or wprot or wreg or wren or wtype or whrc + +;============================================================================= +; +; C O M M A N D O P T I O N S +; +;============================================================================= + +; Options affecting several commands + +; DSORT includes code to sort file entries for the D, TYPE, LIST, ERA, +; and PROT commands. + +dsort equ yes + +; LEFTRIGHT affects the COPY and RENAME commands. If yes, add code to allow +; CP and REN operations work from left to right, and CP to accept a +; single filespec as its source. +; Examples: +; B0>ren b1:old.nam new.nam Renames B1:old.nam to B1:new.nam +; B0>cp a15:that.fil Copies A15:that.fil to B0: +; B0>cp source.fil dest.fil Copies source.fil to dest.fil + +leftright equ yes + +; If LEFTRIGHT is yes, setting TESTEQ to yes also allows CP and REN commands +; to be processed from right to left if they contain an equal sign ("="). +; Costs 11 bytes. +; Example: +; B0>cp dest.fil=source.fil Copies source.fil to dest.fil + +testeq equ yes + +;----------------------------------------------------------------------------- + +; 'CLS' command + +; This command clears the console screen. It can either use a fixed string +; (for shorter code if the same terminal is always used) or use the TCAP +; entry for automatic adaptation to any terminal. + +clstcap equ true ; Use TCAP for clear-screen string + +; If CLSTCAP is not enabled, then the string below must be provided and +; it must end with a character with the high bit set + + if not clstcap +clsstr macro + db 'Z'-'@'+80h ; Control-Z to clear screen + endm + endif ;not clstcap + +typecls equ true ; Clear between screens when typing files + +;----------------------------------------------------------------------------- + +; 'CP' command + +; This command copies a single file. The destination file can have a different +; name from the source file. Set the size of the memory buffer in K below (do +; not change the second definition). The copy buffer is placed at the top of +; the TPA, where it will generally not interfere with subsequent use of the GO +; command to rerun a program loaded in the TPA. + +cpblocks defl 16 ; Size of copy buffer in K + +cpblocks defl cpblocks * 8 ; Convert to number of records + +; If you will be using a BDOS or RSX which supports new functions 102 (Get +; Stamp) and 103 (Set Stamp), set STPCALL to true, and datestamps of files +; will be preserved across copies. The "last access" stamps of both copies +; are set to the time the copy took place if access stamping is enabled. + +stpcall equ true ; Copy file stamps with functions 102/3 + +;----------------------------------------------------------------------------- + +; 'DIR' command + +; This command displays a sorted listing of the files in a directory. The +; file name buffer is allocated at the top of the TPA so as not to modify +; any program loaded in low memory. Consequently, the size of the buffer +; must be set in advance. + + +maxdirs equ 256 ; Maximum number of names in directory buffer + +nosys equ yes ; suppress 'A' and 'S' options if wheel off + +slashchk equ yes ; allow "DIR /S" or "DIR /A" formats (costs + ; about 12 bytes) + +allflag equ 'A' ; Option character for showing + ; all file (both SYS and DIR) +sysflag equ 'S' ; Option character for showing + ; SYS files only +sortnt equ yes ; Sort file by name then type +wide equ yes ; Make display for 80-column screen +fence equ '|' ; Character to use as fence between columns + ; ..if WIDE is false. + +;----------------------------------------------------------------------------- + +; 'ECHO' command + +; This command sends characters to the console or list device. If ECHOLST is +; enabled, then code is included for sending characters to the LST device. +; Characters normally go the the console device and are normally upper or lower +; case depending on the setting of UPCASE. The command line can have special +; command sequences to toggle the case of the output and to change the +; destination between the console and printer. Any other character following +; the command character will be sent as is. For the normal setting of the +; equates below (upcase/yes, cmdchar/%, prtchar/P, crtchar/C, lcasech/>, +; and ucasech/<) an example command line would be: +; A0:ROOT>ECHO T%>HIS IS A TEST^M^J^IDONE%PONE, TWO, %' ; Character after CASECHAR that toggles + ; subsequent output to lower case + +; ---------------------------------------------------------------------------- + +; 'H' command + +; This command displays a list of the resident commands implemented in the +; system. FCP, CPR, and RCP commands can be displayed. The basic command is +; not optional; it is always included. + +listcpr equ yes ; Include list of CPR-resident commands in + ; display (highly recommended) + +listfcp equ yes ; Include list of FCP-resident commands in + ; display (recommended if there is room) + +noshow equ yes ; Suppress listing commands that are wheel- + ; restricted unless wheel byte is set (i.e., + ; don't show commands that won't run) + +cmdsline equ 5 ; Number of commands on each line of display + +cmdspace equ 8 ; Space in display for each command name + +;----------------------------------------------------------------------------- + +; 'LIST' and 'TYPE' commands + +pgdflt equ yes ; Default to paging of console output + +pgdflg equ 'P' ; Character to toggle paging status + +;---------------------------------------- + +; 'PEEK' command + +peekhdr equ yes ; Label columns in PEEK display + +peekbdr equ yes ; Include line of hyphens under labels + +peekchk equ yes ; If yes, the ending address will be tested to + ; prevent overflow past FFFF (costs 5 bytes) + +peeklen equ 127 ; Display 1 record (128 bytes) as default. + ; PEEK displays in increments of 16 bytes. + ; You may set PEEKLEN for 0 to 65525. + +;---------------------------------------- + +; 'S' command + +; Print disk space with other commands (recommended) + +dirsp equ true ; Show space after DIR +erasp equ true ; Show space after ERA +cpsp equ true ; Show space after CP +resetsp equ true ; Show space after R + +;---------------------------------------- + +; 'WHL' command + +whlquiet equ false ; If true, don't report wheel state with + ; .."WHL password" command (costs 1 byte) + +wpass macro + db 'SYSTEM ' + endm + +;---------------------------------------- + +; 'CLED' command +; +; Command line editing takes place in the line buffer. It does not take up +; room in the RCP. The editor will handle a 255 byte line. Although that +; is larger than the maximum command line size, it allows you to edit a long +; line without deleting something first. The history is kept inside the RCP, +; so its size will depend upon how many other RCP commands are enabled. +; HISTSIZE should be at least as large as the multiple command line buffer +; (204 bytes). + +lbufwid equ 255 ; Line buffer size, byte +histsize equ 204 ; History buffer size, word + +; CLERA controls whether to null the internal history when CLED is exited, or +; to leave it for the next run. Secure systems will want to set it to YES. + +clera equ no ; Erase history on terminating shell + +; CLMIN sets the cutoff for discarding short lines from history recording. +; A setting of 3 would discard a command such as DIR. + +clmin equ 5 ; Discard lines which do not exceed this + +clins equ yes ; Initial insert state ON +clsav equ yes ; Initial history recording ON + +; The appearance of the command line prompt may include the system time if a +; DateStamper or ZS/ZDDOS clock is implemented. The DU:DIR separator character +; can be changed to indicate that CLED is running. + +dudir_sep equ '/' ; DU:DIR separator char +puser0 equ no ; Print user #0 in prompt +systime equ yes ; Include system time in prompt + + if systime +civtim equ yes ; Yes for 12-hr civil time vs. 24-hr military +timesep equ '.' ; Separator between hours and minutes + endif + +; The editor must use EREOL. The VLIB routine to send the EREOL sequence +; to the terminal needs to test for a video delay and the presence of the "/" +; quote character. Most terminals can just print the TCAP sequence as is. If +; that doesn't work, set ERLQUICK to NO. If you are using only one terminal, +; it will be most efficient to set ERLTCAP to NO and use the CLR_EOL macro. + +erltcap equ yes ; Use TCAP routine vs macro + + if erltcap +erlquick equ yes ; Use simplified TCAP routine + else + +clr_eol macro ; Define terminal specific string + dc 1bh,'T' ; ...set high bit on last character + endm + + endif ;erltcap + +;----------------------------------------------------------------------------- + +; Command names + +; The CTABLE macro, which constructs the command dispatch table, includes a +; line for each command, the first parameter of which is the name of the +; command. These names may be changed if you wish. But make sure that you +; do not use a name that is longer than the maximum allowed length. If you +; do, the name will be truncated and a nonfatal error will occur during +; assembly. DO NOT CHANGE ANY PARAMETER OTHER THAN THE COMMAND NAME. Lower +; case letters will be converted to upper case. The macro COMMAND is defined +; in Z34MAC.LIB. + +cmdsize equ 4 ; Length of each command name + +cmdtbl macro + command CLED, cledon, wcled, cled + command CLS, clson, false, cls + command CP, cpon, wcp, copy + command D, diron, wdir, dir + command ECHO, echoon, false, echo + command ERA, eraon, wera, era + command H, true, whrc, clist + command LIST, liston, wlist, list + command NOTE, noteon, false, note + command P, peekon, wpeek, peek + command POKE, pokeon, wpoke, poke + command PORT, porton, wport, port + command PROT, proton, wprot, att + command Q, quieton,wquiet, quset + command R, reson, false, reset + command REG, regon, wreg, register + command REN, renon, wren, ren + command SP, spaceon,false, space + command SPOP, cledon, wspop, shpop + command TYPE, lton, wtype, type + command WHL, whlon, false, whl + endm + +; END Z34RCP.LIB + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/z34rcp.lib.sav b/Source/BPBIOS/Z34RCP11/z34rcp.lib.sav new file mode 100644 index 00000000..a0272445 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/z34rcp.lib.sav @@ -0,0 +1,515 @@ + +; File: Z34RCP.LIB +; Description: Resident Command Package (RCP) for ZCPR34 +; Version: 1.4 +; Author: Carson Wilson +; Date: October 4, 1989 +; Changes: Added PUSER0 equate suggested by Rob Friefeld. +; +; Version: 1.3 +; Author: Carson Wilson +; Date: September 29, 1989 +; Changes: Added CLED (Command Line Editor Shell) command options +; and equates. +; +; Version: 1.2 +; Author: Carson Wilson +; Date: August 11, 1989 +; Changes: Removed several "quiet" conditional assembly equates as +; we are now sensing the QUIET flag instead. +; Changed default of WHLQUIET to false. +; +; Version: 1.1 +; Author: Carson Wilson +; Date: July 10, 1989 +; Changes: Changed 'S' command name to 'SP'. +; Removed wheel protection from 'Q' command and set QQUIET +; on by default. +; Changed command order. + +; Version: 1.0 +; Author: Carson Wilson +; Date: June 15, 1988 + +; This is the configuration file for the ZCPR Version 3.4 resident command +; package. You should read through this file and set the equates according +; to the features you want to implement. Since most systems have a limited +; amount of space for the resident command package, it will probably be +; impossible to include all features. + +; If you are using an SLR or equivalent assembler, you will be prompted +; at assembly time for which commands to include in the RCP. + +;============================================================================= +; +; A S S E M B L Y O P T I O N S +; +;============================================================================= + +; SLR controls whether special pseudo-ops such as ".printx" and ".accept", +; and complex macros are used at assembly time. For SLR Systems or compatible +; macro assemblers, set this equate to true. + +SLR equ true + +; RCPTYPE is the final character of the RCP name in the H(elp) command, +; and determines the order in which commands are added. + + if SLR +Y equ true +N equ false +H equ 'H' +F equ 'F' + +.accept 'Enter "F" for floppy, "H" for fixed disk version: ',rcptype + else +rcptype equ 'F' ; Floppy disk version + endif ; SLR + +; ZRL controls whether named common labels are used in the code. To create +; absolute code, modify RCPBASE.LIB to reflect your system's addresses, set +; ZRL to false and instruct your assembler to generate absolute code (either +; a .COM or .HEX file). +; +; To create a relocatable file for use with Bridger Mitchell's JetLDR or +; Joe Wright's NZCOM, you must use an assembler which recognizes named +; common blocks. If your assembler has this capability, set ZRL to true +; and instruct the assembler to generate relocatable (.REL) code. + +ZRL equ true + +;============================================================================= +; +; M A C R O S +; +;============================================================================= + +; SLR assemblers allow selection of options during assembly. + + if SLR + +select macro option default comment + .accept comment,option ; Prompt user for selection + endm + + .printx + .printx Answer "Y" to include, "N" to exclude commands: + .printx + + else + +select macro option default comment +option equ default ; Use selections from file + endm + endif ; SLR + +;============================================================================= +; +; R E S I D E N T C O M M A N D S S E L E C T I O N +; +;============================================================================= + +; Commands to include in the resident command package + +; There are some interrelations between a number of the possible resident +; commands. Some are so close (LIST and TYPE) that a single equate controls +; both functions. Others like DIR and ERA share code (both display file +; listings). It is efficient to select or deselect them together. + +; If you are using an SLR or compatible assembler, you will be prompted +; at assembly time for command selections. Otherwise, select commands to +; be included by setting each SELECT macro below to yes or no. + +select CLEDON no 'CLED - command line editor shell? ' +select CLSON yes 'CLS - clear screen? ' +select RESON yes 'R - reset disk system? ' +select SPACEON yes 'SP - show space remaining on disk? ' + + if RCPTYPE='F' ; Assemble in floppy order +select DIRON yes 'D - disk directory? ' +select ERAON yes 'ERA - erase files? ' +select ECHOON yes 'ECHO - send text to screen or printer? ' +select RENON yes 'REN - rename files? ' +select CPON yes 'CP - copy file? ' +select LTON yes 'TYPE - display file on console? ' +select POKEON yes 'POKE - set memory? ' +select PEEKON yes 'PEEK - view memory? ' + else ; Assemble in fixed-disk order +select ECHOON yes 'ECHO - send text to screen or printer? ' +select POKEON yes 'POKE - set memory? ' +select PEEKON yes 'PEEK - view memory? ' +select QUIETON yes 'Q - set or clear quiet status? ' +select DIRON yes 'D - disk directory? ' +select ERAON yes 'ERA - erase files? ' +select RENON yes 'REN - rename files? ' +select CPON yes 'CP - copy file? ' +select LTON yes 'TYPE - display file on console? ' + endif ; RCPTYPE='F' + + if LTON +select LISTON no 'LIST - list to printer? ' + else ; allowed only if TYPE is enabled +LISTON equ no + endif + + if RCPTYPE='F' ; Assemble in floppy order +select QUIETON yes 'Q - set or clear quiet status? ' + endif + +select PROTON no 'PROT - set file attributes? ' +select NOTEON no 'NOTE - command-line comment? ' +select REGON no 'REG - set and display user registers? ' +select WHLON no 'WHL - set or clear wheel status? ' +select PORTON no 'PORT - view and set I/O ports? ' + +;============================================================================= +; +; W H E E L P R O T E C T I O N +; +;============================================================================= + +; To prevent unauthorized users from performing certain dangerous or sensitive +; operations on the computer, ZCPR34 provides the capability of disabling the +; operation of certain commands when the wheel byte is off. In ZCPR30, an +; attempt to use one of these wheel-protected commands when the wheel byte was +; off resulted in an error message. In ZCPR34 things work differently. In the +; same situation, the command simply disappears. In this way a transient +; program or extended command processor function can take over and deal with +; the attempt to use the command in a much more flexible way. +; +; Wheel-protected commands in ZCPR30 had extra code to intercept the function +; and disable it. In ZCPR34, wheel protection is enabled and disabled in a +; different way. To wheel-protect a command the high bit of the first +; character in the command name is set. The command table scanner in ZCPR34 +; will not recognize these commands when the wheel byte is off. Since the same +; command scanner is used to scan the commands in the RCP and FCP (flow control +; package), commands there can be wheel protected in the same way. For skilled +; computer operators it is very easy to use a debugger, file patcher, or disk +; utility to enable and disable wheel protection without having to reassemble +; the CPR, RCP, or FCP. +; +; Because of the way the command scanner works, once wheel protection is +; implemented in the CCP, there is no further code penalty in protecting RCP +; commands. Therefore, we recommend protecting all possibly sensitive +; commands or none. + +wcled equ no ; CLED +wcp equ yes ; CP +wdir equ no ; DIR +wera equ yes ; ERA +wlist equ yes ; LIST +wpeek equ yes ; PEEK +wpoke equ yes ; POKE +wport equ yes ; PORT +wprot equ yes ; PROT +wquiet equ no ; QUIET +wreg equ yes ; REG +wren equ yes ; REN +wspop equ yes ; SPOP +wtype equ yes ; TYPE +whrc equ no ; H + +wheel defl wcp or wdir or wera or wlist or wpeek or wpoke or wport +wheel defl wheel or wprot or wreg or wren or wtype or whrc + +;============================================================================= +; +; C O M M A N D O P T I O N S +; +;============================================================================= + +; Options affecting several commands + +; DSORT includes code to sort file entries for the D, TYPE, LIST, ERA, +; and PROT commands. + +dsort equ yes + +; LEFTRIGHT affects the COPY and RENAME commands. If yes, add code to allow +; CP and REN operations work from left to right, and CP to accept a +; single filespec as its source. +; Examples: +; B0>ren b1:old.nam new.nam Renames B1:old.nam to B1:new.nam +; B0>cp a15:that.fil Copies A15:that.fil to B0: +; B0>cp source.fil dest.fil Copies source.fil to dest.fil + +leftright equ yes + +; If LEFTRIGHT is yes, setting TESTEQ to yes also allows CP and REN commands +; to be processed from right to left if they contain an equal sign ("="). +; Costs 11 bytes. +; Example: +; B0>cp dest.fil=source.fil Copies source.fil to dest.fil + +testeq equ yes + +;----------------------------------------------------------------------------- + +; 'CLS' command + +; This command clears the console screen. It can either use a fixed string +; (for shorter code if the same terminal is always used) or use the TCAP +; entry for automatic adaptation to any terminal. + +clstcap equ true ; Use TCAP for clear-screen string + +; If CLSTCAP is not enabled, then the string below must be provided and +; it must end with a character with the high bit set + + if not clstcap +clsstr macro + db 'Z'-'@'+80h ; Control-Z to clear screen + endm + endif ;not clstcap + +typecls equ true ; Clear between screens when typing files + +;----------------------------------------------------------------------------- + +; 'CP' command + +; This command copies a single file. The destination file can have a different +; name from the source file. Set the size of the memory buffer in K below (do +; not change the second definition). The copy buffer is placed at the top of +; the TPA, where it will generally not interfere with subsequent use of the GO +; command to rerun a program loaded in the TPA. + +cpblocks defl 16 ; Size of copy buffer in K + +cpblocks defl cpblocks * 8 ; Convert to number of records + +; If you will be using a BDOS or RSX which supports new functions 102 (Get +; Stamp) and 103 (Set Stamp), set STPCALL to true, and datestamps of files +; will be preserved across copies. The "last access" stamps of both copies +; are set to the time the copy took place if access stamping is enabled. + +stpcall equ true ; Copy file stamps with functions 102/3 + +;----------------------------------------------------------------------------- + +; 'DIR' command + +; This command displays a sorted listing of the files in a directory. The +; file name buffer is allocated at the top of the TPA so as not to modify +; any program loaded in low memory. Consequently, the size of the buffer +; must be set in advance. + + +maxdirs equ 256 ; Maximum number of names in directory buffer + +nosys equ yes ; suppress 'A' and 'S' options if wheel off + +slashchk equ yes ; allow "DIR /S" or "DIR /A" formats (costs + ; about 12 bytes) + +allflag equ 'A' ; Option character for showing + ; all file (both SYS and DIR) +sysflag equ 'S' ; Option character for showing + ; SYS files only +sortnt equ yes ; Sort file by name then type +wide equ yes ; Make display for 80-column screen +fence equ '|' ; Character to use as fence between columns + ; ..if WIDE is false. + +;----------------------------------------------------------------------------- + +; 'ECHO' command + +; This command sends characters to the console or list device. If ECHOLST is +; enabled, then code is included for sending characters to the LST device. +; Characters normally go the the console device and are normally upper or lower +; case depending on the setting of UPCASE. The command line can have special +; command sequences to toggle the case of the output and to change the +; destination between the console and printer. Any other character following +; the command character will be sent as is. For the normal setting of the +; equates below (upcase/yes, cmdchar/%, prtchar/P, crtchar/C, lcasech/>, +; and ucasech/<) an example command line would be: +; A0:ROOT>ECHO T%>HIS IS A TEST^M^J^IDONE%PONE, TWO, %' ; Character after CASECHAR that toggles + ; subsequent output to lower case + +; ---------------------------------------------------------------------------- + +; 'H' command + +; This command displays a list of the resident commands implemented in the +; system. FCP, CPR, and RCP commands can be displayed. The basic command is +; not optional; it is always included. + +listcpr equ yes ; Include list of CPR-resident commands in + ; display (highly recommended) + +listfcp equ yes ; Include list of FCP-resident commands in + ; display (recommended if there is room) + +noshow equ yes ; Suppress listing commands that are wheel- + ; restricted unless wheel byte is set (i.e., + ; don't show commands that won't run) + +cmdsline equ 5 ; Number of commands on each line of display + +cmdspace equ 8 ; Space in display for each command name + +;----------------------------------------------------------------------------- + +; 'LIST' and 'TYPE' commands + +pgdflt equ yes ; Default to paging of console output + +pgdflg equ 'P' ; Character to toggle paging status + +;---------------------------------------- + +; 'PEEK' command + +peekhdr equ yes ; Label columns in PEEK display + +peekbdr equ yes ; Include line of hyphens under labels + +peekchk equ yes ; If yes, the ending address will be tested to + ; prevent overflow past FFFF (costs 5 bytes) + +peeklen equ 127 ; Display 1 record (128 bytes) as default. + ; PEEK displays in increments of 16 bytes. + ; You may set PEEKLEN for 0 to 65525. + +;---------------------------------------- + +; 'S' command + +; Print disk space with other commands (recommended) + +dirsp equ true ; Show space after DIR +erasp equ true ; Show space after ERA +cpsp equ true ; Show space after CP +resetsp equ true ; Show space after R + +;---------------------------------------- + +; 'WHL' command + +whlquiet equ false ; If true, don't report wheel state with + ; .."WHL password" command (costs 1 byte) + +wpass macro + db 'SYSTEM ' + endm + +;---------------------------------------- + +; 'CLED' command +; +; Command line editing takes place in the line buffer. It does not take up +; room in the RCP. The editor will handle a 255 byte line. Although that +; is larger than the maximum command line size, it allows you to edit a long +; line without deleting something first. The history is kept inside the RCP, +; so its size will depend upon how many other RCP commands are enabled. +; HISTSIZE should be at least as large as the multiple command line buffer +; (204 bytes). + +lbufwid equ 255 ; Line buffer size, byte +histsize equ 204 ; History buffer size, word + +; CLERA controls whether to null the internal history when CLED is exited, or +; to leave it for the next run. Secure systems will want to set it to YES. + +clera equ no ; Erase history on terminating shell + +; CLMIN sets the cutoff for discarding short lines from history recording. +; A setting of 3 would discard a command such as DIR. + +clmin equ 5 ; Discard lines which do not exceed this + +clins equ yes ; Initial insert state ON +clsav equ yes ; Initial history recording ON + +; The appearance of the command line prompt may include the system time if a +; DateStamper or ZS/ZDDOS clock is implemented. The DU:DIR separator character +; can be changed to indicate that CLED is running. + +dudir_sep equ '/' ; DU:DIR separator char +puser0 equ no ; Print user #0 in prompt +systime equ yes ; Include system time in prompt + + if systime +civtim equ yes ; Yes for 12-hr civil time vs. 24-hr military +timesep equ '.' ; Separator between hours and minutes + endif + +; The editor must use EREOL. The VLIB routine to send the EREOL sequence +; to the terminal needs to test for a video delay and the presence of the "/" +; quote character. Most terminals can just print the TCAP sequence as is. If +; that doesn't work, set ERLQUICK to NO. If you are using only one terminal, +; it will be most efficient to set ERLTCAP to NO and use the CLR_EOL macro. + +erltcap equ yes ; Use TCAP routine vs macro + + if erltcap +erlquick equ yes ; Use simplified TCAP routine + else + +clr_eol macro ; Define terminal specific string + dc 1bh,'T' ; ...set high bit on last character + endm + + endif ;erltcap + +;----------------------------------------------------------------------------- + +; Command names + +; The CTABLE macro, which constructs the command dispatch table, includes a +; line for each command, the first parameter of which is the name of the +; command. These names may be changed if you wish. But make sure that you +; do not use a name that is longer than the maximum allowed length. If you +; do, the name will be truncated and a nonfatal error will occur during +; assembly. DO NOT CHANGE ANY PARAMETER OTHER THAN THE COMMAND NAME. Lower +; case letters will be converted to upper case. The macro COMMAND is defined +; in Z34MAC.LIB. + +cmdsize equ 4 ; Length of each command name + +cmdtbl macro + command CLED, cledon, wcled, cled + command CLS, clson, false, cls + command CP, cpon, wcp, copy + command D, diron, wdir, dir + command ECHO, echoon, false, echo + command ERA, eraon, wera, era + command H, true, whrc, clist + command LIST, liston, wlist, list + command NOTE, noteon, false, note + command P, peekon, wpeek, peek + command POKE, pokeon, wpoke, poke + command PORT, porton, wport, port + command PROT, proton, wprot, att + command Q, quieton,wquiet, quset + command R, reson, false, reset + command REG, regon, wreg, register + command REN, renon, wren, ren + command SP, spaceon,false, space + command SPOP, cledon, wspop, shpop + command TYPE, lton, wtype, type + command WHL, whlon, false, whl + endm + +; END Z34RCP.LIB + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/z34rcp11.doc b/Source/BPBIOS/Z34RCP11/z34rcp11.doc new file mode 100644 index 00000000..92195c5d --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/z34rcp11.doc @@ -0,0 +1,429 @@ + + ZCPR Version 3.4 Resident Command Package + Source Code + + Revised Documentation for Version 1.1 + October 7, 1989 by Carson Wilson + + Version 1.0 Documentation + June 17, 1988 by Carson Wilson + + + Contents + -------- + + 1. Files in this Library. + + 2. Purpose of the Resident Command Package. + + 2.1. "Resident" vs. "Transient" Commands. + + 2.2. The Z-System Resident Command Package. + + 2.2.1. Design Philosophy of the Z34 RCP. + + 3. Structure and Operation of the RCP Segment. + + 4. Generating an RCP for your System. + + 4.1. The Z-Relocatable Approach. + + 4.1.1. NZCOM and JetLDR. + 4.1.2. Z-Relocatable (ZRL) Code + + 4.2. The Assembly Approach. + + 4.2.1. Note on Assemblers. + 4.2.2. Assembly to Absolute Code. + 4.2.3. Assembly to Z-Relocatable Code. + 4.2.4. Adding Custom Commands to Z34RCP. + + 5. Legal Use of These Files. + + + +1. Files in this Library. + +This library of files contains the assembler source code and +accompanying documentation for the Z-System Resident Command +Package (RCP). To use Z34RCP you must be running ZCPR versions +3.3 or above. For pre-assembled Z-Relocatable (ZRL) RCPs, see +RCPZRL11.LBR. Z34RCP11.LBR consists of the following files +(uncompress with UNCR.COM, QL.COM or LBRE.COM): + +CLEDINST.CZM - Installer for CLED command. +CLEDSAVE.CZM - Command file utility for CLED. +RCPCLED .DZC - Documentation on CLED. +RCPBASE .LZB - Used to assemble an absolute address RCP. +RCPCMD .LZB - Used to include custom commands in the RCP. +RCP?????.LZB - Code modules for the standard RCP commands. +SYSDEF .LZB - Commonly used equates. +Z34CMN .LZB - Used to assemble Z-Relocatable RCP's. +Z34DEFN .LZB - Offsets in the ZCPR 3.4 code. +Z34MAC .LZB - Assembler macros. +Z34RCP11.DZC - This file. +Z34RCP11.FOR - Short description of the library. +Z34RCP11.HZP - Help file for the RCP, gives command syntax and + examples. +Z34RCP .LZB - Used to select features when assembling the RCP. +Z34RCP11.NZW - Changes since Z34RCP10.LBR. Intended for former + RCP users, this will be only of passing interest + to new users. +Z34RCP11.ZZ0 - Main file for assembling the RCP. +ZSYSTEM .AZ - General information about Z System. + + +2. Purpose of the Resident Command Package. + + 2.1. "Resident" vs. "Transient" Commands. + +Most of the commands used under CP/M and Z-System are "transient" +commands. The machine code for these commands (or "programs") is +stored in "command files," whose file type is normally .COM. +Thus, the machine code for WordStar is stored in a file called +"WS.COM". + +To run WordStar, the user issues the command "WS" at the command +line, the operating system loads WS.COM from the disk drive to +memory, and WS.COM takes over control of the computer. If the +user then exits WordStar and issues another command, the +operating system can load another command file from the disk, +reusing the area of memory previously occupied by WordStar. + +Recycling memory in this way makes personal computers quite +versatile, as the number of commands available to the user is +only limited to the number of command files stored on disk. +There are some drawbacks to this approach, however. Although +disk-based commands conserve memory by recycling it, they must be +loaded from disk each time they operate. This creates its own +costs in terms of speed of execution, disk storage space, and +memory reallocation. + +The most obvious disadvantage of loading a file from disk each +time a command is issued is that it takes time to find and load +disk files. Especially with floppy diskettes, the amount of time +which elapses after the command is issued and before the command +executes is significant. A second drawback to disk-based +commands is that commands will not execute unless their machine +code is available on the disk system. This means that the user +must know what command files are available on disk before issuing +commands. The final disadvantage of disk file based commands is +that some commands (e.g., SAVE) require that the area of memory +normally overwritten by command files (known as the "Transient +Program Area" or TPA) be preserved. + +In answer to these difficulties, the creators of CP/M designed +some of its commands as "built-in" commands. The DIR, REN, SAVE, +ERA, USER, and TYPE commands of CP/M 2.2 remain in memory and can +be issued without loading machine code files from disk. Thus, +the DIR command provides a disk directory regardless of whether +the command file "DIR.COM" is present on disk, and the SAVE +command saves the contents of memory at 100 hex to a disk file. +Residing permanently in memory, these commands are simple in +order to leave memory for disk-based commands to load in. + + 2.2. The Z-System Resident Command Package. + +The creators of Z-System have followed the resident command +philosophy of CP/M, extending and enhancing the capabilities of +resident commands with the Z-System Resident Command Package. +Under CP/M 2.2, resident commands are tightly integrated with the +operating system. The user is therefore limited to a fixed set +of resident commands. The code in the Z-System Resident Command +Package is much more loosely linked with the operating system. + +The RCP occupies a section of protected memory, but how that +memory is used is only very loosely constrained. The Z-System +resident command package may be tailored for a wide variety of +installations, and easily altered within a single installation. + + 2.2.1. Design Philosophy of the Z34 RCP. + +Though any set of commands which will fit in the RCP memory area +can be loaded as an RCP, a standard set of Z-System RCP commands +have evolved over the years. Distributed as the standard +Z-System RCP, this set of commands consists of universal, +single-purpose commands modeled after the resident command set of +CP/M 2.2, such as DIR, REN, and ERA. + +Keeping these commands simple both conserves memory and allows +their use in custom command scripts. None of the standard RCP +commands require interaction on the part of the user--they +perform a single function and quit. Aliases or other command +scripts which build complex commands from simple RCP commands +therefore retain complete control over the user interface. + +However, the standard RCP forms only one of many alternative uses +to which the Z-System RCP memory segment may be put. Since the +RCP memory is protected, it can be used by background programs +which continue to function even while other applications have +been loaded into the transient program area. Several such +programs, including key-redefiners, screen-trapping programs, +background numeric calculators, program assembly environments, +and memory utilities have been developed. + +Until Z34RCP, all but the most advanced users had to surrender +their entire RCP to use the custom commands, even if the commands +needed far less than the available RCP space. Thus, to use a +key-redefinition RCP, one was forced to give up all of his other +RCP commands. Z34RCP solves this problem by acting as a base to +which programmers may easily add code. By modifying the standard +file RCPCMD.LIB, programmers can implement and distribute custom +RCP commands which may be installed by those with only basic +knowledge of assembly language. See "Adding Custom Commands," +below, for details. + + +3. Structure and Operation of the RCP Segment. + +Typically, the Resident Command Package occupies 2 kilobytes of +memory, protected above the system's Basic Input Output System +(BIOS). Its internal structure is very simple, consisting of the +five byte string "Z3RCP", a table of command names and their +addresses within the RCP, and the machine code of the commands +themselves. This means that an RCP of a given length can contain +any number of resident commands which will fit in 2k, from one +large program to many shorter programs. + +Further, since the RCP is independent of the rest of the +operating system, the resident commands may be changed at will. +On system startup, a default RCP is loaded into memory. +Thereafter the RCP's commands remain active until the system is +turned off or another RCP is loaded. + +Using loader programs to load the RCP to memory allows another +important ability--it can exchange RCPs during a single session. +By allowing users to change their RCPs "on-the-fly," loader +programs provide some of the benefits of transient commands even +while preserving the benefits of resident commands. In fact, +even what portion of memory is allocated to the RCP can be +altered in this way by advanced users! (see the Z-System User's +Manual for more on this). + + +4. Generating an RCP for your System. + +Since Z80 computers come with a wide variety of operating system +environments, it is impossible for a single segment of machine +code function as a universal RCP. Due to differences in +operating system and hardware design, the size and location of +memory available to the Z-System RCP vary widely. Until +Z34RCP, this meant that each type of Z80 computer required an RCP +segment assembled specifically for that computer. + +Now, however, there are two basic means by which you may generate +an RCP for your system. The simplest route is to load already- +assembled Z-Relocatable code modules. This technique is +recommended for beginners and those with little experience in +assembly language. See RCPZRL11.LBR for pre-assembled RCP +modules. For more experienced users with an interest in +customizing or their RCPs or adding their own commands, custom +assembly of the files in this library is also an option. + + 4.1. The Z-Relocatable Approach. + + 4.1.1. NZCOM and JetLDR. + +LDR.COM, the familiar Z-System segment loader first introduced by +Rick Conn as a means of loading the various segments of the ZCPR +environment from disk to memory, required that system segments be +assembled to run only at system-specific addresses. For example, +if the system's RCP started at FA00 hex, only an RCP assembled to +run at that starting address would function on the system. +Therefore, separate SYS.RCP files were needed for each computer +installation. + +Thanks to several advanced loader utilities, namely NZCOM by Joe +Wright and JetLDR and Z3PLUS by Bridger Mitchell, custom- +assembling an RCP for a given target machine is no longer +necessary. These three programs can load specially assembled RCP +segments (known as Z-ReLocatable or "ZRL" segments) to any area +of memory. Z-System users now need only obtain previously +assembled RCP segments, much as they need only obtain assembled +.COM or .OBJ files to run transient programs. + +Using information in the system's environmental descriptor, +advanced loaders load pre-assembled segments to the proper +addresses in any system. The only remaining constraint is the +amount of memory allocated to the RCP. While the standard RCP +size is 2 kilobytes, many Z-System installations vary +considerably from this standard, so it is still necessary to +ensure that a ZRL segment does not exceed available RCP memory. +Fortunately, this is taken care of automatically by the advanced +loader programs. + +Using NZCOM or JetLDR, it is possible to load an RCP directly +from RCPZRL11.LBR. See NZCOM or JetLDR documentation for full +instructions on how to load Z-Relocatable RCPs with these +programs. + + 4.1.2. Z-Relocatable (ZRL) Code + +The technique used by NZCOM and JetLDR to load a single RCP to +any address in memory involves relocatable code. Relocatable +code has been used for years by the "linker" programs available +to assembly language programmers. Since only portions of a given +Z80 machine code routine are specific to one memory address, it +is possible to create "relocatable" files of Z80 routines (known +by their file type .REL) containing all but the address-specific +codes. These files can then be very quickly relocated (or +"linked") by a linker program to operate at any memory address. + +Z-Relocatable code is simply a more sophisticated form of +relocatable code. ZRL files contain separately labeled segments, +known as "named common blocks." Where normal relocatable code is +all linked to operate at one address at a time by the linker +program, named common blocks allow the linker to distinguish +between different sections of the file and to link each "block" +to operate at a different address. + +This represents a major step forwards in user convenience, and +will certainly lead to more innovative work using the ZCPR system +segments. There is a minor drawback, however, inherent in the +form of assembly required by NZCOM and JetLDR. Before the advent +of these two programs, system segments were equivalent to .COM +files which were assembled to operate at addresses other than the +standard 100 hex. The ability to load a single system segment to +any address depends partially on the form of the assembled system +segment itself. + +While public domain assemblers will produce absolute object +files, more sophisticated assemblers, such as SLR's Z80ASM (a +bargain at $50) are required to assemble code with named common +addresses. NZCOM and JetLDR thus greatly reduce the time needed +by most users to install Z-System segments, but those wishing to +assemble Z-Relocatable segments will probably need a commercial +assembler. + + 4.2. The Assembly Approach. + +Those with knowledge of assembly language may opt to assemble +their own custom RCPs, including only the commands and options +which match their exact requirements. Z34RCP.LBR contains the +complete source to the ZCPR 3.4 RCP. The file Z34RCP.Z80 +contains include statements to combine the various library files +during assembly. The file Z34RCP.LIB contains all of the equates +controlling assembly time options, and should be edited before +assembling Z34RCP.Z80. + + 4.2.1. Note on Assemblers. + +The development work on Z34RCP was done exclusively with the SLR +Systems line of Z80 assemblers. Hence, some of the source code +is unavoidably specific to SLR and compatible assemblers. Those +using incompatible assemblers such as public-domain Z80 +assemblers should set the SLR equate in Z34RCP.LIB to FALSE. +This removes most SLR-specific pseudo-ops, but some additional +editing of the source files may be required for error-free +assembly. Unless your assembler can handle named common blocks, +you will also want to set the ZRL equate in Z34RCP.LIB to FALSE. + + 4.2.2. Assembly to Absolute Code. + +If you are assembling a custom RCP for use at one memory address +only, or if your assembler can't handle named common blocks, you +can set the ZRL equate in Z34RCP.LIB to FALSE, and instruct your +assembler to produce absolute code (either a .HEX or a .COM file). + +Before generating absolute code you must set addresses in the +file RCPBASE.LIB for the particular installation. Once you have +set these addresses, you may proceed to assemble Z34RCP.Z80 to +either Z34RCP.COM or Z34RCP.HEX. If your assembler generated a +.HEX file, use LOAD.COM to convert Z34RCP.HEX to Z34RCP.COM. + +If you are running a version of ZCPR prior to 3.4, you must now +alter two bytes in your Z-System environment segment to point to +your system's CCP address. The bytes at offset 39 and 40 hex +(formerly containing "width of printer 2" and "lines on printer +2") should contain a 2-byte address of your system's CCP, most +significant byte last. + +Z34RCP.COM may now be renamed to SYS.RCP and loaded with the Z- +System loader program LDR.COM or Bridger Mitchell's JetLDR. + + 4.2.3. Assembly to Z-Relocatable Code. + +Unlike absolute RCP segments, Z-Relocatable segments may be +loaded to ANY address, provided enough RCP space to contain them +is available. To assemble a Z-Relocatable RCP, you will need an +assembler which can handle named common blocks (see 4.2.1 above). +Set the ZRL equate in Z34RCP.LIB to TRUE, and instruct your +assembler to produce a relocatable (.REL) file. The resulting +Z34RCP.REL may then be renamed to Z34RCP.ZRL and loaded with +NZCOM, Z3PLUS, JetLDR. + + 4.2.4. Adding Custom Commands to Z34RCP. + +As mentioned above, I have designed Z34RCP as a base to which +custom RCP commands may readily be added. All code and command +names for custom commands may be included in RCPCMD.LIB. +Thereafter, whenever the RCP is assembled the commands added to +RCPCMD.LIB will be included automatically. To remove the custom +commands, just replace the modified RCPCMD.LIB with an unmodified +copy. See RCPCMD.LIB for detailed instructions on how to add +code and command names. + +RCPCMD.LIB also facilitates distribution of custom commands to be +included by others in their RCPs. Simply distribute a copy of +your modified RCPCMD.LIB along with instructions on how to use +your custom commands. Users may then easily include the new +commands by substituting your modified RCPCMD.LIB for their +original copy and reassembling their RCPs. + +Of course, users of NZCOM, Z3PLUS, or JetLDR may simply trade +custom RCPs in Z-Relocatable form and dispense with the need for +any assembly work on the part of the user. All three of these +advanced loaders are available from Z Systems Associates (see +below). + + +5. Legal Use of These Files. + +Z34RCP is copyright 1989 by Z Systems Associates (ZSA), all +rights reserved. Any commercial use of Z34RCP, defined as any +situation where the duplicator recieves revenue by duplicating or +distributing Z34RCP by itself or in conjunction with any hardware +or software product, is expressly prohibited unless authorized in +writing by ZSA. + +Except for the file RCPCMD.LIB (see 4.2.4 above), you may +redistribute Z34RCP.LBR in its present form only. I encourage +you to explore the source code, suggest improvements, and +document errors. However, please obtain permission from Z +Systems Associates before redistributing any of these files in +altered form. This will prevent confusion by allowing your work +to be coordinated with the efforts of others. + +The Z Systems Associates are: + + Sage Microsystems East + Selling & Supporting the Best in 8-Bit Software + 1435 Centre St., Newton Centre, MA 02159-2469 + Voice: 617/965-3552 (9:00am - 11:30pm) + Modem: 617/965-7259 (password = DDT)(MABOS on PC-Pursuit) + +and: + + Plu*Perfect Systems + "==World-Class Software" + 410 23rd Street, Santa Monica, CA 90402 + Voice: 213/393-6105 (evenings) + Modem: 213/670-9465 (leave message for "Bridger Mitchell") + +For more information on Z System, check at the above bulletin +board systems, or consult The Computer Journal for excellent +articles on ZCPR and CP/M. The Computer Journal is published six +times a year by Publishing Consultants, 190 Sullivan Crossroad, +Columbia Falls, MT 59912, phone 406/257-9119. An issue averages +forty pages with few ads. Subscription rates are $16 for one +year (6 issues), or $28 for two years (12 issues) in the U.S., +$22 for one year Canada and Mexico, and $24 (surface) for one +year in other countries. + + +Carson Wilson is the operator of: + + Antelope Freeway Remote Access System + Chicago, 312-764-5162 + 300-1200-2400 Baud, 24 Hours + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/z34rcp11.for b/Source/BPBIOS/Z34RCP11/z34rcp11.for new file mode 100644 index 00000000..c60e1910 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/z34rcp11.for @@ -0,0 +1,7 @@ +Z-System Resident Command Package (RCP) in source code form, version +1.1. Complete Z80 source code to the RCP plus documentation, for +ZCPR versions 3.3 and above. Adds several exciting new commands and +features and fixes various bugs in the previous release. See +companion file RCPZRL11.LBR for pre-compiled RCP modules which may +be loaded with NZ-COM, Z3PLUS, or JetLDR. + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/z34rcp11.hlp b/Source/BPBIOS/Z34RCP11/z34rcp11.hlp new file mode 100644 index 00000000..991d5d71 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/z34rcp11.hlp @@ -0,0 +1,390 @@ + The RCP Commands + CLED/SPOP Command Line Editor  + CLS Clear the CRT screen  + CP Copy a File  + D Display Directory  + ECHO Echo Command Tail  + ERA Erase Files  + H Display Available Commands  + LIST/TYPE Display a File on Printer/CRT + NOTE Comment  + P/POKE Display/Alter Memory  + PORT Input/Output to system ports  + PROT Protect Files  + Q Alter or Query the Quiet Flag + R Reset Disk  + REG ZCPR3 Register Display/Alter  + REN Rename a File  + SP Display Disk Space Remaining  + WHL Alter or Query Wheel Byte  +:Thå RCÐ Commands + + + Thå   standarä ZCPR³ Systeí Residenô Commanä  Packagå  (RCP© +distributeä  witè thå ZCPR³ systeí containó á numbeò oæ  residenô +commandó  whicè  maù bå enableä (dependinç oî systeí  memorù  anä +securitù  characteristics©  aô thå discretioî oæ  thå  installer® +Usagå oæ thå RCÐ commandó ió describeä iî thió HELÐ file. + + Thå  RCÐ commandó residå iî memorù ratheò thaî beinç  loadeä +froí  disk¬ anä thereforå operatå verù efficientlù anä arå  disë­ +independent®   Oî thå otheò hand¬ duå tï memorù constraints¬  thå +RCÐ commandó arå ofteî lesó powerfuì thaî theiò transienô prograí +counterparts® + + + + Iæ aî erroò occurs¬ thå RCÐ commandó generallù chaiî tï  thå +installeä erroò handler¬ providinç á detaileä reporô oæ thå erroò +anä  allowinç thå commanä tï bå editeä oò aborted® Iæ  nï  erroò +handleò  ió  installed¬  thå commanä whicè causeä  thå  erroò  ió +echoeä tï thå screen¬ followeä bù á questioî mark. + + Alì RCÐ commandó arå installatioî-dependent¬ anä manù maù bå +configureä  aô  assemblù timå tï perforí  iî  slightlù  differenô +ways®   Thå  exampleó giveî iî thió HELÐ filå arå  baseä  oî  thå +distributeä seô oæ options® Seå sourcå codå filå Z34RCP.LI  foò +morå detailó oî commanä anä optioî selection. + +:CLED anä SPOÐ Commands + Transient Counterparts: LSÈ anä SPOP + + Thå  CLEÄ commanä invokeó á speciaì prograí  whicè  provideó +WordStaò-likå editinç oæ commandó aô thå systeí prompt® Iô  alsï +allowó yoõ tï recalì thå lasô severaì commandó invokeä witè  onlù +onå keystroke® Finally¬ iæ available¬ CLEÄ displayó thå  currenô +systeí timå tï thå lefô oæ youò prompt® Thå syntaø is: + + CLED - invoke as shell + CLED / - invoke for next command only + + + + Thå SPOÐ commanä "pops¢ thå toð shelì froí thå shelì  stack¬ +sï thaô iô nï longeò loadó afteò everù command® Thió commanä  ió +rarelù  needed¬  anä ió includeä onlù witè versionó  oæ  thå  RCÐ +includinç CLEÄ (seå above)® Thå syntaø ió simply: + + SPOP +:CLS Command + Transient Counterpart: None + + Thå  CLÓ  commanä  clearó  thå  terminaì  screen¬   removinç +displayó  whicè  mighô  havå beeî lefô bù  previouó  commandó  oò +programs® Thå syntaø is: + + CLS + +:CP Command + Transient Counterpart: MCOPY + + Thå  CÐ  commanä copieó onå filå froí onå DÕ tï  anotheò  oò +intï  thå samå DÕ undeò á differenô name® Iæ functionó 10²  (geô +stamp©  anä 10³ (seô stamp© arå supporteä bù aî RSØ oò DOS¬  filå +datestampó arå preserveä acrosó copies® Thå syntaø is: + + CP dir:ufn1 dir:ufn2 -- ufn1 to ufn2 + CP dir:ufn1 -- ufn1 to current dir: + CP dir:ufn2=dir:ufn1 -- ufn1 to ufn2 + + Examples: + + CP f1.txt f2.txt + CP b0:f1.txt a15: + CP root:f1.txt + +:D Command + Transient Counterpart: DIR, XD, XDIR + + Thå   Ä   commanä  displayó  thå  directorù  oæ   fileó   iî +alphabeticaì ordeò acrosó thå lineó tï thå user® Thå syntaø is: + + D dir:afn.aft o + D .aft o + D /o + + Options (wheel only) are: + + S - Display System Files Only + A - Display Both Non-System and System Files + + Examples: + + D /a + D root:myfile.txt Š D .com + +:ECHO Command + Transient Counterpart: ECHO + + ECHÏ   ió usefuì iî issuinç botè messageó (tï thå  user¬ +saù withiî á commanä filå durinç execution© anä escapå sequences® +Bù  uså oæ thå %¾ anä %¼ parameters¬ ECHÏ caî senä itó outpuô  iî +combinationó  oæ uppeò anä loweò case® Bù uså oæ thå %Ð  anä  %à +parameters¬  ECHÏ  outpuô  caî  bå  toggleä  betweeî  screeî  anä +printer®   ECHÏ  useó  BIOÓ  calls¬  sï  alì  controì  characteró +(entereä aó ^character© arå passeä exactly® Hence¬ consolå-leveì +programminç oæ sucè deviceó (CRTó anä printers© ió possible. + + Examples: + ECHO %Pthis is a test%Cof echo%P + -- "THIS IS A TEST" goes to the printer + "OF ECHO" goes to the console + (trailing %P flushes printer buffer) + + ECHO t%>his is a % + prtdec ,%recs,,%bytes, + prtdec ; Trailing CRLF + endm + + else ; Non-SLR assemblers +include macro filename + $include filename&.lib + endm + endif ; SLR + + + if ZRL + +; ============================================================================= + +; J e t L D R I D S E C T I O N + +; ============================================================================= + +; Macro to build ID block message for JetLDR. Max. length is ~256 chars. + +optcnt defl 0 ; Options counter + +option macro string1,enable1,string2,enable2 + + if enable1 ;; Skip if command not present + if [optcnt mod 4] eq 0 + db cr,lf ;; New line every four options + endif + optcnt defl optcnt + 1 ;; Increment options counter + count defl 0 ;; Initialize character count + + irpc char,string1 ;; Count and define characters + count defl count + 1 + db '&char' + endm ; irpc + + if not nul enable2 ;; Sub-option label present + if enable2 ;; Sub-option enabled + db ' (' ;; Begin sub-opt description + irpc char,string2 ;; Count and define characters + count defl count + 1 + db '&char' + endm ; irpc + db ')' ;; End sub-opt description + count defl count + 3 ;; For ' ()' + endif ; enable2 + endif ; not nul enable2 + + if [optcnt mod 4] ne 0 + count defl 11 - count + rept count + db ' ' ;; Pad to 11 spaces + endm ; rept + endif ; [optcnt mod 4] ne 0 + + endif ; enable1 + endm ; option macro + +; --------------------------------------------------------- + + COM /_ID_/ ; JetLDR ID block + db 'Copr. 1989 ZSA. Enabled Commands:' + option Cled,cledon + option Cls,clson,TC,clstcap + option Cp,cpon,stp,stpcall + option Dir,diron,sp, + option Echo,echoon,lst,echolst + option Era,eraon,sp, + option Help,true + option List,liston + option Note,noteon + option Peek,peekon,hdr,peekhdr + ; option Poke,pokeon,q,pokeq + option Poke,pokeon + option Port,porton + option Protect,proton + option Quiet,quieton + option Register,regon + option Rename,renon + option Reset,reson,sp, + option Space,spaceon + option Spop,cledon + option Type,lton,cls, + option Wheel,whlon,q,whlquiet + db 0 ; ID string terminator + +;============================================================================= +; +; E N T R Y C O D E S E C T I O N +; +;============================================================================= + + cseg + else + org z3rcp + endif ; ZRL + +RCPbegin: + db 'Z3RCP' ; Package ID + + include RCPcmd ; Command table and custom commands + + include RCPsubs ; File of subroutines + + include RCPh ; 'H' help (command list) command + +; Include only selected code sections. + + if cledon ; 'CLED' and 'SPOP' commands + include RCPcled + endif ;cledon + + if clson + include RCPcls ; 'CLS' clear screen command + endif ;clson + + if reson + include RCPr ; 'R' disk reset command + endif ;reson + + if spaceon + include RCPsp ; 'SP' space on disk command + endif ;spaceon + + if diron + include RCPdir ; 'DIR' directory command + endif ;diron + + if eraon + include RCPera ; 'ERA' erase command + endif ;eraon + + if renon + include RCPren ; 'REN' rename command + endif ;renon + + if cpon + include RCPcp ; 'CP' file copying command + endif ;cpon + + if echoon + include RCPecho ; 'ECHO' command + endif ;echoon + + if quieton + include RCPquiet ; 'Q' quiet flag + endif ;quieton + + if lton + include RCPlt ; 'LIST' and 'TYPE' commands + endif ;lton + + if proton + include RCPprot ; 'PROT' file attribute setting command + endif ;proton + + if peekon or pokeon or porton + include RCPiom ; 'PEEK', 'POKE', 'PORT' commands + endif ;peekon or pokeon or porton + + if regon + include RCPreg ; 'REG' register operation commands + endif ;regon + + if whlon + include RCPwhl ; 'WHL' command + endif ;whlon + + include RCPid ; Add ID string (must come last) + +RCPend: ; Used to calculate length + end + +; End of Z34RCP.Z80 + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/z34rcp11.z80.sav b/Source/BPBIOS/Z34RCP11/z34rcp11.z80.sav new file mode 100644 index 00000000..7af6b875 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/z34rcp11.z80.sav @@ -0,0 +1,267 @@ + +; Program: Z34RCP +; Description: Resident Command Package (RCP) for ZCPR34 +; Version: 1.3 +; Author: Carson Wilson +; Date: September 13, 1989 +; Changes: Added CLED (command line editor shell) command. +; To save space, JetLDR ID section no longer pads end of each +; line. (JetLDR allows only 256 bytes maximum in its +; description field). + +; Version: 1.2 +; Author: Carson Wilson +; Date: July 9, 1989 +; Changes: Uses spaces instead of tabs in JetLDR displays. +; Copyright now ZSA. + +; Version: 1.1 +; Author: Carson Wilson +; Date: September 14, 1988 +; Changes: RCPID macro called from RCPID.LIB so that the final INCLUDE +; macro call gives total size of the RCP, including the ID +; string, with SLR assemblers. +; Label WHLQ changed to WHLQUIET in Option macro to match +; Z34HDR.LIB equate. + +; Version: 1.0 +; Author: Carson Wilson +; Date: June 15, 1988 + +; Derivation: SYSRCP (Richard Conn) + +; Z34RCP is copyright 1989 by Z Systems Associates. All rights reserved. +; End-user distribution and duplication permitted for non-commercial purposes +; only. Any commercial use of Z34RCP, defined as any situation where the +; duplicator recieves revenue by duplicating or distributing Z34RCP by itself +; or in conjunction with any hardware or software product, is expressly +; prohibited unless authorized in writing by Z Systems Associates. + +;============================================================================= +; +; D E F I N I T I O N S S E C T I O N +; +;============================================================================= + + maclib sysdef.lib ; Common logic, sys, ascii defines + maclib z34defn.lib ; Defines offsets in Z34 command processor + maclib z34mac.lib ; Macros + maclib z34rcp.lib ; Defines command options + + if ZRL + maclib z34cmn.lib ; Defines universal ZCPR named common blocks + else + maclib rcpbase.lib ; Defines addresses for one system + endif + + name RCP11 ; Declare module name + +; ============================================================================ + +; M A C R O S S E C T I O N + +; ============================================================================ + +; Macros to include module file and print module length. +; Under SLR and compatible assemblers, a running account of current RCP +; size and bytes added will be given for each module. +; +; For other assemblers you may have to either modify the non-SLR include +; macro or use a text editor to read in the actual contents of each file +; in place of the include statement. + + if SLR +; +; General purpose macro to display decimal values and messages +; +prtdec macro m1,m2,m3,m4,m5 + .radix 10 ; Decimal output + .printx m1 m2 m3 m4 m5 ; Print to screen + endm +; +; Macro to include modules and display lengths. +; +include macro filename +before defl $ + $include filename&.lib +after defl $ +modlen defl after - before ; Module length +rcplen defl after - RCPbegin ; RCP length +recs defl rcplen / 128 ; ..in records +bytes defl rcplen mod 128 ; ..and additional bytes + prtdec %modlen, + prtdec ,%recs,,%bytes, + prtdec ; Trailing CRLF + endm + + else ; Non-SLR assemblers +include macro filename + $include filename&.lib + endm + endif ; SLR + + + if ZRL + +; ============================================================================= + +; J e t L D R I D S E C T I O N + +; ============================================================================= + +; Macro to build ID block message for JetLDR. Max. length is ~256 chars. + +optcnt defl 0 ; Options counter + +option macro string1,enable1,string2,enable2 + + if enable1 ;; Skip if command not present + if [optcnt mod 4] eq 0 + db cr,lf ;; New line every four options + endif + optcnt defl optcnt + 1 ;; Increment options counter + count defl 0 ;; Initialize character count + + irpc char,string1 ;; Count and define characters + count defl count + 1 + db '&char' + endm ; irpc + + if not nul enable2 ;; Sub-option label present + if enable2 ;; Sub-option enabled + db ' (' ;; Begin sub-opt description + irpc char,string2 ;; Count and define characters + count defl count + 1 + db '&char' + endm ; irpc + db ')' ;; End sub-opt description + count defl count + 3 ;; For ' ()' + endif ; enable2 + endif ; not nul enable2 + + if [optcnt mod 4] ne 0 + count defl 11 - count + rept count + db ' ' ;; Pad to 11 spaces + endm ; rept + endif ; [optcnt mod 4] ne 0 + + endif ; enable1 + endm ; option macro + +; --------------------------------------------------------- + + COM /_ID_/ ; JetLDR ID block + db 'Copr. 1989 ZSA. Enabled Commands:' + option Cled,cledon + option Cls,clson,TC,clstcap + option Cp,cpon,stp,stpcall + option Dir,diron,sp, + option Echo,echoon,lst,echolst + option Era,eraon,sp, + option Help,true + option List,liston + option Note,noteon + option Peek,peekon,hdr,peekhdr + option Poke,pokeon,q,pokeq + option Port,porton + option Protect,proton + option Quiet,quieton + option Register,regon + option Rename,renon + option Reset,reson,sp, + option Space,spaceon + option Spop,cledon + option Type,lton,cls, + option Wheel,whlon,q,whlquiet + db 0 ; ID string terminator + +;============================================================================= +; +; E N T R Y C O D E S E C T I O N +; +;============================================================================= + + cseg + else + org z3rcp + endif ; ZRL + +RCPbegin: + db 'Z3RCP' ; Package ID + + include RCPcmd ; Command table and custom commands + + include RCPsubs ; File of subroutines + + include RCPh ; 'H' help (command list) command + +; Include only selected code sections. + + if cledon ; 'CLED' and 'SPOP' commands + include RCPcled + endif ;cledon + + if clson + include RCPcls ; 'CLS' clear screen command + endif ;clson + + if reson + include RCPr ; 'R' disk reset command + endif ;reson + + if spaceon + include RCPsp ; 'SP' space on disk command + endif ;spaceon + + if diron + include RCPdir ; 'DIR' directory command + endif ;diron + + if eraon + include RCPera ; 'ERA' erase command + endif ;eraon + + if renon + include RCPren ; 'REN' rename command + endif ;renon + + if cpon + include RCPcp ; 'CP' file copying command + endif ;cpon + + if echoon + include RCPecho ; 'ECHO' command + endif ;echoon + + if quieton + include RCPquiet ; 'Q' quiet flag + endif ;quieton + + if lton + include RCPlt ; 'LIST' and 'TYPE' commands + endif ;lton + + if proton + include RCPprot ; 'PROT' file attribute setting command + endif ;proton + + if peekon or pokeon or porton + include RCPiom ; 'PEEK', 'POKE', 'PORT' commands + endif ;peekon or pokeon or porton + + if regon + include RCPreg ; 'REG' register operation commands + endif ;regon + + if whlon + include RCPwhl ; 'WHL' command + endif ;whlon + + include RCPid ; Add ID string (must come last) + +RCPend: ; Used to calculate length + end + +; End of Z34RCP.Z80 + \ No newline at end of file diff --git a/Source/BPBIOS/Z34RCP11/zsystem.ad b/Source/BPBIOS/Z34RCP11/zsystem.ad new file mode 100644 index 00000000..5231a575 --- /dev/null +++ b/Source/BPBIOS/Z34RCP11/zsystem.ad @@ -0,0 +1,97 @@ + + Z System Upgrades CP/M + +Z System is first-rate, state-of-the-art software for your CP/M machine. +ZCPR has been with us for over five years now, and has enhanced computer +productivity for thousands of CP/M users. Z System is now a mature +replacement for CP/M 2.2 or CP/M Plus, yet is "backward compatible" with +almost all programs written for CP/M. This means that you can still run +the CP/M programs you now use, yet take advantage of greatly increased +power and performance at the same time. + +NZ-COM and Z3PLUS form the heart of Z System. They replace the most +visible parts of CP/M 2.2 and CP/M Plus respectively, adding enhanced +command processing, named directories, vastly improved resident +commands, flow control processing, error handling, and much more. But +it isn't necessary to master all of these (at least at first!) to take +advantage of NZ-COM and Z3PLUS. In fact, though both packages come with +several disks of software and excellent manuals, almost anyone can +install either of these packages on their CP/M computer in just a few +minutes' time. The price for either NZ-COM or Z3PLUS is $69.95 plus $3 +shipping. + +The next step up in sophistication for CP/M 2.2 users is ZSDOS. ZSDOS +replaces the less visible portion of CP/M 2.2 which controls program +input and output. Through clever coding and exhaustive testing, the +authors of ZSDOS offer significant improvements in performance, safety, +and versatility for CP/M 2.2, including file time and date stamping, +file archiving for faster backups, public files (accessable from all +user areas), path access to files, and improved error messages and +handling. As with NZ-COM and Z3PLUS, ZSDOS installation is completely +menu-driven. ZSDOS comes with a collection of state-of-the-art utility +programs and a 140 page manual to help you make the most of the extended +features. ZSDOS costs $75 ($60 when purchased with NZ-COM) plus $3 +shipping. + +Another path to system enhancement for CP/M 2.2 users with hard or RAM +disks is available in the form of BackGrounder ii (BGii). BGii allows +you to "suspend" any program at the touch of a button and use a wide +range of resident commands such as DIR, REN, ERA, CALC, or TYPE. Then +hit the button again and BGii quickly returns you to the program you +suspended, right where you left off. Or use BGii's SWAP command and +you're back at the CP/M prompt, ready to run any other CP/M program. +When you're through, SWAP again and BGii returns you to your original +program, exactly as you left it! Advanced "Cut" and "Paste" commands +are also available for many terminals, allowing you to transfer sections +of screen directly from one program to another. Print spooling, +advanced key redefinition and recording capabilities, and a beautifully +designed 140-page user's manual are included. Installation is +menu-driven and easy. BGii is compatible with either ZCPR or CP/M 2.2, +and costs $75.00 plus $3 shipping. + +Last but not least is DosDisk, a small but powerful program which allows +you to read or write DIRECTLY to MS-DOS disks with your CP/M computer. +No more file transfers--simply insert a standard MS-DOS DSDD diskette in +your CP/M machine, type "DosDisk :", and away you go--all files +on the diskette are now accessable by all of your CP/M programs--even +files in MS-DOS subdirectories! DosDisk comes with a handsome 38-page +user's manual, and is available preconfigured for most machines at just +$30.00 plus $3 shipping. DosDisk is compatible with either ZCPR or +CP/M 2.2. + +These and other fine products for CP/M and CP/M Plus are available from +users groups around the country, or directly from Z Systems Associates. +Sizeable discounts for users' groups are also available from Z Systems +Associates through the Z Plan. The Z Systems Associates are: + + Sage Microsystems East + Selling & Supporting the Best in 8-Bit Software + 1435 Centre St., Newton Centre, MA 02159-2469 + Voice: 617/965-3552 (9:00am - 11:30pm) + Modem: 617/965-7259 (password = DDT)(MABOS on PC-Pursuit) + + Same-day shipping of most products with modem download and support + available. Order by phone, mail, or modem. Shipping and handling $4 + per order (USA). Check, VISA, or MasterCard. Specify exact disk format. + +and: + + Plu*Perfect Systems + "==World-Class Software" + 410 23rd Street, Santa Monica, CA 90402 + Voice: 213/393-6105 (evenings) + Modem: 213/670-9465 (leave message for "Bridger Mitchell") + + To order: Specify product, operating system, computer, 5 1/4" disk + format. Enclose check, adding $3 shipping ($5 foreign) + 6.5% tax in + California. Enclose invoice if upgrading BGii or ZRDOS. + +For more information on Z System, check at the above bulletin board +systems, or consult The Computer Journal for excellent articles on ZCPR +and CP/M. The Computer Journal is published six times a year by +Publishing Consultants, 190 Sullivan Crossroad, Columbia Falls, MT +59912, phone 406/257-9119. An issue averages forty pages with few ads. +Subscription rates are $16 for one year (6 issues), or $28 for two years +(12 issues) in the U.S., $22 for one year Canada and Mexico, and $24 +(surface) for one year in other countries. + \ No newline at end of file diff --git a/Source/BPBIOS/ZCPR33/Makefile b/Source/BPBIOS/ZCPR33/Makefile new file mode 100644 index 00000000..62450918 --- /dev/null +++ b/Source/BPBIOS/ZCPR33/Makefile @@ -0,0 +1,18 @@ +OBJECTS = zcpr33n.rel zcpr33t.rel +OTHERS = z3basen.lib z3baset.lib +TOOLS = ../../../Tools +DEST = .. + +include $(TOOLS)/Makefile.inc + +DIFFPATH = $(DIFFTO)/Source/BPBIOS + +zcpr33t.rel: ../z3baset.lib + cp ../z3baset.lib z3baset.lib + $(ZXCC) $(CPM)/ZMAC -zcpr33t.z80 -/P + rm z3baset.lib + +zcpr33n.rel: ../z3basen.lib + cp ../z3basen.lib z3basen.lib + $(ZXCC) $(CPM)/ZMAC -zcpr33n.z80 -/P + rm z3basen.lib diff --git a/Source/BPBIOS/cboot-ww.z80 b/Source/BPBIOS/cboot-ww.z80 index ce08a842..3febd543 100644 --- a/Source/BPBIOS/cboot-ww.z80 +++ b/Source/BPBIOS/cboot-ww.z80 @@ -304,7 +304,7 @@ MEMOK: IF MOVCPM ; Space is critical for boot tracks DEFB CR,LF,'P112 - ' ; Save all bytes possible ELSE ; Otherwise sign on with complete name - DEFB CR,LF,'D-X Designs P112 - ' + DEFB CR,LF,'RomWBW - ' ENDIF DEFB 'B/P 50.00k Bios' ;**** Do NOT alter this string **** @@ -322,7 +322,7 @@ MEMOK: DEFB ' ZCPR3+ Env' IF CLOCK IF DS1202 - DEFB CR,LF,' Dallas DS-1202 Clock, ' + DEFB CR,LF,' RomWBW HBIOS Clock, ' IF CLKSET DEFB 'with ' ELSE diff --git a/Source/BPBIOS/def-ww.lib b/Source/BPBIOS/def-ww.lib deleted file mode 100644 index 0eef1948..00000000 --- a/Source/BPBIOS/def-ww.lib +++ /dev/null @@ -1,373 +0,0 @@ -;:::::::::::::::::::::::::::::::::::::::::::::::********************** -; B/P BIOS Configuration and Equate File. ** System Dependant ** -; - D-X Designs Pty Ltd P112 CPU Board - ********************** -; Tailor your system here. -; -; 30 Aug 01 - Cleaned up for GPL release. HFB -; 11 May 97 - Added GIDE and adjusted HD equates. HFB -; 5 Jan 97 - Reformatted to Standard. HFB -; 10 Jun 96 - Initial Test Release. HFB -;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -; BIOS Configuration Equates and Macros - -DATE MACRO - DEFB '17 Jan 14' ; Date of this version - ENDM - -AUTOCL MACRO - DEFB 8,'ZEX Z41 ',0 ; Autostart command line - ENDM - -;--- Basic System and Z-System Section --- - -MOVCPM EQU no ; Integrate into MOVCPM "type" loader? - IF MOVCPM -VERS EQU 13H ; Version number in BCD (Hex) (Major/Minor) - ELSE -VERS EQU 21H ; Version number w/Device Swapping permitted - ENDIF -BANKED EQU YES ; Is this a banked BIOS? -ZSDOS2 EQU YES ; Yes = Banked Dos, No = CP/M 2.2 Compatible -INROM EQU NO ; Alternate bank in ROM? -MHZ EQU 18 ; Set to Speed in MHZ (6/9/12/16/18/24) -FASTWB EQU YES ; Yes if restoring CPR from banked RAM - ; ..No if restoring from Drive A -Z3 EQU YES ; Include ZCPR init code? -HAVIOP EQU NO ; Include IOP code into Jump table? -INTPXY EQU YES ; Internal HBIOS Mini Proxy -CONF_T EQU NO ; Set for Segment Configuration T -CONF_N EQU YES ; Set for Segment Configuration N - -;--- Memory configuration Section --- (Expansion Memory configured here) - -IBMOVS EQU NO ; Yes = Inter-bank Moves allowed (Z180/64180) - ; No = Include Common RAM transfer buffer -;--- Character Device Section --- - -MORDEV EQU NO ; YES = Include any extra Char Device Drivers - ; NO = Only use the 4 defined Char Devices -ESCC_B EQU no ; Include ESCC Channel B Driver? - ; The following two devices result in non-standard data rates - ; with the standard 16.00 MHz crystal in the P112. If a more - ; "standard" crystal is used (12.288, 18.432, 24.576 MHz etc) - ; is used, the ports become usable. - ; Driver code for ASCI0 and ASCI1 includes an option for - ; assembling Polled or Interrupt-driven buffered input. - ; Select the desired option for ASCI0 with the BUFFA0 flag, - ; and BUFFA1 for ASCI1. -ASCI_0 EQU false ; Include ASCI0 Driver? -BUFFA0 EQU false ; Use buffered ASCI0 Input Driver? -ASCI_1 EQU false ; Include ASCI1 Driver? -BUFFA1 EQU false ; Use buffered ASCI1 Input Driver? - -QSIZE EQU 32 ; size of interrupt typeahead buffers (if used) - ; ..must be 2^n with n<8 -RTSCTS EQU no ; Include RTS/CTS code on Serial Outputs? -XONOFF EQU no ; Include Xon/Xoff handshaking in Serial lines? - -;--- Clock and Time Section --- - -CLOCK EQU YES ; Include ZSDOS Clock Driver Code? -DS1202 EQU YES ; Use Dallas DS-1202 instead of Interrupt RTC? -CLKSET EQU YES ; Allow DS-1202 Clock Sets? (Error if No) -TICTOC EQU NO ;== NOT USED IN P112 ("heartbeat" count) - -;--- Floppy Diskette Section --- - -BIOERM EQU yes ; Print BIOS error messages? -CALCSK EQU YES ; Calculate skew table? -AUTOSL EQU YES ; Auto select floppy formats? - ; If AUTOSL=True, the next two are active... -FDDMA EQU no ; Use DMA Control for Floppy Drive Transfers? -FLOPYH EQU no ; Include "Hi-Density" Floppy Formats? -FLOPY8 EQU no ; Include 8" Floppy Formats? -MORDPB EQU NO ; Include additional Floppy DPB Formats? - -;--- RAM Disk Section --- - -RAMDSK EQU YES ; YES = Make RAM-Disk Code, NO = No code made - -;--- Hard Disk Section --- - -HARDDSK EQU YES ; YES = Add Hard-disk Code, NO = Floppy Only - ; (Pick 1 of 3 options below) -SCSI EQU NO ; YES = Use SCSI Driver -IDE EQU NO ; YES = Use IDE Driver -SIMHDSK EQU NO ; YES = Use SIMH HDSK Driver -HBDSK EQU YES ; YES = Use HBIOS Disk Driver -HDDMA EQU NO ; Use DMA-Controlled Hard Disk Data Transfers? - ; (DMA not implemented for GIDE) -UNIT_0 EQU YES ; Hard Disk Physical Unit 1 -UNIT_1 EQU YES ; Hard Disk Physical Unit 2 -UNIT_2 EQU YES ; Hard Disk Physical Unit 3 - -;--- Logical Drive Section --- - -DRV_A EQU no ; Set each of these equates for the drive and -DRV_B EQU no ; partition complement of your system. Assume -DRV_C EQU no ; that A-D are Floppies. -DRV_D EQU no -DRV_E EQU yes ; Assume that E-L and N-P are Hard Disk -DRV_F EQU yes ; Partitions -DRV_G EQU yes -DRV_H EQU yes -DRV_I EQU yes -DRV_J EQU yes -DRV_K EQU yes -DRV_L EQU yes -DRV_M EQU RAMDSK ; This is Yes for RAM drive -DRV_N EQU yes -DRV_O EQU ~RAMDSK ; Use HBIOS RAM disk if BPBIOS RAM disk is not enabled -DRV_P EQU no - -;========== Configuration Unique Equates (P112) =========== -;>>>>>>>>>>>>>>>>>>>>>>>>>>> W A R N I N G <<<<<<<<<<<<<<<<<<<<<<<<<<<<< -;>>> Do NOT Alter these unless you KNOW what you're doing <<< -;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - -REFRSH EQU NO ; Set to NO for only Static RAM, needed for - ; systems with dynamic RAMs. -NOWAIT EQU NO ; Set to NO to use configured Wait States in - ; Hard Disk Driver. Yes to eliminate Waits. - -;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -; For Z-180/HD64180 systems, The Bank numbers should reflect Physical -; memory in 32k increments. In P112, the ROM occupies the first 32k -; increment and is ambiguously addressed occupying 0-1FFFFH. The upper -; memory bounds (BNKU, BNK3 and BNKM) should be set for your configuration. - -BNK0 EQU BID_USR ; First TPA Bank (switched in/out) 40000H -BNK1 EQU BID_HB ; Second TPA Bank (Common Bank) 48000H -BNK2 EQU BID_SYS ; System Bank (BIOS, DOS, CPR) 50000H -BNKU EQU 00H ; User Area Bank 58000H - ; (set to 0 to disable) -BNK3 EQU BID_RAMD ; First Bank for RAM disk 60000H -BNKM EQU BID_RAMM ; Maximum Bank # F8000H - ; With both on-board RAMs only (MEM1 or MEM2), - ; the maximum Bank number is 11 (0BH). - -;=========== CPU-dependent Equates, Zilog Z-180/Hitachi HD64180 ========== - -CNTLA0 EQU 00H ; Control Port ASCI 0 -CNTLA1 EQU 01H ; Control Port ASCI 1 -STAT0 EQU 04H ; Serial port 0 Status -STAT1 EQU 05H ; Serial port 1 Status -TDR0 EQU 06H ; Serial port 0 Output Data -TDR1 EQU 07H ; Serial port 1 Output Data -RDR0 EQU 08H ; Serial port 0 Input Data -RDR1 EQU 09H ; Serial Port 1 Input Data -CNTR EQU 0AH ; HD64180 Counter port -TMDR0L EQU 0CH ; HD64180 DMA channel reg (low) -TMDR0H EQU 0DH ; HD64180 DMA channel reg (hi) -RLDR0L EQU 0EH ; CTC0 Reload Count, Low -RLDR0H EQU 0FH ; CTC0 Reload Count, High -TCR EQU 10H ; Interrupt Control Register -TMDR1L EQU 14H ; Timer Data Reg Ch1 (Low) -TMDR1H EQU 15H ; Timer Data Reg Ch1 (High) -RLDR1L EQU 16H ; Timer Reload Reg Ch1 (Low) -RLDR1H EQU 17H ; Timer Reload Reg Ch1 (High) -FRC EQU 18H ; Free-Running Counter -CCR EQU 1FH ; CPU Control Register (ZS8180/Z80182) -SAR0L EQU 20H ; DMA Channel 0 Register start (8 ports) -MAR1L EQU 28H ; DMA Channel 1 Register start (8 ports) -DSTAT EQU 30H ; DMA Status/Control port -DMODE EQU 31H ; DMA Mode Control port -DCNTL EQU 32H ; DMA/WAIT Control Register -IL EQU 33H ; Interrupt Segment Register -ITC EQU 34H ; Interrupt/Trap Control Register -RCR EQU 36H ; HD64180 Refresh Control register -CBR EQU 38H ; MMU Common Base Register -BBR EQU 39H ; MMU Bank Base Register -CBAR EQU 3AH ; MMU Common/Bank Area Register -OMCR EQU 3EH ; Operation Mode Control Reg -ICR EQU 3FH ; I/O Control Register - -; Some bit definitions used with the Z-180 on-chip peripherals: - -TDRE EQU 02H ; ACSI Transmitter Buffer Empty -RDRF EQU 80H ; ACSI Received Character available - -;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -; Extended Features of Z80182 for P112 - -WSGCS EQU 0D8H ; Wait-State Generator CS -ENH182 EQU 0D9H ; Z80182 Enhancements Register -PINMUX EQU 0DFH ; Interrupt Edge/Pin Mux Register -RAMUBR EQU 0E6H ; RAM End Boundary -RAMLBR EQU 0E7H ; RAM Start Boundary -ROMBR EQU 0E8H ; ROM Boundary -FIFOCTL EQU 0E9H ; FIFO Control Register -RTOTC EQU 0EAH ; RX Time-Out Time Constant -TTOTC EQU 0EBH ; TX Time-Out Time Constant -FCR EQU 0ECH ; FIFO Register -SCR EQU 0EFH ; System Pin Control -RBR EQU 0F0H ; MIMIC RX Buffer Register (R) -THR EQU 0F0H ; MIMIN TX Holding Register (W) -IER EQU 0F1H ; Interrupt Enable Register -LCR EQU 0F3H ; Line Control Register -MCR EQU 0F4H ; Modem Control Register -LSR EQU 0F5H ; Line Status Register -MDMSR EQU 0F6H ; Modem Status Register -MSCR EQU 0F7H ; MIMIC Scratch Register -DLATL EQU 0F8H ; Divisor Latch (Low) -DLATM EQU 0F9H ; Divisor Latch (High) -TTCR EQU 0FAH ; TX Time Constant -RTCR EQU 0FBH ; RX Time Constant -IVEC EQU 0FCH ; MIMIC Interrupt Vector -MIMIE EQU 0FDH ; MIMIC Interrupt Enable Register -IUSIP EQU 0FEH ; MIMIC Interrupt Under-Service Register -MMCR EQU 0FFH ; MIMIC Master Control Register - -; Z80182 PIO Registers - -DDRA EQU 0EDH ; Data Direction Register A -DRA EQU 0EEH ; Port A Data -DDRB EQU 0E4H ; Data Direction Register B -DRB EQU 0E5H ; Data B Data -DDRC EQU 0DDH ; Data Direction Register C -DRC EQU 0DEH ; Data C Data - -;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -; ESCC Registers on Z80182 - -SCCACNT EQU 0E0H ; ESCC Control Channel A -SCCAD EQU 0E1H ; ESCC Data Channel A -SCCBCNT EQU 0E2H ; ESCC Control Channel B -SCCBD EQU 0E3H ; ESCC Data Channel B - -; [E]SCC Internal Register Definitions - -RR0 EQU 00H -RR1 EQU 01H -RR2 EQU 02H -RR3 EQU 03H -RR6 EQU 06H -RR7 EQU 07H -RR10 EQU 0AH -RR12 EQU 0CH -RR13 EQU 0DH -RR15 EQU 0FH - -WR0 EQU 00H -WR1 EQU 01H -WR2 EQU 02H -WR3 EQU 03H -WR4 EQU 04H -WR5 EQU 05H -WR6 EQU 06H -WR7 EQU 07H -WR9 EQU 09H -WR10 EQU 0AH -WR11 EQU 0BH -WR12 EQU 0CH -WR13 EQU 0DH -WR14 EQU 0EH -WR15 EQU 0FH - -; FDC37C665/6 Parallel Port in Standard AT Mode - -DPORT EQU 8CH ; Data Port -SPORT EQU 8DH ; Status Port -CPORT EQU 8EH ; Control Port - -; FDC37C665/6 Configuration Control (access internal registers) - -CFCNTL EQU 90H ; Configuration control port -CFDATA EQU 91H ; Configuration data port - -; FDC37C665/6 Floppy Controller on P112 (Intel 80277 compatible) - -DCR EQU 92H ; Drive Control Register (Digital Output) -MSR EQU 94H ; Main Status Register -DR EQU 95H ; Data/Command Register -DRR EQU 97H ; Data Rate Register/Disk Changed Bit in B7 - -_DMA EQU 0A0H ; Diskette DMA Address - -; FDC37C665/6 Serial Port (National 16550 compatible) - -_RBR EQU 68H ;R Receiver Buffer -_THR EQU 68H ;W Transmit Holding Reg -_IER EQU 69H ;RW Interrupt-Enable Reg -_IIR EQU 6AH ;R Interrupt Ident. Reg -_FCR EQU 6AH ;W FIFO Control Reg -_LCR EQU 6BH ;RW Line Control Reg -_MCR EQU 6CH ;RW Modem Control Reg -_LSR EQU 6DH ;RW Line Status Reg -_MMSR EQU 6EH ;RW Modem Status Reg -_SCR EQU 6FH ;N/A Scratch Reg. (not avail in XT) -_DDL EQU 68H ;RW Divisor LSB | wih DLAB -_DLM EQU 69H ;RW Divisor MSB | set High - -;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -; Equates for the National DP8490/NCR 5380 Prototype SCSI controller - - IF HARDDSK -NCR EQU 40H ; Base of NCR 5380 - -; 5380 Chip Registers - -NCRDAT EQU NCR ; Current SCSI Data (Read) - ; Output Data Register (Write) -NCRCMD EQU NCR+1 ; Initiator Command Register (Read/Write) -NCRMOD EQU NCR+2 ; Mode Register (Read/Write) -NCRTGT EQU NCR+3 ; Target Command Register (Read/Write) -NCRBUS EQU NCR+4 ; Current SCSI Bus Status (Read) -NCRST EQU NCR+5 ; Bus & Status Register (Read) - ; Start DMA Send (Write) -NCRINT EQU NCR+7 ; Reset Parity/Interrupt (Read) - ; Start DMA Initiator Receive (Write) -DMAACK EQU NCR+8 ; SCSI Dack IO Port (Read/Write) - -; Bit Assignments for NCR 5380 Ports as indicated - -B_ARST EQU 10000000B ; Assert *RST (NCRCMD) -B_AACK EQU 00010000B ; Assert *ACK (NCRCMD) -B_ASEL EQU 00000100B ; Assert *SEL (NCRCMD) -B_ABUS EQU 00000001B ; Assert *Data Bus (NCRCMD) - -B_BSY EQU 01000000B ; *Busy (NCRBUS) -B_REQ EQU 00100000B ; *Request (NCRBUS) -B_MSG EQU 00010000B ; *Message (NCRBUS) -B_CD EQU 00001000B ; *Command/Data (NCRBUS) -B_IO EQU 00000100B ; *I/O (NCRBUS) -B_SEL EQU 00000010B ; *Select (NCRBUS) - -B_PHAS EQU 00001000B ; Phase Match (NCRST) -B_BBSY EQU 00000100B ; Bus Busy (NCRST) - -B_MBSY EQU 00000100B ; Monitor Busy Flag (NCRMOD) -B_DMA EQU 00000010B ; DMA Mode of transfer (NCRMOD) - ENDIF ;harddsk - -;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -; Equates reflecting GIDE Base address from Address Jumpers (if GIDE added) -; Set the base GIDE equate to the jumper setting on the GIDE board. - - IF IDE -GIDE EQU 50H ; Set base of 16 byte address range - -IDEDOR EQU GIDE+6 ; Digital Output Register -IDEDat EQU GIDE+8 ; IDE Data Register (16-bit wide) -IDEErr EQU GIDE+9 ; IDE Error Register -IDESCnt EQU GIDE+0AH ; IDE Sector Count Register -IDESNum EQU GIDE+0BH ; IDE Sector Number Register -IDECLo EQU GIDE+0CH ; IDE Cylinder Number (Low) -IDECHi EQU GIDE+0DH ; IDE Cylinter Number (High) -IDESDH EQU GIDE+0EH ; IDE S-Drive-Head Register -IDECmd EQU GIDE+0FH ; IDE Command/Status Register - -CMDHOM EQU 10H ; Home Drive Heads -CMDRD EQU 20H ; Read Sector Command (w/retry) -CMDWR EQU 30H ; Write Sector Command (w/retry) -CMDVER EQU 40H ; Verify Sector(s) Command (w/retry) -CMDFMT EQU 50H ; Format Track Command -CMDDIAG EQU 90H ; Execute Diagnostics Command -CMDINIT EQU 91H ; Initialize Drive Params Command -CMDPW0 EQU 0E0H ; Low Range of Power Control Commands -CMDPW3 EQU 0E3H ; High Range of Power Control Commands -CMDPWQ EQU 0E5H ; Power Status Query Command -CMDID EQU 0ECH ; Read Drive Ident Data Command - ENDIF ;ide -;=================== End Unique Equates ======================= - \ No newline at end of file diff --git a/Source/BPBIOS/dph.lib b/Source/BPBIOS/dph.lib index 72f39fef..1f6021e6 100644 --- a/Source/BPBIOS/dph.lib +++ b/Source/BPBIOS/dph.lib @@ -11,6 +11,11 @@ ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: CSEG + +; Table has been reordered so that hard disk entries come before +; floppy entries. This means that "DRV_A" appears as I: and "DRV_E" +; appears as A:. The point is to allow booting from the first +; hard drive which pretty much needs to be A: DPHTBL: IF DRV_E AND HARDDSK @@ -34,46 +39,46 @@ DPHTBL: DEFW 00 ENDIF - IF DRV_A - DEFW DPH$00 ; Dph for drive A (Floppy) + IF DRV_I AND HARDDSK + DEFW DPH$54 ; Dph for Hard drive Partition I ELSE DEFW 00 ENDIF - IF DRV_B - DEFW DPH$01 ; Dph for drive B (Floppy) + IF DRV_J AND HARDDSK + DEFW DPH$55 ; Dph for Hard drive Partition J ELSE DEFW 00 ENDIF - IF DRV_C - DEFW DPH$02 ; Dph for drive C (Floppy) + IF DRV_K AND HARDDSK + DEFW DPH$56 ; Dph for Hard drive Partition K ELSE DEFW 00 ENDIF - IF DRV_D - DEFW DPH$03 ; Dph for drive D (Floppy) + IF DRV_L AND HARDDSK + DEFW DPH$57 ; Dph for Hard drive Partition L ELSE - DEFW 0 ; Dph for drive D + DEFW 00 ENDIF - IF DRV_I AND HARDDSK - DEFW DPH$54 ; Dph for Hard drive Partition I + IF DRV_A + DEFW DPH$00 ; Dph for drive A (Floppy) ELSE DEFW 00 ENDIF - IF DRV_J AND HARDDSK - DEFW DPH$55 ; Dph for Hard drive Partition J + IF DRV_B + DEFW DPH$01 ; Dph for drive B (Floppy) ELSE DEFW 00 ENDIF - IF DRV_K AND HARDDSK - DEFW DPH$56 ; Dph for Hard drive Partition K + IF DRV_C + DEFW DPH$02 ; Dph for drive C (Floppy) ELSE DEFW 00 ENDIF - IF DRV_L AND HARDDSK - DEFW DPH$57 ; Dph for Hard drive Partition L + IF DRV_D + DEFW DPH$03 ; Dph for drive D (Floppy) ELSE - DEFW 00 + DEFW 0 ; Dph for drive D ENDIF IF DRV_M AND RAMDSK diff --git a/Source/BPBIOS/hardhb.z80 b/Source/BPBIOS/hardhb.z80 index 39d2afab..d19ccb40 100644 --- a/Source/BPBIOS/hardhb.z80 +++ b/Source/BPBIOS/hardhb.z80 @@ -189,7 +189,8 @@ HDSK_RW1: POP BC ; RESTORE INCOMING FUNCTION, DEVICE/UNIT RET NZ ; ABORT IF SEEK RETURNED AN ERROR W/ ERROR IN A LD HL,(HB_DSKBUF) ; GET BUFFER ADDRESS - LD DE,1 ; TRANSFER ONE SECTOR + LD D,BID_HB ; BUFFER IN HBIOS BANK + LD E,1 ; ONE SECTOR CALL HBX_INVOKE ; DO IT OR A ; SET FLAGS RET Z ; DONE IF NO ERROR diff --git a/Source/BPBIOS/hbios.z80 b/Source/BPBIOS/hbios.z80 index be63e92d..c8671c87 100644 --- a/Source/BPBIOS/hbios.z80 +++ b/Source/BPBIOS/hbios.z80 @@ -115,7 +115,7 @@ HBX_INIT: LD (HB_SRCBNK),A LD (HB_DSTBNK),A - IF BANKED + IF BANKED ; Copy vectors from TPA page zero to SYS page zero LD BC,(TPABNK) ; C := TPABNK, B := SYSBNK @@ -127,8 +127,68 @@ HBX_INIT: LD A,(TPABNK) ; Set all Bank regs to TPA ENDIF + +; CSEG +;ORG_CSEG EQU $ +; DSEG +;ORG_DSEG EQU $ + IF BANKED + COMMON /BANK2/ +ORG_BANK2 EQU $ + COMMON /B2RAM/ +ORG_B2RAM EQU $ + ENDIF + + CSEG + + CALL NEWLINE2 + LD DE,HB_STR_TAG + CALL WRITESTR + + IF INTPXY + LD DE,HB_STR_INTPXY + ELSE + LD DE,HB_STR_EXTPXY + ENDIF + CALL WRITESTR + + CALL NEWLINE + LD DE,HB_STR_CSEG + CALL WRITESTR + LD BC,BIOSJT + CALL PRTHEXWORD + LD DE,HB_STR_DSEG + CALL WRITESTR + LD BC,CBOOT + CALL PRTHEXWORD + + IF BANKED + LD DE,HB_STR_BANK2 + CALL WRITESTR + LD BC,ROMJT + CALL PRTHEXWORD + LD DE,HB_STR_B2RAM + CALL WRITESTR + LD BC,CBOOT0 + CALL PRTHEXWORD + LD DE,HB_STR_RESVD + CALL WRITESTR + LD BC,ALV$50 + CALL PRTHEXWORD + ENDIF + + CALL NEWLINE RET + +HB_STR_TAG DB "HBIOS: $" +HB_STR_INTPXY DB "Internal Proxy$" +HB_STR_EXTPXY DB "External Proxy$" +HB_STR_CSEG DB "CSEG=$" +HB_STR_DSEG DB ", DSEG=$" +HB_STR_BANK2 DB ", BANK2=$" +HB_STR_B2RAM DB ", B2RAM=$" +HB_STR_RESVD DB ", RESVD=$" HBX_XCOPY: LD A,C @@ -194,6 +254,9 @@ HBX_ROM: IF MK4 RLCA + JR NC,HBX_BNKSEL1 + XOR 00100001B +HBX_BNKSEL1: RLCA RLCA OUT0 (CPU_BBR),A diff --git a/Source/BPBIOS/romwbw-mk4.lib b/Source/BPBIOS/romwbw-mk4.lib index 09968231..e4dcf70f 100644 --- a/Source/BPBIOS/romwbw-mk4.lib +++ b/Source/BPBIOS/romwbw-mk4.lib @@ -26,15 +26,15 @@ HBCLK EQU YES ; HBIOS clock driver ; ; Set HB_IODEV to appropriate console device ; -HB_IODEV EQU 0 +HB_IODEV EQU 0 ; Assume we want to use first HBIOS serial device ; ; Set HB_HDDEV to appropriate hard disk driver ; -HB_HDDEV EQU 2 +HB_HDDEV EQU 2 ; Assumes disk device #2 is first hard disk device ; ; Set HB_MDDEV to appropriate memory disk driver ; -HB_MDDEV EQU 0 +HB_MDDEV EQU 0 ; Assumes disk device #0 is ROM disk device ; ; RAM/ROM disk sizes expressed as count of 2K blocks ; @@ -66,3 +66,17 @@ MEMTOP EQU 0FFE0H - 1 ; Start of HBIOS 32 byte control block ELSE MEMTOP EQU HBLOC - 1 ; Start of HBIOS 512 byte proxy ENDIF + + IF INTPXY +HB_EI MACRO + ENDM +HB_DI MACRO + ENDM + ELSE +HB_EI MACRO + EI + ENDM +HB_DI MACRO + DI + ENDM + ENDIF diff --git a/Source/BPBIOS/romwbw-sim.lib b/Source/BPBIOS/romwbw-sim.lib index 61087ae5..d666295f 100644 --- a/Source/BPBIOS/romwbw-sim.lib +++ b/Source/BPBIOS/romwbw-sim.lib @@ -26,15 +26,15 @@ HBCLK EQU YES ; HBIOS clock driver ; ; Set HB_IODEV to appropriate console device ; -HB_IODEV EQU 0 +HB_IODEV EQU 0 ; Assume we want to use first HBIOS serial device ; ; Set HB_HDDEV to appropriate hard disk driver ; -HB_HDDEV EQU 2 +HB_HDDEV EQU 2 ; Assumes disk device #2 is first hard disk device ; ; Set HB_MDDEV to appropriate memory disk driver ; -HB_MDDEV EQU 0 +HB_MDDEV EQU 0 ; Assumes disk device #0 is ROM disk device ; ; RAM/ROM disk sizes expressed as count of 2K blocks ; @@ -66,3 +66,17 @@ MEMTOP EQU 0FFE0H - 1 ; Start of HBIOS 32 byte control block ELSE MEMTOP EQU HBLOC - 1 ; Start of HBIOS 512 byte proxy ENDIF + + IF INTPXY +HB_EI MACRO + ENDM +HB_DI MACRO + ENDM + ELSE +HB_EI MACRO + EI + ENDM +HB_DI MACRO + DI + ENDM + ENDIF diff --git a/Source/BPBIOS/util.z80 b/Source/BPBIOS/util.z80 index cf71b8ee..4602fcfa 100644 --- a/Source/BPBIOS/util.z80 +++ b/Source/BPBIOS/util.z80 @@ -90,6 +90,8 @@ PC_PRTCHR: POP AF RET +NEWLINE2: + CALL NEWLINE NEWLINE: CALL PC_CR CALL PC_LF diff --git a/Source/BPBIOS/z33.zex b/Source/BPBIOS/z33.zex index bd8b92d3..ea1e714e 100644 --- a/Source/BPBIOS/z33.zex +++ b/Source/BPBIOS/z33.zex @@ -1,3 +1,3 @@ IOPINIT -LDR SYS.RCP,SYS.NDR,SYS.FCP,WW.Z3T +JETLDR RCP.ZRL,SYS.NDR,FCP.ZRL,MYTERM.Z3T  \ No newline at end of file diff --git a/Source/BPBIOS/z34.zex b/Source/BPBIOS/z34.zex index bd8b92d3..ea1e714e 100644 --- a/Source/BPBIOS/z34.zex +++ b/Source/BPBIOS/z34.zex @@ -1,3 +1,3 @@ IOPINIT -LDR SYS.RCP,SYS.NDR,SYS.FCP,WW.Z3T +JETLDR RCP.ZRL,SYS.NDR,FCP.ZRL,MYTERM.Z3T  \ No newline at end of file diff --git a/Source/BPBIOS/z41.zex b/Source/BPBIOS/z41.zex index 010e3334..aa944b80 100644 --- a/Source/BPBIOS/z41.zex +++ b/Source/BPBIOS/z41.zex @@ -1,4 +1,4 @@ IOPINIT -LDR SYS.NDR,SYS.FCP,WW.Z3T +JETLDR SYS.NDR,FCP.ZRL,MYTERM.Z3T ZSCFG2 CB  \ No newline at end of file diff --git a/Source/Build.cmd b/Source/Build.cmd index 77751057..9419b3ce 100644 --- a/Source/Build.cmd +++ b/Source/Build.cmd @@ -3,7 +3,7 @@ setlocal REM setlocal & call BuildDoc || exit /b 1 & endlocal setlocal & call BuildProp || exit /b 1 & endlocal -setlocal & call BuildImages || exit /b 1 & endlocal setlocal & call BuildShared || exit /b 1 & endlocal REM setlocal & call BuildBP || exit /b 1 & endlocal +setlocal & call BuildImages || exit /b 1 & endlocal setlocal & call BuildROM %* || exit /b 1 & endlocal \ No newline at end of file diff --git a/Source/BuildDoc.cmd b/Source/BuildDoc.cmd deleted file mode 100644 index f2683b30..00000000 --- a/Source/BuildDoc.cmd +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -setlocal - -setlocal & cd Doc && call Build || exit /b 1 & endlocal \ No newline at end of file diff --git a/Source/BuildShared.cmd b/Source/BuildShared.cmd index c2852206..578af84e 100644 --- a/Source/BuildShared.cmd +++ b/Source/BuildShared.cmd @@ -1,10 +1,13 @@ @echo off setlocal -setlocal & cd Apps && call Build || exit /b 1 & endlocal +setlocal & cd CBIOS && call Build || exit /b 1 & endlocal setlocal & cd CPM22 && call Build || exit /b 1 & endlocal setlocal & cd ZCPR && call Build || exit /b 1 & endlocal setlocal & cd ZCPR-DJ && call Build || exit /b 1 & endlocal setlocal & cd ZSDOS && call Build || exit /b 1 & endlocal -setlocal & cd CBIOS && call Build || exit /b 1 & endlocal +setlocal & cd CPM3 && call Build || exit /b 1 & endlocal +setlocal & cd ZPM3 && call Build || exit /b 1 & endlocal +setlocal & cd Apps && call Build || exit /b 1 & endlocal setlocal & cd Forth && call Build || exit /b 1 & endlocal +setlocal & cd Fonts && call Build || exit /b 1 & endlocal diff --git a/Source/CBIOS/Makefile b/Source/CBIOS/Makefile new file mode 100644 index 00000000..46e4b507 --- /dev/null +++ b/Source/CBIOS/Makefile @@ -0,0 +1,10 @@ +OBJECTS = cbios_wbw.bin cbios_una.bin +TOOLS = ../../Tools +include $(TOOLS)/Makefile.inc + +cbios_wbw.bin: cbios.asm + $(TASM) -dPLTWBW $< $@ cbios_wbw.lst + +cbios_una.bin: cbios.asm + $(TASM) -dPLTUNA $< $@ cbios_una.lst + diff --git a/Source/CBIOS/cbios.asm b/Source/CBIOS/cbios.asm index 7dadf2bc..5f627426 100644 --- a/Source/CBIOS/cbios.asm +++ b/Source/CBIOS/cbios.asm @@ -50,7 +50,7 @@ DEF_IOBYTE .EQU %10010100 ; DEFAULT IOBYTE VALUE DEV_BAT .EQU $FE ; BAT: DEV_NUL .EQU $FF ; NUL: ; -#INCLUDE "../HBIOS/ver.inc" +#INCLUDE "../ver.inc" ; #INCLUDE "config.asm" ; @@ -131,7 +131,7 @@ STPSIZ .EQU $ - STPIMG ; ; The following section contains key information and addresses for the ; RomWBW CBIOS. A pointer to the start of this section is stored with -; with the ZPX data in page zero at $44 (see above). +; with the CBX data in page zero at $44 (see above). ; CBX: DEVMAPADR .DW DEVMAP ; DEVICE MAP ADDRESS @@ -192,18 +192,18 @@ LD_UL1 .EQU 0 ; -> LPT1: #ELSE -LD_TTY .EQU CIODEV_CONSOLE ; -> COM0: -LD_CRT .EQU CIODEV_CONSOLE ; -> CRT: +LD_TTY .EQU CIO_CONSOLE ; -> COM0: +LD_CRT .EQU CIO_CONSOLE ; -> CRT: LD_BAT .EQU DEV_BAT -LD_UC1 .EQU CIODEV_CONSOLE ; -> COM1: -LD_PTR .EQU CIODEV_CONSOLE ; -> COM1: -LD_UR1 .EQU CIODEV_CONSOLE ; -> COM2: -LD_UR2 .EQU CIODEV_CONSOLE ; -> COM3: -LD_PTP .EQU CIODEV_CONSOLE ; -> COM1: -LD_UP1 .EQU CIODEV_CONSOLE ; -> COM2: -LD_UP2 .EQU CIODEV_CONSOLE ; -> COM3: -LD_LPT .EQU CIODEV_CONSOLE ; -> LPT0: -LD_UL1 .EQU CIODEV_CONSOLE ; -> LPT1: +LD_UC1 .EQU CIO_CONSOLE ; -> COM1: +LD_PTR .EQU CIO_CONSOLE ; -> COM1: +LD_UR1 .EQU CIO_CONSOLE ; -> COM2: +LD_UR2 .EQU CIO_CONSOLE ; -> COM3: +LD_PTP .EQU CIO_CONSOLE ; -> COM1: +LD_UP1 .EQU CIO_CONSOLE ; -> COM2: +LD_UP2 .EQU CIO_CONSOLE ; -> COM3: +LD_LPT .EQU CIO_CONSOLE ; -> LPT0: +LD_UL1 .EQU CIO_CONSOLE ; -> LPT1: #ENDIF ; @@ -237,7 +237,7 @@ DEVMAP: ; Disk mapping is done using a drive map table (DRVMAP) which is built ; dynamically at cold boot. See the DRV_INIT routine. This table is ; made up of entries as documented below. The table is prefixed with one -; byte indicating the number of entries. The postion of the entry indicates +; byte indicating the number of entries. The position of the entry indicates ; the drive letter, so the first entry is A:, the second entry is B:, etc. ; ; UNIT: BIOS DISK UNIT # (BYTE) @@ -272,8 +272,8 @@ DEVMAP: ; DPB MAPPING TABLE ;================================================================================================== ; -; MAP MEDIA ID'S TO APPROPRIATE DPB ADDRESSEES -; THE ENTRIES IN THIS TABLE MUST CONCIDE WITH THE VALUES +; MAP MEDIA ID'S TO APPROPRIATE DPB ADDRESSES +; THE ENTRIES IN THIS TABLE MUST COINCIDE WITH THE VALUES ; OF THE MEDIA ID'S (SAME SEQUENCE, NO GAPS) ; .DB DPBCNT @@ -300,9 +300,8 @@ DPBCNT .EQU ($ - DPBMAP) / 2 BOOT: ; STANDARD BOOT INVOCATION LD SP,STACK ; STACK FOR INITIALIZATION - ; - ; COPY INITIALIZATION CODE TO RUNNINT LOCATION $8000 + ; COPY INITIALIZATION CODE TO RUNNING LOCATION $8000 LD HL,BUFPOOL LD DE,$8000 LD BC,CBIOS_END - BUFPOOL @@ -351,7 +350,7 @@ WBOOT: #IFDEF PLTUNA ; RESTORE COMMAND PROCESSOR FROM UNA BIOS CACHE LD BC,$01FB ; UNA FUNC = SET BANK - LD DE,BID_BIOS ; UBIOS_PAGE (SEE PAGES.INC) + LD DE,(BNKBIOS) ; UBIOS_PAGE (SEE PAGES.INC) RST 08 ; DO IT PUSH DE ; SAVE PREVIOUS BANK @@ -1409,7 +1408,9 @@ CHS2: POP BC ; RESTORE INCOMING FUNCTION, DEVICE/UNIT RET NZ ; ABORT IF SEEK RETURNED AN ERROR W/ ERROR IN A LD HL,(DSKBUF) ; GET BUFFER ADDRESS - LD DE,1 ; TRANSFER ONE SECTOR + LD A,(BNKBIOS) ; GET BIOS BANK + LD D,A ; TRANSFER TO/FROM BIOS BANK + LD E,1 ; TRANSFER ONE SECTOR RST 08 ; DO IT OR A ; SET FLAGS RET ; DONE @@ -1512,6 +1513,11 @@ BNKBIOS .DB 0 ; BIOS BANK ID BNKUSER .DB 0 ; USER BANK ID #ENDIF ; +#IFDEF PLTUNA +BNKBIOS .DW 0 ; BIOS BANK ID +BNKUSER .DW 0 ; USER BANK ID +#ENDIF +; ; DOS DISK VARIABLES ; DSKOP .DB 0 ; DISK OPERATION (DOP_READ/DOP_WRITE) @@ -1668,8 +1674,8 @@ DPB_RF: .DB 15 ; BLM: BLOCK MASK .DB 0 ; EXM: EXTENT MASK .DW 2047 ; DSM: TOTAL STORAGE IN BLOCKS - 1 BLK = (4MB / 2K BLS) - 1 = 2047 - .DW 255 ; DRM: DIR ENTRIES - 1 = 256 - 1 = 255 - .DB 11110000B ; AL0: DIR BLK BIT MAP, FIRST BYTE + .DW 511 ; DRM: DIR ENTRIES - 1 = 256 - 1 = 255 + .DB 11111111B ; AL0: DIR BLK BIT MAP, FIRST BYTE .DB 00000000B ; AL1: DIR BLK BIT MAP, SECOND BYTE .DW 0 ; CKS: ZERO FOR NON-REMOVABLE MEDIA .DW 0 ; OFF: RESERVED TRACKS = 0 TRK @@ -1820,14 +1826,24 @@ INIT: LD (CBIOS_LOC + 1),HL ; STORE IT IN FIRST ENTRY OF CBIOS JUMP TABLE #IFDEF PLTUNA + ; GET CRITICAL BANK ID'S + LD BC,$03FA ; UNA FUNC = GET CUR EXEC ENV + CALL $FFFD ; DE = CUR EXEC PAGE, HL = UBIOS PAGE + LD (BNKBIOS),HL ; SAVE UBIOS PAGE + LD BC,$05FA ; UNA FUNC = GET USER EXEC ENV + CALL $FFFD ; DE = USER LOW PAGE, HL = USER HIGH PAGE (COMMMON) + LD (BNKUSER),DE ; SAVE USER PAGE + LD DE,$8000 ; RAM DRIVE STARTS AT FIRST RAM BANK + LD (BNKRAMD),DE ; SAVE STARTING RAM DRIVE BANK + ; MAKE SURE UNA EXEC PAGE IS ACTIVE LD BC,$01FB ; UNA FUNC = SET BANK - LD DE,BID_USR ; SWITCH BACK TO EXEC BANK + LD DE,(BNKUSER) ; SWITCH BACK TO EXEC BANK CALL $FFFD ; DO IT (RST 08 NOT YET INSTALLED) ; COPY BIOS PAGE ZERO TO USER BANK LD BC,$01FB ; UNA FUNC = SET BANK - LD DE,BID_BIOS ; UBIOS_PAGE (SEE PAGES.INC) + LD DE,(BNKBIOS) ; UBIOS_PAGE (SEE PAGES.INC) CALL $FFFD ; DO IT (RST 08 NOT YET INSTALLED) PUSH DE ; SAVE PREVIOUS BANK @@ -1850,6 +1866,7 @@ INIT: LD (8),A ; STORE AT 0x0008 LD HL,($FFFE) ; UNA ENTRY VECTOR LD (9),HL ; STORE AT 0x0009 + #ELSE ; GET CRITICAL BANK ID'S LD B,BF_SYSGET ; HBIOS FUNC=GET SYS INFO @@ -1884,10 +1901,6 @@ INIT: LD A,DEF_IOBYTE ; LOAD DEFAULT IOBYTE LD (IOBYTE),A ; STORE IT - ; INIT DEFAULT DRIVE TO A: FOR NOW - XOR A ; ZERO - LD (DEFDRIVE),A ; STORE IT - ; CBIOS BANNER CALL NEWLINE2 ; FORMATTING LD DE,STR_BANNER ; POINT TO BANNER @@ -1902,7 +1915,7 @@ INIT: LD (CCPBUF),HL ; SAVE THE ADDRESS (IN BIOS MEM) LD BC,$01FB ; UNA FUNC = SET BANK - LD DE,BID_BIOS ; UBIOS_PAGE (SEE PAGES.INC) + LD DE,(BNKBIOS) ; UBIOS_PAGE (SEE PAGES.INC) RST 08 ; DO IT PUSH DE ; SAVE PREVIOUS BANK @@ -1996,9 +2009,14 @@ AUTOSUB: ; ; SETUP AUTO SUBMIT COMMAND (IF REQUIRED FILES EXIST) LD A,(DEFDRIVE) ; GET DEFAULT DRIVE + PUSH AF ; SAVE DEFAULT DRIVE INC A ; CONVERT FROM DRIVE NUM TO FCB DRIVE CODE LD (FCB_SUB),A ; SET DRIVE OF SUBMIT.COM FCB LD (FCB_PRO),A ; SET DRIVE OF PROFILE.SUB FCB +; + LD C,13 ; RESET DISK SYSTEM + CALL BDOS ; DO IT + POP AF ; RESTORE DEFAULT DRIVE ; LD C,17 ; BDOS FUNCTION: FIND FIRST LD DE,FCB_SUB ; CHECK FOR SUBMIT.COM @@ -2012,7 +2030,7 @@ AUTOSUB: INC A ; CHECK FOR ERR, $FF --> $00 RET Z ; ERR, DO NOT ATTEMPT AUTO SUBMIT ; - LD HL,CMD ; ADDRESS OF STARTUP COMMAND + LD HL,CMD ; ADDRESS OF STARTUP COMMANDS LD DE,CCP_LOC + 7 ; START OF COMMAND BUFFER IN CCP LD BC,CMDLEN ; LENGTH OF AUTOSTART COMMAND LDIR ; PATCH COMMAND LINE INTO CCP @@ -2166,7 +2184,7 @@ MD_INIT4: #IF (CLRRAMDISK != CLR_NEVER) DI ; NO INTERRUPTS LD BC,$01FB ; UNA FUNC = SET BANK - LD DE,BID_RAMD0 ; FIRST BANK OF RAM DISK + LD DE,(BNKRAMD) ; FIRST BANK OF RAM DISK CALL $FFFD ; DO IT (RST 08 NOT SAFE HERE) #IF (CLRRAMDISK == CLR_AUTO) @@ -2191,7 +2209,7 @@ CLRRAM1: CLRRAM2: #ENDIF LD BC,$01FB ; UNA FUNC = SET BANK - LD DE,BID_USR ; SWITCH BACK TO EXEC BANK FOR WRITESTR + LD DE,(BNKUSER) ; SWITCH BACK TO EXEC BANK FOR WRITESTR CALL $FFFD ; DO IT (RST 08 NOT SAFE HERE) CALL NEWLINE2 ; FORMATTING @@ -2199,7 +2217,7 @@ CLRRAM2: CALL WRITESTR ; DISPLAY IT LD BC,$01FB ; UNA FUNC = SET BANK - LD DE,BID_RAMD0 ; FIRST BANK OF RAM DISK + LD DE,(BNKRAMD) ; FIRST BANK OF RAM DISK CALL $FFFD ; DO IT (RST 08 NOT SAFE HERE) LD HL,0 ; SOURCE ADR FOR FILL @@ -2208,7 +2226,7 @@ CLRRAM2: CALL FILL ; DO IT CLRRAM3: LD BC,$01FB ; UNA FUNC = SET BANK - LD DE,BID_USR ; SWITCH BACK TO EXEC BANK + LD DE,(BNKUSER) ; SWITCH BACK TO EXEC BANK CALL $FFFD ; DO IT (RST 08 NOT SAFE HERE) EI ; RESUME INTERRUPTS @@ -2259,7 +2277,7 @@ CLRRAM2: CLRRAM3: LD A,(BNKUSER) ; USR BANK (TPA) CALL HB_BNKSEL ; SELECT BANK - EI ; RESUME INTRRUPTS + EI ; RESUME INTERRUPTS #ENDIF ; #ENDIF @@ -2278,9 +2296,25 @@ DRV_INIT: ; GET BOOT UNIT/SLICE INFO LD BC,$00FC ; UNA FUNC: GET BOOTSTRAP HISTORY RST 08 ; CALL UNA - LD D,L ; SAVE L AS UNIT - LD E,0 ; SLICE IS ZERO + LD A,L ; PUT IN ACCUM + AND $0F ; UNIT IN LOW NIBBLE + LD D,A ; UNIT NUM TO D + LD A,L ; GET ORIGINAL VALUE BACK + RLCA ; MOVE SLICE TO LOW NIBBLE + RLCA ; ... + RLCA ; ... + RLCA ; ... + AND $0F ; SLICE NOW IN LOW NIBBLE + LD E,A ; SLICE TO E LD (BOOTVOL),DE ; D -> UNIT, E -> SLICE +; + ; INIT DEFAULT + LD A,D ; BOOT UNIT? + CP 1 ; IF ROM BOOT, DEF DRIVE SHOULD BE B: + JR Z,DRV_INIT1 ; ... SO LEAVE AS IS AND SKIP AHEAD + XOR A ; ELSE FORCE TO DRIVE A: +DRV_INIT1: + LD (DEFDRIVE),A ; STORE IT ; ; SETUP THE DRVMAP STRUCTURE LD HL,(HEAPTOP) ; GET CURRENT HEAP TOP @@ -2328,6 +2362,14 @@ DRV_INIT5: LD A,E ; SLICES PER VOLUME VALUE TO ACCUM LD (HDSPV),A ; SAVE IT ; + LD DE,(BOOTVOL) ; BOOT VOLUME (UNIT, SLICE) + LD A,1 ; ROM DISK UNIT? + CP D ; CHECK IT + JR Z,DRV_INIT5A ; IF SO, SKIP BOOT DRIVE + LD B,1 ; JUST ONE SLICE PLEASE + CALL DRV_INIT8A ; DO THE BOOT DEVICE +; +DRV_INIT5A: ; SETUP TO ENUMERATE DEVICES TO BUILD DRVMAP LD B,0 ; START WITH UNIT 0 ; @@ -2356,7 +2398,22 @@ DRV_INIT7: ; PROCESS CURRENT UNIT (SEE UNA PROTOIDS.INC) LD A,(HDSPV) ; GET SLICES PER VOLUME TO ACCUM LD B,A ; MOVE TO B FOR LOOP COUNTER ; -DRV_INIT8: ; SLICE CREATION LOOP +DRV_INIT8: + ; SLICE CREATION LOOP + ; DE=UNIT/SLICE, B=SLICE CNT + LD A,(BOOTVOL + 1) ; GET BOOT UNIT + CP 1 ; ROM BOOT? + JR Z,DRV_INIT8A ; IF SO, OK TO CONTINUE + CP D ; COMPARE TO CUR UNIT + JR NZ,DRV_INIT8A ; IF NE, OK TO CONTINUE + LD A,(BOOTVOL) ; GET BOOT SLICE + CP E ; COMPARE TO CUR SLICE + JR NZ,DRV_INIT8A ; IF NE, OK TO CONTINUE + INC E ; IS BOOT DU/SLICE, SKIP IT + DJNZ DRV_INIT8 ; LOOP AS NEEDED + RET ; DONE +; +DRV_INIT8A: ; ENTRY POINT TO SKIP BOOT DISK/LU CHECK ; ; INC DRVMAP ENTRY COUNT AND CHECK FOR 16 ENTRY MAXIMUM LD HL,(DRVMAPADR) ; POINT TO DRIVE MAP @@ -2389,6 +2446,14 @@ DRV_INIT: ; GET BOOT UNIT/SLICE INFO LD DE,(HCB + HCB_BOOTVOL) ; BOOT VOLUME (UNIT, SLICE) LD (BOOTVOL),DE ; D -> UNIT, E -> SLICE +; + ; INIT DEFAULT + LD A,D ; BOOT UNIT? + CP 1 ; IF ROM BOOT, DEF DRIVE SHOULD BE B: + JR Z,DRV_INIT1 ; ... SO LEAVE AS IS AND SKIP AHEAD + XOR A ; ELSE FORCE TO DRIVE A: +DRV_INIT1: + LD (DEFDRIVE),A ; STORE IT ; ; SETUP THE DRVMAP STRUCTURE LD HL,(HEAPTOP) ; GET CURRENT HEAP TOP @@ -2396,7 +2461,11 @@ DRV_INIT: LD (DRVMAPADR),HL ; SAVE AS DRVMAP ADDRESS LD (HEAPTOP),HL ; AND AS NEW HEAP TOP ; - ; SETUP TO LOOP THROUGH AVAILABLE DEVICES + ; SETUP TO LOOP THROUGH AVAILABLE DEVICES BUILDING LIST OF + ; ACTIVE UNITS AND COUNTING NUMBER OF ACTIVE HARD DISK + ; DEVICES. NON-HARD DISK UNITS ARE ALWAYS CONSIDERED + ; ACTIVE, BUT HARD DISK UNITS ARE ONLY CONSIDERED ACTIVE + ; IF THERE IS MEDIA IN THE DRIVE. LD B,BF_SYSGET LD C,BF_SYSGET_DIOCNT RST 08 ; E := DISK UNIT COUNT @@ -2406,27 +2475,56 @@ DRV_INIT: RET Z ; HANDLE ZERO DEVICES (ALBEIT POORLY) ; ; LOOP THRU DEVICES TO COUNT TOTAL HARD DISK VOLUMES - PUSH BC ; SAVE THE DEVICE COUNT - LD C,0 ; USE C AS DEVICE LIST INDEX - LD E,0 ; INIT E FOR HARD DISK VOLUME COUNT + LD C,0 ; INIT C AS DEVICE LIST INDEX + LD D,0 ; INIT D AS TOTAL DEVICE COUNT + LD E,0 ; INIT E FOR HARD DISK DEVICE COUNT + LD HL,DRVLST ; INIT HL PTR TO DRIVE LIST ; DRV_INIT2: - PUSH BC ; SAVE LOOP CONTROL CALL DRV_INIT3 ; CHECK DRIVE - POP BC ; RESTORE LOOP CONTROL INC C ; NEXT UNIT DJNZ DRV_INIT2 ; LOOP - POP BC ; RESTORE UNIT COUNT IN B + LD A,D ; TOTAL DEVICE COUNT TO D + LD (DRVLSTC),A ; SAVE THE COUNT JR DRV_INIT4 ; CONTINUE ; DRV_INIT3: PUSH DE ; SAVE DE (HARD DISK VOLUME COUNTER) + PUSH HL ; SAVE DRIVE LIST PTR + PUSH BC ; SAVE LOOP CONTROL LD B,BF_DIODEVICE ; HBIOS FUNC: REPORT DEVICE INFO RST 08 ; CALL HBIOS, UNIT TO C LD A,D ; DEVICE TYPE TO A + POP BC ; RESTORE LOOP CONTROL + POP HL ; RESTORE DRIVE LIST PTR POP DE ; RESTORE DE CP DIODEV_IDE ; HARD DISK DEVICE? - RET C ; NOPE, RETURN + JR NC,DRV_INIT3A ; IF SO, HANDLE SPECIAL + LD (HL),C ; SAVE UNIT NUM IN LIST + INC HL ; BUMP PTR + INC D ; INC TOTAL DEVICE COUNT + RET +; +DRV_INIT3A: + ; CHECK FOR ACTIVE AND RETURN IF NOT + PUSH DE ; SAVE DE (HARD DISK VOLUME COUNTER) + PUSH HL ; SAVE DRIVE LIST PTR + PUSH BC ; SAVE LOOP CONTROL + + LD B,BF_DIOMEDIA ; HBIOS FUNC: SENSE MEDIA + LD E,1 ; PERFORM MEDIA DISCOVERY + RST 08 + + POP BC ; RESTORE LOOP CONTROL + POP HL ; RESTORE DRIVE LIST PTR + POP DE ; RESTORE DE + + RET NZ ; IF NO MEDIA, JUST RETURN + + ; IF ACTIVE... + LD (HL),C ; SAVE UNIT NUM IN LIST + INC HL ; BUMP PTR + INC D ; INC TOTAL DEVICE COUNT INC E ; INCREMENT HARD DISK COUNT RET ; AND RETURN ; @@ -2443,15 +2541,21 @@ DRV_INIT4: ; SET SLICES PER VOLUME (HDSPV) BASED ON HARD DISK VOLUME COUNT DRV_INIT5: LD A,E ; SLICES PER VOLUME VALUE TO ACCUM LD (HDSPV),A ; SAVE IT -; - ; SETUP TO ENUMERATE DEVICES TO BUILD DRVMAP - LD B,BF_SYSGET - LD C,BF_SYSGET_DIOCNT - RST 08 ; E := DISK UNIT COUNT - LD B,E ; COUNT TO B - LD C,0 ; USE C AS DEVICE LIST INDEX + LD DE,(BOOTVOL) ; BOOT VOLUME (UNIT, SLICE) + LD A,1 ; ROM DISK UNIT? + CP D ; CHECK IT + JR Z,DRV_INIT5A ; IF SO, SKIP BOOT DRIVE + LD B,1 ; JUST ONE SLICE PLEASE + CALL DRV_INIT8A ; DO THE BOOT DEVICE +; +DRV_INIT5A: + LD A,(DRVLSTC) ; ACTIVE DRIVE LIST COUNT TO ACCUM + LD B,A ; ... AND MOVE TO B FOR LOOP COUNTER + LD HL,DRVLST ; HL IS PTR TO ACTIVE DRIVE LIST ; DRV_INIT6: ; LOOP THRU ALL UNITS AVAILABLE + PUSH HL ; PRESERVE DRIVE LIST PTR + LD C,(HL) ; GET UNIT NUM FROM LIST PUSH BC ; PRESERVE LOOP CONTROL LD B,BF_DIODEVICE ; HBIOS FUNC: REPORT DEVICE INFO RST 08 ; CALL HBIOS, D := DEVICE TYPE @@ -2460,6 +2564,8 @@ DRV_INIT6: ; LOOP THRU ALL UNITS AVAILABLE CALL DRV_INIT7 ; MAKE DRIVE MAP ENTRY(S) POP BC ; RESTORE LOOP CONTROL INC C ; INCREMENT LIST INDEX + POP HL ; RESTORE DRIVE LIST PTR + INC HL ; INCREMENT ACTIVE DRIVE LIST PTR DJNZ DRV_INIT6 ; LOOP AS NEEDED RET ; FINISHED ; @@ -2473,7 +2579,22 @@ DRV_INIT7: ; PROCESS UNIT LD A,(HDSPV) ; GET SLICES PER VOLUME TO ACCUM LD B,A ; MOVE TO B FOR LOOP COUNTER ; -DRV_INIT8: ; SLICE CREATION LOOP +DRV_INIT8: + ; SLICE CREATION LOOP + ; DE=UNIT/SLICE, B=SLICE CNT + LD A,(BOOTVOL + 1) ; GET BOOT UNIT + CP 1 ; ROM BOOT? + JR Z,DRV_INIT8A ; IF SO, OK TO CONTINUE + CP D ; COMPARE TO CUR UNIT + JR NZ,DRV_INIT8A ; IF NE, OK TO CONTINUE + LD A,(BOOTVOL) ; GET BOOT SLICE + CP E ; COMPARE TO CUR SLICE + JR NZ,DRV_INIT8A ; IF NE, OK TO CONTINUE + INC E ; IS BOOT DU/SLICE, SKIP IT + DJNZ DRV_INIT8 ; LOOP AS NEEDED + RET ; DONE +; +DRV_INIT8A: ; ENTRY POINT TO SKIP BOOT DISK/LU CHECK ; ; INC DRVMAP ENTRY COUNT AND ENFORCE FOR 16 ENTRY MAXIMUM LD HL,(DRVMAPADR) ; POINT TO DRIVE MAP @@ -2486,7 +2607,6 @@ DRV_INIT8: ; SLICE CREATION LOOP ; ALLOCATE ENTRY AND FILL IN UNIT, SLICE LD HL,4 ; 4 BYTES PER ENTRY CALL ALLOC ; ALLOCATE SPACE - ;CALL NZ,PANIC ; SHOULD NEVER ERROR HERE CALL C,PANIC ; SHOULD NEVER ERROR HERE LD (HL),D ; SAVE UNIT IN FIRST BYTE OF DRVMAP ENTRY INC HL ; POINT TO NEXT BYTE OF DRVMAP ENTRY @@ -2521,7 +2641,6 @@ DPH_INIT: ADD HL,HL ; ... OF DPH (16) ADD HL,HL ; ... FOR TOTAL SIZE CALL ALLOC ; ALLOCATE THE SPACE - ;CALL NZ,PANIC ; SHOULD NEVER ERROR CALL C,PANIC ; SHOULD NEVER ERROR ; ; SET DPHTOP TO START OF ALLOCATED SPACE @@ -2530,7 +2649,6 @@ DPH_INIT: ; ALLOCATE DIRECTORY BUFFER LD HL,128 ; SIZE OF DIRECTORY BUFFER CALL ALLOC ; ALLOCATE THE SPACE - ;CALL NZ,PANIC ; SHOULD NEVER ERROR CALL C,PANIC ; SHOULD NEVER ERROR LD (DIRBUF),HL ; ... AND SAVE IN DIRBUF ; @@ -2559,26 +2677,12 @@ DPH_INIT1: CALL PRTDRV ; PRINT DRIVE INFO LD A,D ; A := UNIT PUSH HL ; SAVE DRIVE MAP POINTER - PUSH AF ; SAVE UNIT - ; MATCH AND SAVE DEFAULT DRIVE BASED ON BOOT UNIT/SLICE - LD HL,BOOTVOL + 1 ; POINT TO BOOT UNIT - LD A,D ; LOAD CURRENT UNIT - CP (HL) ; MATCH? - JR NZ,DPH_INIT1A ; BYPASS IF NOT BOOT UNIT - DEC HL ; POINT TO BOOT SLICE - LD A,E ; LOAD CURRENT SLICE - CP (HL) ; MATCH? - JR NZ,DPH_INIT1A ; BYPASS IF NOT BOOT SLICE - LD A,C ; LOAD THE CURRENT DRIVE NUM - LD (DEFDRIVE),A ; SAVE AS DEFAULT DPH_INIT1A: - POP AF ; RESTORE UNIT LD DE,(DPHTOP) ; GET ADDRESS OF NEXT DPH PUSH DE ; ... AND SAVE IT ; INVOKE THE DPH BUILD ROUTINE PUSH BC ; SAVE LOOP CONTROL CALL MAKDPH ; MAKE THE DPH AT DE, UNIT IN A - ;CALL NZ,PANIC ; FOR NOW, PANIC ON ANY ERROR POP BC ; RESTORE LOOP CONTROL ; STORE THE DPH POINTER IN DRIVE MAP POP DE ; RESTORE DPH ADDRESS TO DE @@ -2705,7 +2809,6 @@ MAKDPH2: PUSH HL ; MOVE ALLOC RESULT PTR POP BC ; ... TO BC POP HL ; RECOVER DPH PTR TO HL - ;JR NZ,ERR_HEAPOVF ; HANDLE POSSIBLE ALLOC OVERFLOW HERE JR C,ERR_HEAPOVF ; HANDLE POSSIBLE ALLOC OVERFLOW HERE LD (HL),C ; SAVE CKS/ALS BUF INC HL ; ... ADDRESS IN @@ -2724,7 +2827,6 @@ ALLOC: PUSH DE ; AND SAVE FOR RETURN VALUE ADD HL,DE ; ADD REQUESTED SPACE, HL := NEW HEAP TOP JR C,ALLOCX ; TEST FOR CPU MEMORY SPACE OVERFLOW - ;LD DE,(HEAPLIM) ; LOAD DE WITH HEAP LIMIT LD DE,HEAPEND ; LOAD DE WITH HEAP LIMIT EX DE,HL ; DE=NEW HEAPTOP, HL=HEAPLIM SBC HL,DE ; HEAPLIM - HEAPTOP @@ -2880,8 +2982,16 @@ DPHTOP .DW 0 ; CURRENT TOP OF DPH POOL DIRBUF .DW 0 ; DIR BUF POINTER HEAPTOP .DW BUFPOOL ; CURRENT TOP OF HEAP BOOTVOL .DW 0 ; BOOT VOLUME, MSB=BOOT UNIT, LSB=BOOT SLICE -BNKRAMD .DB 0 ; STARTING BANK ID FOR RAM DRIVE HDSPV .DB 2 ; SLICES PER VOLUME FOR HARD DISKS (MUST BE >= 1) +DRVLST .FILL 32 ; ACTIVE DRIVE LIST USED DURINT DRV_INIT +DRVLSTC .DB 0 ; ENTRY COUNT FOR ACTIVE DRIVE LIST +; +#IFDEF PLTWBW +BNKRAMD .DB 0 ; STARTING BANK ID FOR RAM DRIVE (WBW) +#ENDIF +#IFDEF PLTUNA +BNKRAMD .DW 0 ; STARTING BANK ID FOR RAM DRIVE (UNA) +#ENDIF ; CMD .DB CMDLEN - 2 .TEXT "SUBMIT PROFILE" diff --git a/Source/CBIOS/ver.inc b/Source/CBIOS/ver.inc deleted file mode 100644 index 00c91863..00000000 --- a/Source/CBIOS/ver.inc +++ /dev/null @@ -1,5 +0,0 @@ -#DEFINE RMJ 2 -#DEFINE RMN 9 -#DEFINE RUP 1 -#DEFINE RTP 0 -#DEFINE BIOSVER "2.9.1-pre.11" diff --git a/Source/CPM22/Build.cmd b/Source/CPM22/Build.cmd index 93a836cb..5c1effaa 100644 --- a/Source/CPM22/Build.cmd +++ b/Source/CPM22/Build.cmd @@ -32,6 +32,14 @@ zx MLOAD25 -OS2CCP.BIN=OS2CCP.HEX zx MAC -OS3BDOS.ASM -$PO zx MLOAD25 -OS3BDOS.BIN=OS3BDOS.HEX +tasm -t80 -g3 -fFF loader.asm loader.bin loader.lst + +copy /b os2ccp.bin + os3bdos.bin + ..\cbios\cbios_wbw.bin cpm_wbw.bin +copy /b os2ccp.bin + os3bdos.bin + ..\cbios\cbios_una.bin cpm_una.bin + +copy /b loader.bin + cpm_wbw.bin cpm_wbw.sys +copy /b loader.bin + cpm_una.bin cpm_una.sys + goto :eof :asm diff --git a/Source/CPM22/Clean.cmd b/Source/CPM22/Clean.cmd index e2e6145a..243e3c08 100644 --- a/Source/CPM22/Clean.cmd +++ b/Source/CPM22/Clean.cmd @@ -5,3 +5,4 @@ if exist *.bin del *.bin if exist *.lst del *.lst if exist *.prn del *.prn if exist *.hex del *.hex +if exist *.sys del *.sys diff --git a/Source/CPM22/Makefile b/Source/CPM22/Makefile new file mode 100644 index 00000000..b1f37b0d --- /dev/null +++ b/Source/CPM22/Makefile @@ -0,0 +1,16 @@ +SYSFILES = cpm_wbw.sys cpm_una.sys +BINFILES = cpm_wbw.bin cpm_una.bin +OBJECTS = CCP.bin BDOS.bin CCP22.bin BDOS22.bin OS2CCP.bin OS3BDOS.bin \ + ccpb03.bin bdosb01.bin loader.bin $(SYSFILES) $(BINFILES) +OTHERS = *.hex +TOOLS = ../../Tools +include $(TOOLS)/Makefile.inc + +%.sys: %.bin loader.bin + cat loader.bin $*.bin > $@ + +cpm_una.bin: OS2CCP.bin OS3BDOS.bin ../CBIOS/cbios_una.bin + cat OS2CCP.bin OS3BDOS.bin ../CBIOS/cbios_una.bin > $@ + +cpm_wbw.bin: OS2CCP.bin OS3BDOS.bin ../CBIOS/cbios_wbw.bin + cat OS2CCP.bin OS3BDOS.bin ../CBIOS/cbios_wbw.bin > $@ diff --git a/Source/CPM22/ccpb03.asm b/Source/CPM22/ccpb03.asm index dd3c8462..5fef8040 100644 --- a/Source/CPM22/ccpb03.asm +++ b/Source/CPM22/ccpb03.asm @@ -803,7 +803,7 @@ RESETDR:LD A,(CHGDRV) ; DRIVE CHANGE INDICATED? ;************************************************************** ; .IF MON -MONITOR:RST 38 +MONITOR:RST 38H .ENDIF ; diff --git a/Source/CPM22/loader.asm b/Source/CPM22/loader.asm new file mode 100644 index 00000000..bdebc314 --- /dev/null +++ b/Source/CPM22/loader.asm @@ -0,0 +1,226 @@ +;=============================================================================== +; LOADER.ASM +; +; BOOTLOADER FOR ROMWBW DISK OPERATING SYSTEMS. +; +; CP/M DISK FORMATS ALLOW FOR RESERVED TRACKS THAT CONTAIN AN IMAGE OF THE +; OPERATING SYSTEM TO BE LOADED WHEN THE DISK IS BOOTED. THE OPERATING SYSTEM +; IMAGE ITSELF IS NORMALLY PREFIXED BY A 1-N SECTORS CONTAINING OS BOOTSTRAP +; CODE AND DISK METADATA. +; +; THE RETROBREW COMPUTING GROUP HAS BEEN USING A CONVENTION OF PREFIXING THE +; OS IMAGE WITH 3 SECTORS (512 BYTES X 3 FOR A TOTAL OF 1536 BYTES): +; +; SECTOR 1: IBM-PC STYLE BOOT BLOCK CONTAINING BOOTSTRAP, +; PARTITION TABLE, AND BOOT SIGNATURE +; SECTOR 2: RESERVED +; SECTOR 3: METADATA +; +; THE HARDWARE BIOS IS EXPECTED TO READ AND LOAD THE FIRST TWO SECTORS FROM THE +; DISK TO MEMORY ADDRESS $8000 AND JUMP TO THAT LOCATION TO BEGIN THE BOOT +; PROCESS. THE BIOS IS EXPECTED TO VERIFY THAT A STANDARD BOOT SIGNATURE +; OF $55, $AA IS PRESENT AT OFFSET $1FE-$1FF. IF THE SIGNATURE IS NOT FOUND, +; THE BIOS SHOULD ASSUME THE DISK HAS NOT BEEN PROPERLY INITIALIZED AND SHOULD +; NOT JUMP TO THE LOAD ADDRESS. +; +;=============================================================================== +; +#INCLUDE "../ver.inc" +; +SYS_ENT .EQU $E600 ; SYSTEM (OS) ENTRY POINT ADDRESS +SYS_LOC .EQU $D000 ; STARTING ADDRESS TO LOAD SYSTEM IMAGE +SYS_END .EQU $FE00 ; ENDING ADDRESS OF SYSTEM IMAGE +; +SEC_SIZE .EQU 512 ; DISK SECTOR SIZE +BLK_SIZE .EQU 128 ; OS BLOCK/RECORD SIZE +; +PREFIX_SIZE .EQU (SEC_SIZE * 3) ; 3 SECTORS +; +META_SIZE .EQU 32 ; SEE BELOW +META_LOC .EQU (PREFIX_SIZE - META_SIZE) +; +PT_LOC .EQU $1BE +PT_SIZ .EQU $40 +; +;------------------------------------------------------------------------------- +; SECTOR 1 +; +; THIS SECTOR FOLLOWS THE CONVENTIONS OF AN IBM-PC MBR CONTAINING THE OS +; BOOTSTRAP CODE, PARTITION TABLE, AND BOOT SIGNATURE +; +;---------------------------------------------------------------------------- +; +; THE FOLLOWING BOOTSTRAP CODE IS BUILT TO ASSUME IT WILL BE EXECUTED AT A STARTING +; ADDRESS OF $8000. THIS CODE IS *ONLY* FOR UNA. THE ROMWBW ROM BOOTLOADER +; USES THE METADATA TO LOAD THE OS DIRECTLY. +; + .ORG $8000 + JR BOOT +; +BOOT: + LD DE,STR_LOAD ; LOADING STRING + CALL PRTSTR ; PRINT + CALL PRTDOT ; PROGRESS +; + LD BC,$00FC ; UNA FUNC: GET BOOTSTRAP HISTORY + CALL $FFFD ; CALL UNA + JR NZ,ERROR ; HANDLE ERROR + CALL PRTDOT ; PROGRESS + LD B,L ; MOVE BOOT UNIT ID TO B +; + LD C,$41 ; UNA FUNC: SET LBA + LD DE,0 ; HI WORD ALWAYS ZERO + LD HL,3 ; IMAGE STARTS AT FOURTH SECTOR + CALL $FFFD ; SET LBA + JR NZ,ERROR ; HANDLE ERROR + CALL PRTDOT ; PROGRESS +; + LD C,$42 ; UNA FUNC: READ SECTORS + LD DE,$D000 ; STARTING ADDRESS FOR IMAGE + LD L,22 ; READ 22 SECTORS + CALL $FFFD ; DO READ + JR NZ,ERROR ; HANDLE ERROR + CALL PRTDOT ; PROGRESS +; + LD DE,STR_DONE ; DONE MESSAGE + CALL PRTSTR ; PRINT IT +; + LD D,B ; PASS BOOT UNIT TO OS + LD E,0 ; ASSUME LU IS ZERO + JP SYS_ENT ; GO TO SYSTEM +; +PRTCHR: + PUSH BC + PUSH DE + LD BC,$0012 ; UNIT 0, WRITE CHAR + LD E,A ; CHAR TO PRINT + CALL $FFFD ; PRINT + POP DE + POP BC + RET +; +PRTSTR: + PUSH BC + PUSH HL + LD BC,$0015 ; UNIT 0, WRITE CHARS UNTIL TERMINATOR + LD L,0 ; TERMINATOR IS NULL + CALL $FFFD ; PRINT + POP HL + POP BC + RET +; +PRTDOT: + LD A,'.' ; DOT CHARACTER + JR PRTCHR ; PRINT AND RETURN +; +; PRINT THE HEX BYTE VALUE IN A +; +PRTHEXBYTE: + PUSH AF + PUSH DE + CALL HEXASCII + LD A,D + CALL PRTCHR + LD A,E + CALL PRTCHR + POP DE + POP AF + RET +; +; CONVERT BINARY VALUE IN A TO ASCII HEX CHARACTERS IN DE +; +HEXASCII: + LD D,A + CALL HEXCONV + LD E,A + LD A,D + RLCA + RLCA + RLCA + RLCA + CALL HEXCONV + LD D,A + RET +; +; CONVERT LOW NIBBLE OF A TO ASCII HEX +; +HEXCONV: + AND $0F ;LOW NIBBLE ONLY + ADD A,$90 + DAA + ADC A,$40 + DAA + RET +; +ERROR: + LD DE,STR_ERR ; POINT TO ERROR STRING + CALL PRTSTR ; PRINT IT + HALT ; HALT +; +; DATA +; +STR_LOAD .DB "\r\nLoading",0 +STR_DONE .DB "\r\n",0 +STR_ERR .DB " Read Error!",0 +; + .ORG $ - $8000 ; BACK TO ABSOLUTE ADDRESS +; + .FILL PT_LOC - $,0 ; FILL TO START OF PARTITION TABLE +; +; RESERVE SPACE FOR STANDARD IBM-PC PARTITION TABLE. ALTHOUGH A +; PARTITION TABLE IS NOT RELEVANT FOR A FLOPPY DISK, IT DOES NO HARM. +; THE CONTENTS OF THE PARTITION TABLE MUST BE MANAGED BY FDISK80. +; +PARTTBL .FILL PT_SIZ,0 ; PARTITION TABLE, FILL WITH ZEROES +; +; THE END OF THE FIRST SECTOR MUST CONTAIN THE TWO BYTE BOOT SIGNATURE. +; +BOOTSIG .DB $55,$AA ; STANDARD BOOT SIGNATURE +; +;------------------------------------------------------------------------------- +; SECTOR 2 +; +; THIS SECTOR HAS NOT BEEN DEFINED AND IS RESERVED. +; +;---------------------------------------------------------------------------- +; + .FILL SEC_SIZE,0 ; JUST FILL SECTOR WITH ZEROES +; +;------------------------------------------------------------------------------- +; SECTOR 3 +; +; OS AND DISK METADATA +; +;---------------------------------------------------------------------------- +; + .FILL (BLK_SIZE * 3),0 ; FIRST 384 BYTES ARE NOT YET DEFINED +; +; THE FOLLOWING TWO BYTES ARE AN ADDITIONAL SIGNATURE THAT IS VERIFIED BY +; SOME HARDWARE BIOSES. +; +PR_SIG .DB $5A,$A5 ; SIGNATURE GOES HERE +; + .FILL (META_LOC - $),0 +; +; METADATA +; +PR_WP .DB 0 ; (1) WRITE PROTECT BOOLEAN +PR_UPDSEQ .DW 0 ; (2) PREFIX UPDATE SEQUENCE NUMBER (DEPRECATED?) +PR_VER .DB RMJ,RMN,RUP,RTP ; (4) OS BUILD VERSION +PR_LABEL .DB "Unlabeled$$$$$$$","$" ; (17) DISK LABEL (EXACTLY 16 BYTES!!!) + .DW 0 ; (2) DEPRECATED +PR_LDLOC .DW SYS_LOC ; (2) ADDRESS TO START LOADING SYSTEM +PR_LDEND .DW SYS_END ; (2) ADDRESS TO STOP LOADING SYSTEM +PR_ENTRY .DW SYS_ENT ; (2) ADDRESS TO ENTER SYSTEM (OS) +; +#IF (META_SIZE != ($ - META_LOC)) + .ECHO "META_SIZE VALUE IS WRONG!!!\r\n" + !!! +#ENDIF +; +#IF ($ != PREFIX_SIZE) + .ECHO "LOADER PREFIX IS WRONG SIZE!!!\r\n" + !!! +#ENDIF +; + .END diff --git a/Source/CPM3/Build.cmd b/Source/CPM3/Build.cmd new file mode 100644 index 00000000..1d0e1a9c --- /dev/null +++ b/Source/CPM3/Build.cmd @@ -0,0 +1,103 @@ +@echo off +setlocal + +set TOOLS=../../Tools + +set PATH=%TOOLS%\tasm32;%TOOLS%\zx;%TOOLS%\cpmtools;%PATH% + +set TASMTABS=%TOOLS%\tasm32 + +set ZXBINDIR=%TOOLS%/cpm/bin/ +set ZXLIBDIR=%TOOLS%/cpm/lib/ +rem set ZXINCDIR=%TOOLS%/cpm/include/ +set ZXINCDIR=../ + +echo. +echo. +echo *** CPM Loader *** +echo. +zx RMAC -CPMLDR +copy optdsk.lib ldropts.lib +zx Z80ASM -BIOSLDR/MF +move /Y biosldr.rel biosldrd.rel +zx LINK -CPMLDRD[L100]=CPMLDR,BIOSLDRD +move /Y cpmldrd.com cpmldr.bin +copy optcmd.lib ldropts.lib +zx Z80ASM -BIOSLDR/MF +move /Y biosldr.rel biosldrc.rel +zx LINK -CPMLDRC[L100]=CPMLDR,BIOSLDRC +move /Y cpmldrc.com cpmldr.com +rem pause + +echo. +echo. +echo *** Resident CPM3 BIOS *** +echo. +copy optres.lib options.lib +copy genres.dat gencpm.dat +zx RMAC -BIOSKRNL +zx RMAC -SCB +zx Z80ASM -BOOT/MF +zx Z80ASM -CHARIO/MF +zx Z80ASM -MOVE/MF +zx Z80ASM -DRVTBL/MF +zx Z80ASM -DISKIO/MF +zx Z80ASM -UTIL/MF +zx LINK -BIOS3[OS]=BIOSKRNL,SCB,BOOT,CHARIO,MOVE,DRVTBL,DISKIO,UTIL +zx GENCPM -AUTO -DISPLAY +copy cpm3.sys cpm3res.sys +rem pause + +echo. +echo. +echo *** Banked CPM3 BIOS *** +echo. +copy optbnk.lib options.lib +copy genbnk.dat gencpm.dat +zx RMAC -BIOSKRNL +zx RMAC -SCB +zx Z80ASM -BOOT/MF +zx Z80ASM -CHARIO/MF +zx Z80ASM -MOVE/MF +zx Z80ASM -DRVTBL/MF +zx Z80ASM -DISKIO/MF +zx Z80ASM -UTIL/MF +zx LINK -BNKBIOS3[B]=BIOSKRNL,SCB,BOOT,CHARIO,MOVE,DRVTBL,DISKIO,UTIL +zx GENCPM -AUTO -DISPLAY +copy cpm3.sys cpm3bnk.sys +rem pause + +echo. +echo. +echo *** Banked ZPM3 BIOS *** +echo. +copy optzpm.lib options.lib +copy genbnk.dat gencpm.dat +zx RMAC -BIOSKRNL +zx RMAC -SCB +zx Z80ASM -BOOT/MF +zx Z80ASM -CHARIO/MF +zx Z80ASM -MOVE/MF +zx Z80ASM -DRVTBL/MF +zx Z80ASM -DISKIO/MF +zx Z80ASM -UTIL/MF +zx LINK -ZPMBIOS3[B]=BIOSKRNL,SCB,BOOT,CHARIO,MOVE,DRVTBL,DISKIO,UTIL +rem zx GENCPM -AUTO -DISPLAY +rem copy cpm3.sys zpm3.sys +rem pause + +rem *** Resident *** + +rem copy cpm3res.sys cpm3.sys +rem copy genres.dat getcpm.dat + +rem *** Banked *** + +copy cpm3bnk.sys cpm3.sys +copy genbnk.dat gencpm.dat + +rem Loader + +tasm -t80 -g3 -fFF loader.asm loader.bin loader.lst + +copy /b loader.bin + cpmldr.bin cpmldr.sys diff --git a/Source/CPM3/Clean.cmd b/Source/CPM3/Clean.cmd new file mode 100644 index 00000000..950d6b8b --- /dev/null +++ b/Source/CPM3/Clean.cmd @@ -0,0 +1,16 @@ +@echo off +setlocal + +if exist bios3.spr del bios3.spr +if exist bnkbios3.spr del bnkbios3.spr +if exist zpmbios3.spr del zpmbios3.spr +if exist *.rel del *.rel +if exist cpmldr.com del cpmldr.com +if exist *.err del *.err +if exist *.lst del *.lst +if exist *.sym del *.sym +if exist *.sys del *.sys +if exist *.bin del *.bin +if exist gencpm.dat del gencpm.dat +if exist options.lib del options.lib +if exist ldropts.lib del ldropts.lib diff --git a/Source/CPM3/Makefile b/Source/CPM3/Makefile new file mode 100644 index 00000000..4eb2a352 --- /dev/null +++ b/Source/CPM3/Makefile @@ -0,0 +1,91 @@ +# +# this makefile does double duty. it serves as the top level make +# and as the invoked make for the different ways that the cpm3 is built +# +# it does this by overriding OBJECTS in an invoked sub-make +# +OBJECTS = cpmldr.com cpmldr.sys cpm3res cpm3bnk zpmbios3 cpm3.sys gencpm.dat +OTHERS = cpmldr.rel biosldr.rel cpm3res.sys cpm3bnk.sys zpmbios3.spr loader.bin cpmldr.bin +OTHERS += biosldrc.rel biosldrd.rel + +TOOLS = ../../Tools +include $(TOOLS)/Makefile.inc + +export ZXINCDIR = ../ + +BIOSOBJS = bioskrnl.rel scb.rel boot.rel chario.rel +BIOSOBJS += move.rel drvtbl.rel diskio.rel util.rel +COMMA := , +NULL := +SPACE := $(NULL) $(NULL) +BIOSNAMES := $(subst $(SPACE),$(COMMA),$(basename $(BIOSOBJS))) + +DEFCPM3 = bnk +#DEFCPM3 = res + +clean:: biosclean + @rm -f bios3.spr bnkbios3.spr zpmbios3.spr cpmldr.com gencpm.dat options.lib ldropts.lib + +biosclean: + @rm -f $(BIOSOBJS) + +cpm3res: + make biosclean + cp optres.lib options.lib + cp genres.dat gencpm.dat + make OBJECTS=bios3.spr + $(ZXCC) gencpm -auto -display + mv cpm3.sys cpm3res.sys + rm gencpm.dat + +cpm3bnk: + make biosclean + cp optbnk.lib options.lib + cp genbnk.dat gencpm.dat + make OBJECTS=bnkbios3.spr + $(ZXCC) gencpm -auto -display + mv cpm3.sys cpm3bnk.sys + rm gencpm.dat + +zpmbios3: + make biosclean + cp optzpm.lib options.lib + cp genbnk.dat gencpm.dat + make OBJECTS=zpmbios3.spr + rm gencpm.dat + +cpmldr.bin: biosldrd.rel cpmldr.rel + $(ZXCC) $(TOOLS)/cpm/bin/LINK -CPMLDRD[L100]=CPMLDR,BIOSLDRD + mv cpmldrd.com cpmldr.bin + +cpmldr.com: biosldrc.rel cpmldr.rel + $(ZXCC) $(TOOLS)/cpm/bin/LINK -CPMLDRC[L100]=CPMLDR,BIOSLDRC + mv cpmldrc.com cpmldr.com + +biosldrc.rel: biosldr.z80 optcmd.lib + cp optcmd.lib ldropts.lib + $(ZXCC) $(TOOLS)/cpm/bin/Z80ASM -BIOSLDR/MF + mv biosldr.rel biosldrc.rel + +biosldrd.rel: biosldr.z80 optdsk.lib + cp optdsk.lib ldropts.lib + $(ZXCC) $(TOOLS)/cpm/bin/Z80ASM -BIOSLDR/MF + mv biosldr.rel biosldrd.rel + +bios3.spr: $(BIOSOBJS) + $(ZXCC) $(CPM)/LINK -bios3[OS]=$(BIOSNAMES) + +bnkbios3.spr: $(BIOSOBJS) + $(ZXCC) $(CPM)/LINK -bnkbios3[B]=$(BIOSNAMES) + +zpmbios3.spr: $(BIOSOBJS) + $(ZXCC) $(TOOLS)/cpm/bin/LINK -zpmbios3[B]=$(BIOSNAMES) + +cpm3.sys: cpm3$(DEFCPM3).sys + cp cpm3$(DEFCPM3).sys cpm3.sys + +gencpm.dat: gen$(DEFCPM3).dat + cp gen$(DEFCPM3).dat gencpm.dat + +cpmldr.sys: loader.bin cpmldr.bin + cat loader.bin cpmldr.bin > $@ diff --git a/Source/CPM3/bdos3.spr b/Source/CPM3/bdos3.spr new file mode 100644 index 00000000..728fe2af Binary files /dev/null and b/Source/CPM3/bdos3.spr differ diff --git a/Source/CPM3/bioskrnl.asm b/Source/CPM3/bioskrnl.asm new file mode 100644 index 00000000..ab3a9ccd --- /dev/null +++ b/Source/CPM3/bioskrnl.asm @@ -0,0 +1,650 @@ + title 'Root module of relocatable BIOS for CP/M 3.0' + + ; version 1.0 15 Sept 82 + + maclib options + + +; Copyright (C), 1982 +; Digital Research, Inc +; P.O. Box 579 +; Pacific Grove, CA 93950 + + +; This is the invariant portion of the modular BIOS and is +; distributed as source for informational purposes only. +; All desired modifications should be performed by +; adding or changing externally defined modules. +; This allows producing "standard" I/O modules that +; can be combined to support a particular system +; configuration. + +cr equ 13 +lf equ 10 +bell equ 7 +ctlQ equ 'Q'-'@' +ctlS equ 'S'-'@' + +ccp equ 0100h ; Console Command Processor gets loaded into the TPA + + cseg ; GENCPM puts CSEG stuff in common memory + + + ; variables in system data page + + extrn @covec,@civec,@aovec,@aivec,@lovec ; I/O redirection vectors + extrn @mxtpa ; addr of system entry point + extrn @bnkbf ; 128 byte scratch buffer + + ; initialization + + extrn ?init ; general initialization and signon + extrn ?ldccp,?rlccp ; load & reload CCP for BOOT & WBOOT + + ; user defined character I/O routines + + extrn ?ci,?co,?cist,?cost ; each take device in + extrn ?cinit ; (re)initialize device in + extrn @ctbl ; physical character device table + + ; disk communication data items + + extrn @dtbl ; table of pointers to XDPHs + public @adrv,@rdrv,@trk,@sect ; parameters for disk I/O + public @dma,@dbnk,@cnt ; '' '' '' '' + + ; memory control + + public @cbnk ; current bank + extrn ?xmove,?move ; select move bank, and block move + extrn ?bank ; select CPU bank + + ; clock support + + extrn ?time ; signal time operation + + ; general utility routines + + public ?pmsg,?pdec ; print message, print number from 0 to 65535 + public ?pderr ; print BIOS disk error message header + + maclib modebaud ; define mode bits + + + ; External names for BIOS entry points + + public ?boot,?wboot,?const,?conin,?cono,?list,?auxo,?auxi + public ?home,?sldsk,?sttrk,?stsec,?stdma,?read,?write + public ?lists,?sctrn + public ?conos,?auxis,?auxos,?dvtbl,?devin,?drtbl + public ?mltio,?flush,?mov,?tim,?bnksl,?stbnk,?xmov + + + ; BIOS Jump vector. + + ; All BIOS routines are invoked by calling these + ; entry points. + +?boot: jmp boot ; initial entry on cold start +?wboot: jmp wboot ; reentry on program exit, warm start + +?const: jmp const ; return console input status +?conin: jmp conin ; return console input character +?cono: jmp conout ; send console output character +?list: jmp list ; send list output character +?auxo: jmp auxout ; send auxilliary output character +?auxi: jmp auxin ; return auxilliary input character + +?home: jmp home ; set disks to logical home +?sldsk: jmp seldsk ; select disk drive, return disk parameter info +?sttrk: jmp settrk ; set disk track +?stsec: jmp setsec ; set disk sector +?stdma: jmp setdma ; set disk I/O memory address +?read: jmp read ; read physical block(s) +?write: jmp write ; write physical block(s) + +?lists: jmp listst ; return list device status +?sctrn: jmp sectrn ; translate logical to physical sector + +?conos: jmp conost ; return console output status +?auxis: jmp auxist ; return aux input status +?auxos: jmp auxost ; return aux output status +?dvtbl: jmp devtbl ; return address of device def table +?devin: jmp ?cinit ; change baud rate of device + +?drtbl: jmp getdrv ; return address of disk drive table +?mltio: jmp multio ; set multiple record count for disk I/O +?flush: jmp flush ; flush BIOS maintained disk caching + +?mov: jmp ?move ; block move memory to memory +?tim: jmp ?time ; Signal Time and Date operation +?bnksl: jmp bnksel ; select bank for code execution and default DMA +?stbnk: jmp setbnk ; select different bank for disk I/O DMA operations. +?xmov: jmp ?xmove ; set source and destination banks for one operation + + jmp 0 ; reserved for future expansion + jmp 0 ; reserved for future expansion + jmp 0 ; reserved for future expansion + + + ; BOOT + ; Initial entry point for system startup. + + dseg ; this part can be banked + +boot: + lxi sp,boot$stack + mvi c,15 ; initialize all 16 character devices +c$init$loop: + push b ! call ?cinit ! pop b + dcr c ! jp c$init$loop + + call ?init ; perform any additional system initialization + ; and print signon message + + lxi b,16*256+0 ! lxi h,@dtbl ; init all 16 logical disk drives +d$init$loop: + push b ; save remaining count and abs drive + mov e,m ! inx h ! mov d,m ! inx h ; grab @drv entry + mov a,e ! ora d ! jz d$init$next ; if null, no drive + push h ; save @drv pointer + xchg ; XDPH address in + dcx h ! dcx h ! mov a,m ! sta @RDRV ; get relative drive code + mov a,c ! sta @ADRV ; get absolute drive code + dcx h ; point to init pointer + mov d,m ! dcx h ! mov e,m ; get init pointer + xchg ! call ipchl ; call init routine + pop h ; recover @drv pointer +d$init$next: + pop b ; recover counter and drive # + inr c ! dcr b ! jnz d$init$loop ; and loop for each drive + jmp boot$1 + + cseg ; following in resident memory + +boot$1: + call set$jumps + call ?ldccp ; fetch CCP for first time + jmp ccp + + + ; WBOOT + ; Entry for system restarts. + +wboot: + lxi sp,boot$stack + call set$jumps ; initialize page zero + call ?rlccp ; reload CCP + jmp ccp ; then reset jmp vectors and exit to ccp + + +set$jumps: + + if banked + mvi a,1 ! call ?bnksl + endif + + mvi a,JMP + sta 0 ! sta 5 ; set up jumps in page zero + lxi h,?wboot ! shld 1 ; BIOS warm start entry + lhld @MXTPA ! shld 6 ; BDOS system call entry + ret + + + ds 64 +boot$stack equ $ + + + ; DEVTBL + ; Return address of character device table + +devtbl: + lxi h,@ctbl ! ret + + + ; GETDRV + ; Return address of drive table + +getdrv: + lxi h,@dtbl ! ret + + + + ; CONOUT + ; Console Output. Send character in + ; to all selected devices + +conout: + + lhld @covec ; fetch console output bit vector + jmp out$scan + + + ; AUXOUT + ; Auxiliary Output. Send character in + ; to all selected devices + +auxout: + lhld @aovec ; fetch aux output bit vector + jmp out$scan + + + ; LIST + ; List Output. Send character in + ; to all selected devices. + +list: + lhld @lovec ; fetch list output bit vector + +out$scan: + mvi b,0 ; start with device 0 +co$next: + dad h ; shift out next bit + jnc not$out$device + push h ; save the vector + push b ; save the count and character +not$out$ready: + push b ! call coster ! pop b ! ora a ! jz not$out$ready + pop b ! push b ; restore and resave the character and device + call ?co ; if device selected, print it + pop b ; recover count and character + pop h ; recover the rest of the vector +not$out$device: + inr b ; next device number + mov a,h ! ora l ; see if any devices left + jnz co$next ; and go find them... + ret + + + ; CONOST + ; Console Output Status. Return true if + ; all selected console output devices + ; are ready. + +conost: + lhld @covec ; get console output bit vector + jmp ost$scan + + + ; AUXOST + ; Auxiliary Output Status. Return true if + ; all selected auxiliary output devices + ; are ready. + +auxost: + lhld @aovec ; get aux output bit vector + jmp ost$scan + + + ; LISTST + ; List Output Status. Return true if + ; all selected list output devices + ; are ready. + +listst: + lhld @lovec ; get list output bit vector + +ost$scan: + mvi b,0 ; start with device 0 +cos$next: + dad h ; check next bit + push h ; save the vector + push b ; save the count + mvi a,0FFh ; assume device ready + cc coster ; check status for this device + pop b ; recover count + pop h ; recover bit vector + ora a ; see if device ready + rz ; if any not ready, return false + inr b ; drop device number + mov a,h ! ora l ; see if any more selected devices + jnz cos$next + ori 0FFh ; all selected were ready, return true + ret + +coster: ; check for output device ready, including optional + ; xon/xoff support + mov l,b ! mvi h,0 ; make device code 16 bits + push h ; save it in stack + dad h ! dad h ! dad h ; create offset into device characteristics tbl + lxi d,@ctbl+6 ! dad d ; make address of mode byte + mov a,m ! ani mb$xonxoff + pop h ; recover console number in + jz ?cost ; not a xon device, go get output status direct + lxi d,xofflist ! dad d ; make pointer to proper xon/xoff flag + call cist1 ; see if this keyboard has character + mov a,m ! cnz ci1 ; get flag or read key if any + cpi ctlq ! jnz not$q ; if its a ctl-Q, + mvi a,0FFh ; set the flag ready +not$q: + cpi ctls ! jnz not$s ; if its a ctl-S, + mvi a,00h ; clear the flag +not$s: + mov m,a ; save the flag + call cost1 ; get the actual output status, + ana m ; and mask with ctl-Q/ctl-S flag + ret ; return this as the status + +cist1: ; get input status with and saved + push b ! push h + call ?cist + pop h ! pop b + ora a + ret + +cost1: ; get output status, saving & + push b ! push h + call ?cost + pop h ! pop b + ora a + ret + +ci1: ; get input, saving & + push b ! push h + call ?ci + pop h ! pop b + ret + + + ; CONST + ; Console Input Status. Return true if + ; any selected console input device + ; has an available character. + +const: + lhld @civec ; get console input bit vector + jmp ist$scan + + + ; AUXIST + ; Auxiliary Input Status. Return true if + ; any selected auxiliary input device + ; has an available character. + +auxist: + lhld @aivec ; get aux input bit vector + +ist$scan: + mvi b,0 ; start with device 0 +cis$next: + dad h ; check next bit + mvi a,0 ; assume device not ready + cc cist1 ; check status for this device + ora a ! rnz ; if any ready, return true + inr b ; drop device number + mov a,h ! ora l ; see if any more selected devices + jnz cis$next + xra a ; all selected were not ready, return false + ret + + + ; CONIN + ; Console Input. Return character from first + ; ready console input device. + +conin: + lhld @civec + jmp in$scan + + + ; AUXIN + ; Auxiliary Input. Return character from first + ; ready auxiliary input device. + +auxin: + lhld @aivec + +in$scan: + push h ; save bit vector + mvi b,0 +ci$next: + dad h ; shift out next bit + mvi a,0 ; insure zero a (nonexistant device not ready). + cc cist1 ; see if the device has a character + ora a + jnz ci$rdy ; this device has a character + inr b ; else, next device + mov a,h ! ora l ; see if any more devices + jnz ci$next ; go look at them + pop h ; recover bit vector + jmp in$scan ; loop til we find a character + +ci$rdy: + pop h ; discard extra stack + jmp ?ci + + +; Utility Subroutines + + +ipchl: ; vectored CALL point + pchl + + +?pmsg: ; print message @ up to a null + ; saves & + push b + push d +pmsg$loop: + mov a,m ! ora a ! jz pmsg$exit + mov c,a ! push h + call ?cono ! pop h + inx h ! jmp pmsg$loop +pmsg$exit: + pop d + pop b + ret + +?pdec: ; print binary number 0-65535 from + lxi b,table10! lxi d,-10000 +next: + mvi a,'0'-1 +pdecl: + push h! inr a! dad d! jnc stoploop + inx sp! inx sp! jmp pdecl +stoploop: + push d! push b + mov c,a! call ?cono + pop b! pop d +nextdigit: + pop h + ldax b! mov e,a! inx b + ldax b! mov d,a! inx b + mov a,e! ora d! jnz next + ret + +table10: + dw -1000,-100,-10,-1,0 + +?pderr: + lxi h,drive$msg ! call ?pmsg ; error header + lda @adrv ! adi 'A' ! mov c,a ! call ?cono ; drive code + lxi h,track$msg ! call ?pmsg ; track header + lhld @trk ! call ?pdec ; track number + lxi h,sector$msg ! call ?pmsg ; sector header + lhld @sect ! call ?pdec ; sector number + ret + + + ; BNKSEL + ; Bank Select. Select CPU bank for further execution. + +bnksel: + sta @cbnk ; remember current bank + jmp ?bank ; and go exit through users + ; physical bank select routine + + +xofflist db -1,-1,-1,-1,-1,-1,-1,-1 ; ctl-s clears to zero + db -1,-1,-1,-1,-1,-1,-1,-1 + + + + dseg ; following resides in banked memory + + + +; Disk I/O interface routines + + + ; SELDSK + ; Select Disk Drive. Drive code in . + ; Invoke login procedure for drive + ; if this is first select. Return + ; address of disk parameter header + ; in + +seldsk: + mov a,c ! sta @adrv ; save drive select code + mov l,c ! mvi h,0 ! dad h ; create index from drive code + lxi b,@dtbl ! dad b ; get pointer to dispatch table + mov a,m ! inx h ! mov h,m ! mov l,a ; point at disk descriptor + ora h ! rz ; if no entry in table, no disk + mov a,e ! ani 1 ! jnz not$first$select ; examine login bit + push h ! xchg ; put pointer in stack & + lxi h,-2 ! dad d ! mov a,m ! sta @RDRV ; get relative drive + lxi h,-6 ! dad d ; find LOGIN addr + mov a,m ! inx h ! mov h,m ! mov l,a ; get address of LOGIN routine + call ipchl ; call LOGIN + pop h ; recover DPH pointer +not$first$select: + ret + + + ; HOME + ; Home selected drive. Treated as SETTRK(0). + +home: + lxi b,0 ; same as set track zero + + + ; SETTRK + ; Set Track. Saves track address from + ; in @TRK for further operations. + +settrk: + mov l,c ! mov h,b + shld @trk + ret + + + ; SETSEC + ; Set Sector. Saves sector number from + ; in @sect for further operations. + +setsec: + mov l,c ! mov h,b + shld @sect + ret + + + ; SETDMA + ; Set Disk Memory Address. Saves DMA address + ; from in @DMA and sets @DBNK to @CBNK + ; so that further disk operations take place + ; in current bank. + +setdma: + mov l,c ! mov h,b + shld @dma + + lda @cbnk ; default DMA bank is current bank + ; fall through to set DMA bank + + ; SETBNK + ; Set Disk Memory Bank. Saves bank number + ; in @DBNK for future disk data + ; transfers. + +setbnk: + sta @dbnk + ret + + + ; SECTRN + ; Sector Translate. Indexes skew table in + ; with sector in . Returns physical sector + ; in . If no skew table (=0) then + ; returns physical=logical. + +sectrn: + mov l,c ! mov h,b + mov a,d ! ora e ! rz + xchg ! dad b ! mov l,m ! mvi h,0 + ret + + + ; READ + ; Read physical record from currently selected drive. + ; Finds address of proper read routine from + ; extended disk parameter header (XDPH). + +read: + lhld @adrv ! mvi h,0 ! dad h ; get drive code and double it + lxi d,@dtbl ! dad d ; make address of table entry + mov a,m ! inx h ! mov h,m ! mov l,a ; fetch table entry + push h ; save address of table + lxi d,-8 ! dad d ; point to read routine address + jmp rw$common ; use common code + + + ; WRITE + ; Write physical sector from currently selected drive. + ; Finds address of proper write routine from + ; extended disk parameter header (XDPH). + +write: + lhld @adrv ! mvi h,0 ! dad h ; get drive code and double it + lxi d,@dtbl ! dad d ; make address of table entry + mov a,m ! inx h ! mov h,m ! mov l,a ; fetch table entry + push h ; save address of table + lxi d,-10 ! dad d ; point to write routine address + +rw$common: + mov a,m ! inx h ! mov h,m ! mov l,a ; get address of routine + pop d ; recover address of table + dcx d ! dcx d ; point to relative drive + ldax d ! sta @rdrv ; get relative drive code and post it + inx d ! inx d ; point to DPH again + pchl ; leap to driver + + + ; MULTIO + ; Set multiple sector count. Saves passed count in + ; @CNT + +multio: + sta @cnt ! ret + + + ; FLUSH + ; BIOS deblocking buffer flush. Not implemented. + +flush: + xra a ! ret ; return with no error + + + + ; error message components +drive$msg db cr,lf,bell,'BIOS Error on ',0 +track$msg db ': T-',0 +sector$msg db ', S-',0 + + + ; disk communication data items + +@adrv ds 1 ; currently selected disk drive +@rdrv ds 1 ; controller relative disk drive +@trk ds 2 ; current track number +@sect ds 2 ; current sector number +@dma ds 2 ; current DMA address +@cnt db 0 ; record count for multisector transfer +@dbnk db 0 ; bank for DMA operations + + + cseg ; common memory + +@cbnk db 0 ; bank for processor operations + + + end diff --git a/Source/CPM3/biosldr.z80 b/Source/CPM3/biosldr.z80 new file mode 100644 index 00000000..9fc960d3 --- /dev/null +++ b/Source/CPM3/biosldr.z80 @@ -0,0 +1,536 @@ + + maclib ldropts.lib + + maclib cpm3.lib + + cseg + +; BIOS Jump vector. + +; All BIOS routines are invoked by calling these +; entry points. + +?boot: jp boot ; initial entry on cold start +?wboot: jp wboot ; reentry on program exit, warm start + +?const: jp const ; return console input status +?conin: jp conin ; return console input character +?cono: jp conout ; send console output character +?list: jp list ; send list output character +?auxo: jp auxout ; send auxilliary output character +?auxi: jp auxin ; return auxilliary input character + +?home: jp home ; set disks to logical home +?sldsk: jp seldsk ; select disk drive, return disk parameter info +?sttrk: jp settrk ; set disk track +?stsec: jp setsec ; set disk sector +?stdma: jp setdma ; set disk I/O memory address +?read: jp read ; read physical block(s) +?write: jp write ; write physical block(s) + +?lists: jp listst ; return list device status +?sctrn: jp sectrn ; translate logical to physical sector + +?conos: jp conost ; return console output status +?auxis: jp auxist ; return aux input status +?auxos: jp auxost ; return aux output status +?dvtbl: jp devtbl ; return address of device def table +?devin: jp devini ; change baud rate of device + +?drtbl: jp drvtbl ; return address of disk drive table +?mltio: jp multio ; set multiple record count for disk I/O +?flush: jp flush ; flush BIOS maintained disk caching + +?mov: jp move ; block move memory to memory +?tim: jp time ; Signal Time and Date operation +?bnksl: jp selmem ; select bank for code execution and default DMA +?stbnk: jp setbnk ; select different bank for disk I/O DMA operations. +?xmov: jp xmove ; set source and destination banks for one operation + + jp 0 ; reserved for future expansion + jp 0 ; reserved for future expansion + jp 0 ; reserved for future expansion + +boot: + + if cmdline + + ld (stksav),sp + ld sp,stack + +boot1: + ld de,msgunit + call writestr + call cin + push af + call cout + pop af + + sub '0' + ld (unit),a + jr c,selerr + + ld bc,0F810h ; HBIOS, get disk unit count + call 0FFF0h ; do it, E := disk unit count + ld a,(unit) ; get unit num back + cp e ; compare to entry + jr nc,selerr ; loop if too high + + ld de,msgslc + call writestr + call cin + push af + call cout + pop af + + sub '0' + ld (slice),a + jr c,selerr + cp 10 + jr nc,selerr + + jr boot2 + +selerr: + ld de,msginv + call writestr + jr boot1 + +boot2: + ld de,crlf + call writestr + + ld sp,(stksav) + + ld bc,0F9E0h ; HBIOS func: set boot info + ld a,(unit) ; get unit + ld d,a ; put in D + ld a,(slice) ; get slice + ld e,a ; put in E + ld l,0 ; Bank is always zero + call 0FFF0h ; do it + + else + + ld bc,0F8E0h ; HBIOS func: get boot info + call 0FFF0h ; do it, D := boot unit, E: := slice + ld a,d ; move unit to A + ld (unit),a ; save it + ld a,e ; move slice to A + ld (slice),a ; save it + + endif + + ld a,(unit) ; Get boot unit + ld c,a ; put in C + ld b,18h ; HBIOS Media function + ld e,1 ; Enable media check/discovery + call 0FFF0H ; HBIOS call + ld a,e ; Resultant media id to accum + or a ; Set flags + ;halt + ; + ; !!! Need to do something on error !!! + ; + ret z ; Bail out on error + + ld hl,dpb$start - dpb$sz + ld de,dpb$sz + ld b,a ; loop count +dsk$login1: + add hl,de ; next dpb + djnz dsk$login1 ; loop as needed + + ; hl is ptr to desired dpb + ld de,dph0 ; load DPH pointer + ex de,hl ; de = DPB adr, hl = DPH adr + push de ; save DPB adr + ld de,12 ; offset of DPB in DPH + add hl,de ; hl = adr of DPB field in DPH + pop de ; recover DPB adr + ld (hl),e ; update LSB + inc hl + ld (hl),d ; udpate MSB + + ret + +wboot: + ld a,81H + halt + +const: + ld a,82H + halt +conin: + ld bc,0080H ; unit 80h (console), func 0 = CIN + call 0FFF0H + +conout: + ld e,c ; output character in E + ld bc,0180H ; unit 80h (console), func 1 = COUT + ;rst 08 ; do it + call 0FFF0H + ret ; return +list: + ld a,85H + halt +auxout: + ld a,86H + halt +auxin: + ld a,87H + halt + +home: + ld hl,0 + ld (trk),hl + ret +seldsk: + ld hl,dph0 + ret +settrk: + ld (trk),bc + ret +setsec: + ld (sect),bc + ret +setdma: + ld (dma),bc + ret +read: + ld a,(unit) ; get unit + ld c,a ; BIOS Disk Unit in C + ld b,12H ; HBIOS SEEK function + push bc ; save it + ;push de ; save XDPH pointer + ld b,17h ; HBIOS DEVICE function + rst 08 ; Do it, D=device type + ld a,d ; put in accum + and 0F0h ; isolate high bits + ld b,1 ; assume it is floppy, 1 head bit + ld c,01h ; 1 bit head mask + cp 10h ; floppy? + jr z,seek0 ; yup, skip ahead + ld b,4 ; must be hard disk, 4 head bits + ld c,0Fh ; 4 bit head mask +seek0: + ;pop de ; recover XDPH pointer + push bc ; save bc + ld a,(slice) ; get slice + ld e,a ; slice to E + ld h,65 ; number of tracks per slice + call mult8 ; HL now has track offset for slice + pop bc ; recover bc + push hl ; save track offset for now + ld hl,(trk) ; get track value + ld a,l ; lsb of track to a + and c ; apply mask + ld d,a ; save in d +seek1: + srl h ; shift one bit out + rr l ; ... of hl + djnz seek1 ; do all bits + ld a,(sect) ; get sector + ld e,a ; stuff it in e + ex de,hl ; de=track, hl=head/sect + ex (sp),hl ; save head/sect, hl = offset + add hl,de ; hl has final track value + pop de ; recover head/sect to de + pop bc ; recover function & unit + ;rst 08 ; perform seek + call 0FFF0H + + ; Read Sector + ld b,13h ; HBIOS read + ld a,(unit) ; get boot unit + ld c,a ; put in C + ld hl,(dma) ; dma address + ld a,(0FFE0H) ; current bank + ld d,a ; ... to D + ld e,1 ; 1 sector + ;rst 08 + call 0FFF0H + + ret +write: + ld a,8EH + halt + +listst: + ld a,8FH + halt +sectrn: + ld h,b + ld l,c + ret + +conost: + ld a,91H + halt +auxist: + ld a,92H + halt +auxost: + ld a,93H + halt +devtbl: + ld a,94H + halt +devini: + ld a,95H + halt + +drvtbl: + ld a,96H + halt +multio: + ld a,97H + halt +flush: + ld a,98H + halt + +move: + ex de,hl ; we are passed source in DE and dest in HL + ldir ; use Z80 block move instruction + ex de,hl ; need next addresses in same regs + ret +time: + ld a,9AH + halt +selmem: + ld a,9BH + halt +setbnk: + ld a,9CH + halt +xmove: + ld a,9DH + halt + +cin: + ; input character from console via hbios + ld c,080H ; console unit to c + ld b,00H ; hbios func: input char + call 0FFF0H ; hbios reads character + ld a,e ; move character to a for return + ret + +cout: + ; output character to console via hbios + ld e,a ; output char to e + ld c,080H ; console unit to c + ld b,01H ; hbios func: output char + call 0FFF0H ; hbios outputs character + ret + +writestr: + push af +writestr1: + ld a,(de) + cp '$' ; test for string terminator + jp z,writestr2 + push de + call cout + pop de + inc de + jp writestr1 +writestr2: + pop af + ret + +; +; multiply 8-bit values +; in: multiply h by e +; out: hl = result, e = 0, b = 0 +; +mult8: + ld d,0 + ld l,d + ld b,8 +mult8_loop: + add hl,hl + jr nc,mult8_noadd + add hl,de +mult8_noadd: + djnz mult8_loop + ret + +msgunit db 13,10,13,10,'Boot CP/M 3 from Disk Unit: $' +msgslc db ' Slice: $' +msginv db 13,10,13,10,'*** Invalid Selection ***$' +crlf db 13,10,'$' + +dpb$start: +dpb$rom: ; 384K ROM Drive + dw 64 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 1 ; exm: extent mask + dw 192 - 1 ; dsm: total storage in blocks - 1 = (384kb / 2k bls) - 1 = 191 + dw 256 - 1 ; drm: dir entries - 1 = 256 - 1 = 255 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 8000h ; cks: directory check vector size - permanent storage = 8000H + dw 0 ; off: reserved tracks = 16 trks * (16 trks * 16 heads * 16 secs * 512 bytes) = 128k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 +dpb$sz equ $ - dpb$start + +dpb$ram: ; 256K RAM Drive + dw 64 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 1 ; exm: extent mask + dw 128 - 1 ; dsm: total storage in blocks - 1 = (256kb / 2k bls) - 1 = 127 + dw 256 - 1 ; drm: dir entries - 1 = 256 - 1 = 255 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 8000h ; cks: directory check vector size - permanent storage = 8000H + dw 0 ; off: reserved tracks = 16 trks * (16 trks * 16 heads * 16 secs * 512 bytes) = 128k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb$rf: ; 4MB RAM Floppy Drive + dw 64 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 0 ; exm: extent mask + dw 2047 ; dsm: total storage in blocks - 1 = (4mb / 2k bls) - 1 = 2047 + dw 255 ; drm: dir entries - 1 = 256 - 1 = 255 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 8000h ; cks: directory check vector size - permanent storage = 8000H + dw 0 ; off: reserved tracks = 0 trks + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb$hd: ; 8MB Hard Disk Drive + dw 64 ; spt: sectors per track + db 5 ; bsh: block shift factor + db 31 ; blm: block mask + db 1 ; exm: extent mask + dw 2047 ; dsm: total storage in blocks - 1 = (8mb / 4k bls) - 1 = 2047 + dw 511 ; drm: dir entries - 1 = 512 - 1 = 511 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 8000h ; cks: directory check vector size - permanent storage = 8000H + dw 16 ; off: reserved tracks = 16 trks * (16 trks * 16 heads * 16 secs * 512 bytes) = 128k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb$fd720: ; 3.5" DS/DD Floppy Drive (720K) + dw 36 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 0 ; exm: extent mask + dw 350 ; dsm: total storage in blocks - 1 blk = ((720k - 18k off) / 2k bls) - 1 = 350 + dw 127 ; drm: dir entries - 1 = 128 - 1 = 127 + db 11000000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 32 ; cks: directory check vector size = 128 / 4 + dw 4 ; off: reserved tracks = 4 trks * (512 b/sec * 36 sec/trk) = 18k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb_fd144: ; 3.5" DS/HD Floppy Drive (1.44M) + dw 72 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 0 ; exm: extent mask + dw 710 ; dsm: total storage in blocks - 1 blk = ((1,440k - 18k off) / 2k bls) - 1 = 710 + dw 255 ; drm: dir entries - 1 = 256 - 1 = 255 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 64 ; cks: directory check vector size = 256 / 4 + dw 2 ; off: reserved tracks = 2 trks * (512 b/sec * 72 sec/trk) = 18k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb_fd360: ; 5.25" DS/DD Floppy Drive (360K) + dw 36 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 1 ; exm: extent mask + dw 170 ; dsm: total storage in blocks - 1 blk = ((360k - 18k off) / 2k bls) - 1 = 170 + dw 127 ; drm: dir entries - 1 = 128 - 1 = 127 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 32 ; cks: directory check vector size = 128 / 4 + dw 4 ; off: reserved tracks = 4 trks * (512 b/sec * 36 sec/trk) = 18k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb_fd120: ; 5.25" DS/HD Floppy Drive (1.2M) + dw 60 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 0 ; exm: extent mask + dw 591 ; dsm: total storage in blocks - 1 blk = ((1,200k - 15k off) / 2k bls) - 1 = 591 + dw 255 ; drm: dir entries - 1 = 256 - 1 = 255 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 64 ; cks: directory check vector size = 256 / 4 + dw 2 ; off: reserved tracks = 2 trks * (512 b/sec * 60 sec/trk) = 15k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb_fd111: ; 8" DS/DD Floppy Drive (1.11M) + dw 60 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 0 ; exm: extent mask + dw 569 ; dsm: total storage in blocks - 1 blk = ((1,155k - 15k off) / 2k bls) - 1 = 569 + dw 255 ; drm: dir entries - 1 = 256 - 1 = 255 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 64 ; cks: directory check vector size = 256 / 4 + dw 2 ; off: reserved tracks = 2 trks * (512 b/sec * 60 sec/trk) = 15k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dph0: dw 0 ; xlt, 0 means no translation + db 0,0,0,0,0,0,0,0,0 ; scratch (9 bytes) + db 0 ; mf: media flag + dw dpb$hd ; dpb + dw csvbuf ; csv: + dw alvbuf ; alv: + dw dirbcb ; dirbcb + dw dtabcb ; dtabcb + dw 0ffffh ; hash (disabled) + db 0 ; hbank + +dtbl: dtbl dph0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + +dirbcb: db 0ffh ; drv + db 0,0,0 ; rec# + db 0 ; wflg + db 0 ; scratch + dw 0 ; track + dw 0 ; sector + dw dirbuf ; buffad + +dtabcb: db 0ffh ; drv + db 0,0,0 ; rec# + db 0 ; wflg + db 0 ; scratch + dw 0 ; track + dw 0 ; sector + dw dtabuf ; buffad + +unit ds 1 ; HBIOS unit number +slice ds 1 ; HBIOS slice number +trk ds 2 ; current track +sect ds 2 ; current sector +dma ds 2 ; current DMA address + +csvbuf ds 128 ; length (CSV) = ((DRM+1)/4) +alvbuf ds 512 ; length (ALV) = ((DSM+1)/4) +dirbuf ds 512 ; sector buffer +dtabuf ds 512 ; sector buffer + + ds 64 +stack equ $ +stksav dw 0 + + end diff --git a/Source/CPM3/bnkbdos3.spr b/Source/CPM3/bnkbdos3.spr new file mode 100644 index 00000000..97c8aee4 Binary files /dev/null and b/Source/CPM3/bnkbdos3.spr differ diff --git a/Source/CPM3/boot.z80 b/Source/CPM3/boot.z80 new file mode 100644 index 00000000..53e36f47 --- /dev/null +++ b/Source/CPM3/boot.z80 @@ -0,0 +1,730 @@ + title 'Boot loader module for CP/M 3.0' + + maclib options.lib + + public ?init,?ldccp,?rlccp,?time + public @bootdu,@bootsl + extrn ?pmsg,?conin + extrn ?mvinit,?bnkxlt,?xmove,?move + extrn @civec,@covec,@aivec,@aovec,@lovec + extrn @cbnk,?bnksl,?bank + extrn @sysdr,@ccpdr + extrn dph0 + extrn @dtbl,@ctbl + extrn @date,@hour,@min,@sec + extrn @srch1 + extrn @hbbio + extrn addhla, bcd2bin, bin2bcd + extrn cout, phex8, phex16, crlf, crlf2 + + include c:ver.lib + +bdos equ 5 + + if banked +tpa$bank equ 1 + else +tpa$bank equ 0 + endif + + dseg ; init done from banked memory + +?init: + call ?mvinit + + ; Install RomWBW CBIOS stamp in page zero + ld hl,stpimg + ld de,stploc + ld bc,stpsiz + ldir + + if banked + + ; Clone page zero from bank 0 to additional banks + ld b,3 ; last bank + ld c,0 ; src bank +init$2: + push bc ; save bank id's + call ?xmove ; set src/dest banks + ld bc,0100h ; size is one page + ld hl,0 ; dest adr is 0 + ld de,0 ; src adr is 0 + call ?move ; do it + pop bc ; restore bank id's + djnz init$2 ; loop till done + + endif + + call cinit ; char device init + ld hl,signon$msg ; signon message + call ?pmsg ; print it + + ; Get boot disk unit and save it + ld bc,0F8E0h ; HBIOS func: get boot info + rst 08 ; do it, D := boot unit, E: := boot slice + ld a,d ; move boot unit to A + ld (@bootdu),a ; save it + ld a,e ; move boot slice to A + ld (@bootsl),a ; save it + + call dinit + call clrram + ret + +cinit: + ; Setup CON: I/O vector based on HBIOS console device + ld b,0FAh ; HBIOS Peek Function + ld a,(@hbbio) ; HBIOS bank id + ld d,a ; ... goes in D + ld hl,112h ; Offset 112h is current console device + rst 08 ; Call HBIOS, value in E + push de ; save console unit value + ld b,e ; Use as loop counter + inc b ; ... but loop 1 extra time + ld hl,0 ; Clear vector bitmap + scf ; Set carry +cinit$1: + rr h ; Rotate carry flag + rr l ; ... into correct vector position + djnz cinit$1 ; loop as needed + + ld (@civec),hl ; assign to console input + ld (@covec),hl ; assign to console output + + ; Setup AUX: I/O vector if there are 2+ char devices in system + ld bc,0F800h ; HBIOS GET Character Device Count + rst 08 ; do it, count in E + ld a,e ; device count to accum + pop de ; recover console unit num to E + push af ; save device count + cp 2 ; check for 2+ char devices + jr c,cinit$3 ; if not, skip aux assignment + ld a,e ; console unit num to A + or a ; check for zero + ld hl,4000h ; assume aux on second char device + jr z,cinit$2 ; if console on unit 0, assumption good + ld hl,8000h ; otherwise, aux goes to first char device +cinit$2: + ld (@aivec),hl ; assign to aux input + ld (@aovec),hl ; assign to aux output +cinit$3: + pop af ; recover device count + ; Truncate char table based on actual num of char devices + rlca ; A still has char device count + rlca ; * 8 for ctbl entry size + rlca ; " + ld hl,@ctbl ; Start of char table + call addhla ; Skip used entries + xor a ; Zero to accum + ld (hl),0 ; Set table terminator + ret ; done + +dinit: + ; loop through all disk devices to count hard disk units + ld b,0F8h ; SYS GET + ld c,010h ; Disk Drive Unit Count + rst 08 ; e := disk unit count + ld b,e ; count to b + ld a,b ; count to a + or a ; set flags + ret z ; !!! handle zero devices (albeit poorly) !!! + + ; loop thru devices to count total hard disk volumes + ld c,0 ; init c as device list index + ld d,0 ; init d as total device count + ld e,0 ; init e for hard disk device count + ld hl,drvlst ; init hl ptr to drive list +; +dinit2: + call dinit3 ; check drive + inc c ; next unit + djnz dinit2 ; loop + ld a,d ; total device count to d + ld (drvlstc),a ; save the count + jr dinit4 ; continue + +dinit3: + push de ; save de (hard disk volume counter) + push hl ; save drive list ptr + push bc ; save loop control + ld b,17h ; hbios func: report device info + rst 08 ; call hbios, unit to c + ld a,d ; device type to a + pop bc ; restore loop control + pop hl ; restore drive list ptr + pop de ; restore de + cp 30h ; hard disk device? + jr nc,dinit3a ; if so, handle special + ld (hl),c ; save unit num in list + inc hl ; bump ptr + inc d ; inc total device count + ret +; +dinit3a: + ; check for active and return if not + push de ; save de (hard disk volume counter) + push hl ; save drive list ptr + push bc ; save loop control + + ld b,18h ; hbios func: sense media + ld e,1 ; perform media discovery + rst 08 + + pop bc ; restore loop control + pop hl ; restore drive list ptr + pop de ; restore de + + ret nz ; if no media, just return + + ; if active... + ld (hl),c ; save unit num in list + inc hl ; bump ptr + inc d ; inc total device count + inc e ; increment hard disk count + ret ; and return + +dinit4: ; set slices per volume (hdspv) based on hard disk volume count + ld a,e ; hard disk volume count to a + ld e,8 ; assume 8 slices per volume + dec a ; dec accum to check for count = 1 + jr z,dinit5 ; yes, skip ahead to implement 8 hdspv + ld e,4 ; now assume 4 slices per volume + dec a ; dec accum to check for count = 2 + jr z,dinit5 ; yes, skip ahead to implement 4 hdspv + ld e,2 ; in all other cases, we use 2 hdspv + +dinit5: + ld a,e ; slices per volume value to accum + ld (hdspv),a ; save it + ld hl,0 ; dph index + + ld a,(@bootdu) ; boot disk unit + ld d,a ; ... to d + ld a,(@bootsl) ; boot slice + ld e,a ; ... to e + ld b,1 ; one slice please + call dinit8a ; make DPH for A: + + ld a,(drvlstc) ; active drive list count to accum + ld b,a ; ... and move to b for loop counter + ld de,drvlst ; de is ptr to active drive list + +dinit6: + ; loop thru all units available + push de ; preserve drive list ptr + ex de,hl ; list ptr to hl + ld c,(hl) ; get unit num from list + ex de,hl ; list ptr back to de + push bc ; preserve loop control + push hl ; preserve dph pointer + ld b,17h ; hbios func: report device info + rst 08 ; call hbios, d := device type + pop hl ; restore dph pointer + pop bc ; get unit index back in c + push bc ; resave loop control + call dinit7 ; make drive map entry(s) + pop bc ; restore loop control + inc c ; increment list index + pop de ; restore drive list ptr + inc de ; increment active drive list ptr + djnz dinit6 ; loop as needed + + ; zero out remaining dph table entries + ld a,16 ; dph table entries + sub l ; subtract entries used + ret z ; return if all entries used + ld b,a ; save as loop counter + ld a,l ; current dph to accum + rlca ; *2 for word entry + ld hl,@dtbl ; start of dtbl + call addhla ; hl now points to entry + +dinit6a: + xor a ; zero accum + ld (hl),a ; zero lsb + inc hl ; next byte + ld (hl),a ; zero msb + inc hl ; next byte + djnz dinit6a + ret ; finished + +dinit7: ; process a unit (all slices) + ld e,0 ; initialize slice index + ld b,1 ; default loop counter + ld a,d ; device type to accum + ld d,c ; unit number to d + cp 030h ; hard disk device? + jr c,dinit8 ; nope, leave loop count at 1 + ld a,(hdspv) ; get slices per volume to accum + ld b,a ; move to b for loop counter + +dinit8: ; test to avoid reallocating boot disk unit/slice + ld a,(@bootdu) ; boot disk unit to accum + cp d ; compare to cur unit + jr nz,dinit8a ; if ne, ok to continue + ld a,(@bootsl) ; boot slice to accum + cp e ; compare to cur slice + jr nz,dinit8a ; if ne, ok to continue + inc e ; is boot du/slice, skip it + djnz dinit8 ; loop till done with unit + ret + +dinit8a: + ; d=unit, e=slice, l=dph#, b=slice cnt + ld a,l ; dph # to accum + cp 16 ; dph table size + ret z ; bail out if overflow + push hl ; save dph # + rlca ; *2 for adr entry + ld hl,@dtbl ; dph table start + call addhla ; offset hl to desired entry + ld a,(hl) ; dereference + inc hl + ld h,(hl) + ld l,a + dec hl ; backup to slice field + ld (hl),e ; update slice number + dec hl ; backup to unit number + ld (hl),d ; update unit number + pop hl ; restore dph # + inc hl ; next dph # + inc e ; next slice + djnz dinit8 ; loop till done with unit + ret + +; RomWBW CBIOS page zero stamp starts at $40 +; $40-$41: Marker ('W', ~'W') +; $42-$43: Version bytes: major/minor, update/patch +; $44-$45: CBIOS Extension Info address + +stploc equ 40h +stpimg db 'W',~'W' ; marker + db rmj << 4 | rmn ; first byte of version info + db rup << 4 | rtp ; second byte of version info + dw cbx ; address of cbios ext data +stpsiz equ $ - stpimg + + + cseg ; ram disk init must be done from resident memory + +; +; Initialize ram disk by filling directory with 'e5' bytes +; Fill first 8k of ram disk track 1 with 'e5' +; +clrram: + di ; no interrupts + ld a,(0FFE0h) ; get current bank + push af ; save it + ;ld a,(bnkramd) ; first bank of ram disk + ld a,080h ; first bank of ram disk + ;call hb_bnksel ; select bank + call 0FFF3h ; select bank + + ; Check first 32 directory entries. If any start with an invalid + ; value, init the ram disk. Valid entries are e5 (empty entry) or + ; 0-15 (user number). + ld hl,0 + ld de,32 + ld b,32 +clrram0: + ld a,(hl) + cp 0E5h + jr z,clrram1 ; e5 is valid + cp 16 + jr c,clrram1 ; 0-15 is also valid + jr clrram2 ; invalid entry! jump to init +clrram1: + add hl,de ; loop for 32 entries + djnz clrram0 +; jr clrram2 ; *debug* + jr clrram3 ; all entries valid, bypass init +clrram2: + ld hl,0 ; source adr for fill + ld bc,2000h ; length of fill is 8k + ld a,0E5h ; fill value + call fill ; do it + or 0ffh ; flag value for cleared + ld (clrflg),a ; save it +clrram3: + ;ld a,(bnkuser) ; usr bank (tpa) + pop af ; recover original bank + ;call hb_bnksel ; select bank + call 0FFF3h ; select bank + ei ; resume interrupts + + ld a,(clrflg) ; get cleared flag + or a ; set flags + ld hl,clr$msg ; clear ram disk message + call nz,?pmsg ; print msg if true + + ret + +; +; Fill memory at hl with value a, length in bc. All regs used. +; Length *must* be greater than 1 for proper operation!!! +; +fill: + ld d,h ; set de to hl + ld e,l ; so destination equals source + ld (hl),a ; fill the first byte with desired value + inc de ; increment destination + dec bc ; decrement the count + ldir ; do the rest + ret ; return + + + cseg ; boot loading most be done from resident memory + + ; This version of the boot loader loads the CCP from a file + ; called CCP.COM on the system drive. + +?ldccp: + ; Force CCP to use system boot drive as initial default + ld a,(@sysdr) ; get system boot drive + ld (@ccpdr),a ; set CCP current drive + + ; First time, load the CCP.COM file into TPA + ld a,(@sysdr) ; get system boot drive + ;ld (4),a ; save in page zero??? + inc a ; drive + 1 for FCB + ld (ccp$fcb),a ; stuff into FCB + add 'A' - 1 ; drive letter + ld (ccp$msg$drv),a ; save for load msg + xor a + ld (ccp$fcb+15),a + ld hl,0 + ld (fcb$nr),hl + ld de,ccp$fcb + call open + inc a + jr z,no$CCP + ld de,0100H + call setdma + ld de,128 + call setmulti + ld de,ccp$fcb + call read + + if banked + + ld hl,0100h ; clone 3K, just in case + ld bc,0C80h + ld a,(@cbnk) ; save current bank + push af +ld$1: + ld a,tpa$bank ; select TPA + call ?bnksl + ld a,(hl) ; get a byte + push af + ld a,2 ; select extra bank + call ?bnksl + pop af ; save the byte + ld (hl),a + inc hl ; bump pointer, drop count + dec bc + ld a,b ; test for done + or c + jr nz,ld$1 + pop af ; restore original bank + call ?bnksl + + endif + + ret + +no$CCP: ; here if we couldn't find the file + ld hl,ccp$msg + call ?pmsg + call ?conin + jp ?ldccp + + +?rlccp: + + if banked + + ld hl,0100h ; clone 3K + ld bc,0C80h +rl$1: + ld a,2 ; select extra bank + call ?bnksl + ld a,(hl) ; get a byte + push af + ld a,tpa$bank ; select TPA + call ?bnksl + pop af ; save the byte + ld (hl),a + inc hl ; bump pointer, drop count + dec bc + ld a,b ; test for done + or c + jr nz,rl$1 + ret + + else + + jr ?ldccp + + endif + +?time: + ; per CP/M 3 docs, *must* preserve HL, DE + push hl + push de + + ; force return through time$ret + ld hl,time$ret + push hl + + ; branch to get or set routine + ld a,c ; get switch value + or a ; test for zero + jr z,time$get ; 0 means get time + jr time$set ; else set time + +time$ret: + ; restore HL, DE + pop de + pop hl + ret + +time$get: + ; RTC -> cpm date/time in SCB + + ; read time from RTC + ld b,020h ; HBIOS func: get time + ld hl,tim$buf ; time buffer + rst 08 ; do it + ret nz ; bail out on error + + ld a,(datehack) + or a + jr nz,time$get1 + + ; convert yymmss in time buffer -> cpm3 epoch date offset + call date2cpm ; time buf (yr, mon, day) -> SCB (@date) + +time$get1: + ; set time fields in SCB + ld a,(tim$hr) ; get hour from time buf + ld (@hour),a ; ... and put in SCB + ld a,(tim$min) ; get minute from time buf + ld (@min),a ; ... and put in SCB + ld a,(tim$sec) ; get second from time buf + ld (@sec),a ; ... and put in SCB + + ret + +time$set: + ; CPM date/time in SCB -> RTC + + ; convert CPM3 epoch date offset in SCB -> yymmss in time buffer + ;call cpm2date ; SCB (@date) -> time buf (yr, mon, day) + + ; this is a temporary hack!!! + ; since we cannot actually set the date on the RTC, we + ; just read the current RTC date and use that so that we + ; don't clobber a potentially good date. + ; read time from RTC + ld b,020h ; HBIOS func: get time + ld hl,tim$buf ; time buffer + rst 08 ; do it + ret nz ; bail out on error + ; + ; now we set a hack active flag so that future time$get + ; calls do not update the date field in the SCB + ; + ld a,0FFh ; true value + ld (datehack),a ; save it + + ; copy CPM3 time values from SCB -> time buffer + ld a,(@hour) ; get hour from SCB + ld (tim$hr),a ; ... and put in tim$hr + ld a,(@min) ; get minute from SCB + ld (tim$min),a ; ... and put in tim$min + ld a,(@sec) ; get second from SCB + ld (tim$sec),a ; ... and put in tim$sec + + ; send time to RTC + ld b,021h ; HBIOS func: set time + ld hl,tim$buf ; ... from time buffer + rst 08 ; do it + + ret + +date2cpm: + ; Convert YYMMSS from time buffer at HL + ; into offset from CPM epoch and store + ; result in SCB. + + ld hl,0 ; initialize day counter + ; Add in days for elapsed years + ld a,(tim$yr) ; get current year + call bcd2bin ; convert to binary + sub 78 ; epoch year + jr nc,d2c1 ; if not negative, good to go + add a,100 ; else, adjust for Y2K wrap +d2c1: + ld b,a ; loop counter + ld c,3 ; leap counter, 78->79->80, so 3 + ld de,365 ; days in non-leap year + or a ; check for zero + jr z,d2c10 ; skip if zero +d2c2: + add hl,de ; add non-leap days + dec c ; dec leap counter + jr nz,d2c3 ; if not leap, bypss leap inc + inc hl ; add leap day + ld c,4 ; reset leap year counter +d2c3: + djnz d2c2 ; loop for all years +d2c10: + ; Add in days for elapsed months + ex de,hl ; save HL in DE + ld hl,daystbl ; point to table of cum days by month + ld a,(tim$mon) ; get current month + call bcd2bin ; convert to binary + dec a ; index from zero + rlca ; table entries are 2 bytes + call addhla ; offset to desired month entry + ld a,(hl) ; get the entry into HL + inc hl ; ... + ld h,(hl) ; ... + ld l,a ; ... + ex de,hl ; HL = day count, DE = months count + add hl,de ; add months count into day count + ; Add leap day for current year if appropriate + dec c ; C still has leap counter + jr nz,d2c20 ; skip if not leap year + ld a,(tim$mon) ; get cur mon + cp 3 ; March? + jr c,d2c20 ; skip if mon less than March + inc hl ; add leap day for cur year +d2c20: + ; Add in days elapsed within month + ; Note that we don't adjust the date to be a zero + ; offset which seems wrong. From what I can tell + ; the CP/M epoch is really 1/0/1978 rather than the + ; 1/1/1978 that the documentation claims. Below seems + ; to work correctly. + ld a,(tim$day) ; get day + call bcd2bin ; make binary + call addhla ; add in days + ld (@date),hl ; store in SCB + ret + +cpm2date: + ; Convert CPM epoch date offset in SCB + ; into YYMMSS values and store result in + ; time buffer at HL. + ld a,019h + ld (tim$yr),a + ld a,001h + ld (tim$mon),a + ld a,001h + ld (tim$day),a + + ret + +daystbl: + ; cumulative days elapsed by month (non-leap year) + dw 0 ; January + dw 31 ; February (non-leap) + dw 59 ; March + dw 90 ; April + dw 120 ; May + dw 151 ; June + dw 181 ; July + dw 212 ; August + dw 243 ; September + dw 273 ; October + dw 304 ; November + dw 334 ; December + + ; RTC time buffer (all values packed bcd) +tim$buf: +tim$yr db 80h +tim$mon db 05h +tim$day db 10h +tim$hr db 01h +tim$min db 02h +tim$sec db 03h + +datehack db 00h + +open: + ld c,15 + jp bdos + +setdma: + ld c,26 + jp bdos + +setmulti: + ld c,44 + jp bdos + +read: + ld c,20 + jp bdos + +clrflg db 0 ; RAM disk cleared flag +clr$msg db 'RAM Disk Initialized',13,10,13,10,0 + + if zpm + +signon$msg db 13,10,'ZPM3' + if banked + db ' [BANKED]' + endif + db ' on HBIOS v' + biosver + db 13,10,13,10,0 + +ccp$msg db 13,10,'BIOS Err on ' +ccp$msg$drv db '?' + db ': No ZCCP.COM file',0 + + +ccp$fcb db 0,'ZCCP ','COM',0,0,0,0 + ds 16 +fcb$nr db 0,0,0 + + else + +signon$msg db 13,10,'CP/M v3.0' + if banked + db ' [BANKED]' + endif + db ' on HBIOS v' + biosver + db 13,10,13,10,0 + +ccp$msg db 13,10,'BIOS Err on ' +ccp$msg$drv db '?' + db ': No CCP.COM file',0 + + +ccp$fcb db 0,'CCP ','COM',0,0,0,0 + ds 16 +fcb$nr db 0,0,0 + + endif + +@bootdu db 0 ; boot disk unit +@bootsl db 0 ; boot slice +hdspv db 2 ; slices per volume for hard disks (must be >= 1) +drvlst ds 32 ; active drive list used durint drv_init +drvlstc db 0 ; entry count for active drive list + +; The following section contains key information and addresses for the +; RomWBW CBIOS. A pointer to the start of this section is stored with +; with the CBX data in page zero at $44 (see above). + +cbx: +devmapadr dw 0 ; device map address +drvtbladr dw @dtbl ; drive map address (filled in later) +dphtbladr dw dph0 ; dpb map address +cbxsiz equ $ - cbx + + end diff --git a/Source/CPM3/ccp.com b/Source/CPM3/ccp.com new file mode 100644 index 00000000..3934d840 Binary files /dev/null and b/Source/CPM3/ccp.com differ diff --git a/Source/CPM3/chario.z80 b/Source/CPM3/chario.z80 new file mode 100644 index 00000000..23db1f73 --- /dev/null +++ b/Source/CPM3/chario.z80 @@ -0,0 +1,233 @@ + title 'Character I/O handler for z80 chip based system' + +; Character I/O for the Modular CP/M 3 BIOS + + public ?cinit,?ci,?co,?cist,?cost + public @ctbl + +; maclib Z80 ; define Z80 op codes +; maclib ports ; define port addresses + maclib modebaud.lib ; define mode bits and baud equates + +;max$devices equ 6 + + cseg + +?cinit: + ret +; mov a,c ! cpi max$devices ! jz cent$init ; init parallel printer +; rnc ; invalid device +; mov l,c ! mvi h,0 ; make 16 bits from device number +; push h ; save device in stack +; dad h ! dad h ! dad h ; *8 +; lxi d,@ctbl+7 ! dad d ! mov l,m ; get baud rate +; mov a,l ! cpi baud$600 ; see if baud > 300 +; mvi a,44h ! jnc hi$speed ; if >= 600, use *16 mode +; mvi a,0C4h ; else, use *64 mode +;hi$speed: +; sta sio$reg$4 +; mvi h,0 ! lxi d,speed$table ! dad d ; point to counter entry +; mov a,m ! sta speed ; get and save ctc count +; pop h ; recover +; lxi d,data$ports ! dad d ; point at SIO port address +; mov a,m ! inr a ! sta sio$port ; get and save port +; lxi d,baud$ports-data$ports ! dad d ; offset to baud rate port +; mov a,m ! sta ctc$port ; get and save +; lxi h,serial$init$tbl +; jmp stream$out +; +;cent$init: +; lxi h,pio$init$tbl +; +;stream$out: +; mov a,m ! ora a ! rz +; mov b,a ! inx h ! mov c,m ! inx h +; outir +; jmp stream$out + + +?ci: ; character input + ;ld bc,0000H ; unit 0, func 0 = CIN + ld c,b ; Unit number to C + ld b,00h ; HBIOS CIN Function + rst 08 ; do it + ld a,e ; put char in A + ret ; done + +; mov a,b ! cpi 6 ! jnc null$input ; can't read from centronics +;ci1: +; call ?cist ! jz ci1 ; wait for character ready +; dcr c ! inp a ; get data +; ani 7Fh ; mask parity +; ret +; +;null$input: +; mvi a,1Ah ; return a ctl-Z for no device +; ret + +?cist: ; character input status + ;ld bc,0200H ; unit 0, func 2 = IST + ld c,b ; Unit number to C + ld b,02h ; HBIOS IST Function + rst 08 ; do it + or a ; set flags + ret z ; return w/ ZF set if no char ready + or 0FFH ; else signal nothing ready + ret ; done + +; mov a,b ! cpi 6 ! jnc null$status ; can't read from centronics +; mov l,b ! mvi h,0 ; make device number 16 bits +; lxi d,data$ports ! dad d ; make pointer to port address +; mov c,m ! inr c ; get SIO status port +; inp a ; read from status port +; ani 1 ; isolate RxRdy +; rz ; return with zero +; ori 0FFh +; ret + +;null$status: +; xra a ! ret + +?co: ; character output + ld e,c ; char to E + ;ld bc,0100H ; unit 0, func 1 = COUT + ld c,b ; Unit number to C + ld b,01h ; HBIOS COUT Function + rst 08 ; do it + ret ; done + +; mov a,b ! cpi 6 ! jz centronics$out +; jnc null$output +; mov a,c ! push psw ; save character from +; push b ; save device number +;co$spin: +; call ?cost ! jz co$spin ; wait for TxEmpty +; pop h ! mov l,h ! mvi h,0 ; get device number in +; lxi d,data$ports ! dad d ; make address of port address +; mov c,m ; get port address +; pop psw ! outp a ; send data +;null$output: +; ret +; +;centronics$out: +; in p$centstat ! ani 20h ! jnz centronics$out +; mov a,c ! out p$centdata ; give printer data +; in p$centstat ! ori 1 ! out p$centstat ; set strobe +; ani 7Eh ! out p$centstat ; clear strobe +; ret + +?cost: ; character output status + ;ld bc,0300H ; unit 0, func 3 = OST + ld c,b ; Unit number to C + ld b,03h ; HBIOS OST Function + rst 08 ; do it + or a ; set flags + ret z ; return w/ ZF set if not ready to send + or 0FFH ; else signal nothing ready + ret ; done + + +; mov a,b ! cpi 6 ! jz cent$stat +; jnc null$status +; mov l,b ! mvi h,0 +; lxi d,data$ports ! dad d +; mov c,m ! inr c +; inp a ; get input status +; ani 4 ! rz ; test transmitter empty +; ori 0FFh ! ret ; return true if ready +; +; +;cent$stat: +; in p$centstat ! cma +; ani 20h ! rz +; ori 0FFh ! ret + +;baud$ports: ; CTC ports by physical device number +; db p$baud$con1,p$baud$lpt1,p$baud$con2,p$baud$con34 +; db p$baud$con34,p$baud$lpt2 +; +;data$ports: ; serial base ports by physical device number +; db p$crt$data,p$lpt$data,p$con2data,p$con3data +; db p$con4data,p$lpt2data + + +@ctbl db 'COM0 ' + db mb$in$out+mb$serial+baud$none + db baud$none + db 'COM1 ' + db mb$in$out+mb$serial+baud$none + db baud$none + db 'COM2 ' + db mb$in$out+mb$serial+baud$none + db baud$none + db 'COM3 ' + db mb$in$out+mb$serial+baud$none + db baud$none + db 'COM4 ' + db mb$in$out+mb$serial+baud$none + db baud$none + db 'COM5 ' + db mb$in$out+mb$serial+baud$none + db baud$none + db 'COM6 ' + db mb$in$out+mb$serial+baud$none + db baud$none + db 'COM7 ' + db mb$in$out+mb$serial+baud$none + db baud$none + db 'COM8 ' + db mb$in$out+mb$serial+baud$none + db baud$none + db 'COM9 ' + db mb$in$out+mb$serial+baud$none + db baud$none + db 'COM10 ' + db mb$in$out+mb$serial+baud$none + db baud$none + db 'COM11 ' + db mb$in$out+mb$serial+baud$none + db baud$none + db 0 + +;@ctbl db 'CRT ' ; device 0, CRT port 0 +; db mb$in$out+mb$serial+mb$softbaud +; db baud$9600 +; db 'LPT ' ; device 1, LPT port 0 +; db mb$in$out+mb$serial+mb$softbaud+mb$xonxoff +; db baud$9600 +; db 'CRT1 ' ; device 2, CRT port 1 +; db mb$in$out+mb$serial+mb$softbaud +; db baud$9600 +; db 'CRT2 ' ; device 3, CRT port 2 +; db mb$in$out+mb$serial+mb$softbaud +; db baud$9600 +; db 'CRT3 ' ; device 4, CRT port 3 +; db mb$in$out+mb$serial+mb$softbaud +; db baud$9600 +; db 'VAX ' ; device 5, LPT port 1 used for VAX interface +; db mb$in$out+mb$serial+mb$softbaud +; db baud$9600 +; db 'CEN ' ; device 6, Centronics parallel printer +; db mb$output +; db baud$none +; db 0 ; table terminator + +;speed$table db 0,255,255,255,233,208,104,208,104,69,52,35,26,17,13,7 +; +;serial$init$tbl +; db 2 ; two bytes to CTC +;ctc$port ds 1 ; port address of CTC +; db 47h ; CTC mode byte +;speed ds 1 ; baud multiplier +; db 7 ; 7 bytes to SIO +;sio$port ds 1 ; port address of SIO +; db 18h,3,0E1h,4 +;sio$reg$4 ds 1 +; db 5,0EAh +; db 0 ; terminator +; +;pio$init$tbl db 2,p$zpio$2b,0Fh,07h +; db 3,p$zpio$2a,0CFh,0F8h,07h +; db 0 + + end diff --git a/Source/CPM3/cpm3.lib b/Source/CPM3/cpm3.lib new file mode 100644 index 00000000..6b45a43d --- /dev/null +++ b/Source/CPM3/cpm3.lib @@ -0,0 +1,180 @@ +; Macro Definitions for CP/M3 BIOS Data Structures. + + ; dtbl - drive table + + ; dph translate$table, - disk parameter header + ; disk$parameter$block, + ; checksum$size, (optional) + ; alloc$size (optional) + + ; skew sectors, - skew table + ; skew$factor, + ; first$sector$number + + ; dpb physical$sector$size, - disk parameter block + ; physical$sectors$per$track, + ; number$tracks, + ; block$size, + ; number$dir$entries, + ; track$offset, + ; checksum$vec$size (optional) + + +; Drive Table. Contains 16 one word entries. + +dtbl macro ?list + local ?n +?n aset 0 + irp ?drv, +?n aset ?n+1 + dw ?drv + endm + + if ?n > 16 +.' Too many drives. Max 16 allowed' + exitm + endif + + if ?n < 16 + rept (16-?n) + dw 0 + endm + endif + endm + +dph macro ?trans,?dpb,?csize,?asize + local ?csv,?alv + dw ?trans ; translate table address + db 0,0,0,0,0,0,0,0,0 ; BDOS Scratch area + db 0 ; media flag + dw ?dpb ; disk parameter block + if not nul ?csize + dw ?csv ; checksum vector + else + dw 0FFFEh ; checksum vector allocated by + endif ; GENCPM + if not nul ?asize + dw ?alv ; allocation vector + else + dw 0FFFEh ; alloc vector allocated by GENCPM + endif + dw 0fffeh,0fffeh,0fffeh ; dirbcb, dtabcb, hash allocd + ; by GENCPM + db 0 ; hash bank + + if not nul ?csize +?csv ds ?csize ; checksum vector + endif + if not nul ?asize +?alv ds ?asize ; allocation vector + endif + + endm + +dpb macro ?psize,?pspt,?trks,?bls,?ndirs,?off,?ncks + local ?spt,?bsh,?blm,?exm,?dsm,?drm,?al0,?al1,?cks,?psh,?psm + local ?n +;; physical sector mask and physical sector shift + ?psh aset 0 + ?n aset ?psize/128 + ?psm aset ?n-1 + rept 8 + ?n aset ?n/2 + if (?n = 0) + exitm + endif + ?psh aset ?psh + 1 + endm + ?spt aset ?pspt*(?psize/128) + + ?bsh aset 3 + ?n aset ?bls/1024 + rept 8 + ?n aset ?n/2 + if (?n = 0) + exitm + endif + ?bsh aset ?bsh + 1 + endm + ?blm aset ?bls/128-1 + ?size aset (?trks-?off)*?spt + ?dsm aset ?size/(?bls/128)-1 + + ?exm aset ?bls/1024 + if ?dsm > 255 + if ?bls = 1024 +.'Error, can''t have this size disk with 1k block size' + exitm + endif + ?exm aset ?exm/2 + endif + ?exm aset ?exm-1 + ?all aset 0 + ?n aset (?ndirs*32+?bls-1)/?bls + rept ?n + ?all aset (?all shr 1) or 8000h + endm + ?al0 aset high ?all + ?al1 aset low ?all + ?drm aset ?ndirs-1 + if not nul ?ncks + ?cks aset ?ncks + else + ?cks aset ?ndirs/4 + endif + dw ?spt ; 128 byte records per track + db ?bsh,?blm ; block shift and mask + db ?exm ; extent mask + dw ?dsm ; maximum block number + dw ?drm ; maximum directory entry number + db ?al0,?al1 ; alloc vector for directory + dw ?cks ; checksum size + dw ?off ; offset for system tracks + db ?psh,?psm ; physical sector size shift + ; and mask + endm + +; +gcd macro ?m,?n + ;; greatest common divisor of m,n + ;; produces value gcdn as result + ;; (used in sector translate table generation) + ?gcdm aset ?m ;;variable for m + ?gcdn aset ?n ;;variable for n + ?gcdr aset 0 ;;variable for r + rept 65535 + ?gcdx aset ?gcdm/?gcdn + ?gcdr aset ?gcdm - ?gcdx*?gcdn + if ?gcdr = 0 + exitm + endif + ?gcdm aset ?gcdn + ?gcdn aset ?gcdr + endm + endm + +skew macro ?secs,?skf,?fsc +;; generate the translate table + ?nxtsec aset 0 ;;next sector to fill + ?nxtbas aset 0 ;;moves by one on overflow + gcd %?secs,?skf + ;; ?gcdn = gcd(?secs,skew) + ?neltst aset ?secs/?gcdn + ;; neltst is number of elements to generate + ;; before we overlap previous elements + ?nelts aset ?neltst ;;counter + rept ?secs ;;once for each sector + db ?nxtsec+?fsc + ?nxtsec aset ?nxtsec+?skf + if ?nxtsec >= ?secs + ?nxtsec aset ?nxtsec-?secs + endif + ?nelts aset ?nelts-1 + if ?nelts = 0 + ?nxtbas aset ?nxtbas+1 + ?nxtsec aset ?nxtbas + ?nelts aset ?neltst + endif + endm + endm + \ No newline at end of file diff --git a/Source/CPM3/cpm3fix.pat b/Source/CPM3/cpm3fix.pat new file mode 100644 index 00000000..b60a2720 Binary files /dev/null and b/Source/CPM3/cpm3fix.pat differ diff --git a/Source/CPM3/cpmldr.asm b/Source/CPM3/cpmldr.asm new file mode 100644 index 00000000..7849afb1 --- /dev/null +++ b/Source/CPM3/cpmldr.asm @@ -0,0 +1,1572 @@ + title 'CP/M V3.0 Loader' + + +; Copyright (C) 1982 +; Digital Research +; Box 579, Pacific Grove +; California, 93950 + +; Revised: +; 01 Nov 82 by Bruce Skidmore + +base equ $ +abase equ base-0100h + +cr equ 0dh +lf equ 0ah + +fcb equ abase+005ch ;default FCB address +buff equ abase+0080h ;default buffer address + +; +; System Equates +; +resetsys equ 13 ;reset disk system +printbuf equ 09 ;print string +open$func equ 15 ;open function +read$func equ 20 ;read sequential +setdma$func equ 26 ;set dma address +; +; Loader Equates +; +comtop equ abase+80h +comlen equ abase+81h +bnktop equ abase+82h +bnklen equ abase+83h +osentry equ abase+84h + + cseg + + lxi sp,stackbot + + call bootf ;first call is to Cold Boot + + mvi c,resetsys ;Initialize the System + call bdos + + mvi c,printbuf ;print the sign on message + lxi d,signon + call bdos + + mvi c,open$func ;open the CPM3.SYS file + lxi d,cpmfcb + call bdos + cpi 0ffh + lxi d,openerr + jz error + + lxi d,buff + call setdma$proc + + call read$proc ;read the load record + + lxi h,buff + lxi d,mem$top + mvi c,6 +cloop: + mov a,m + stax d + inx d + inx h + dcr c + jnz cloop + + call read$proc ;read display info + + mvi c,printbuf ;print the info + lxi d,buff + call bdos + +; +; Main System Load +; + +; +; Load Common Portion of System +; + lda res$len + mov h,a + lda mem$top + call load +; +; Load Banked Portion of System +; + lda bank$len + ora a + jz execute + mov h,a + lda bank$top + call load +; +; Execute System +; +execute: + lxi h,fcb+1 + mov a,m + cpi '$' + jnz execute$sys + inx h + mov a,m + cpi 'B' + cz break +execute$sys: + lxi sp,osentry$adr + ret + +; +; Load Routine +; +; Input: A = Page Address of load top +; H = Length in pages of module to read +; +load: + ora a ;clear carry + mov d,a + mvi e,0 + mov a,h + ral + mov h,a ;h = length in records of module +loop: + xchg + lxi b,-128 + dad b ;decrement dma address by 128 + xchg + push d + push h + call setdma$proc + call read$proc + pop h + pop d + dcr h + jnz loop + ret + +; +; Set DMA Routine +; +setdma$proc: + mvi c,setdma$func + call bdos + ret + +; +; Read Routine +; +read$proc: + mvi c,read$func ;Read the load record + lxi d,cpmfcb ;into address 80h + call bdos + ora a + lxi d,readerr + rz +; +; Error Routine +; +error: + mvi c,printbuf ;print error message + call bdos + di + hlt + +break: + db 0ffh + ret + +cpmfcb: + db 0,'CPM3 SYS',0,0,0,0,0,0 + dw 0,0,0,0,0,0,0,0,0 + +openerr: + db cr,lf + db 'CPMLDR error: failed to open CPM3.SYS' + db cr,lf,'$' + +readerr: + db cr,lf + db 'CPMLDR error: failed to read CPM3.SYS' + db cr,lf,'$' + +signon: + db cr + db lf,lf,lf,lf,lf,lf,lf,lf,lf,lf,lf,lf + db lf,lf,lf,lf,lf,lf,lf,lf,lf,lf,lf,lf + db 'CP/M V3.0 Loader',cr,lf + db 'Copyright (C) 1998, Caldera Inc. ' + db cr,lf,'$' + maclib makedate + @BDATE ;[JCE] Build date + db 0,0,0,0 +stackbot: + +mem$top: + ds 1 +res$len: + ds 1 +bank$top: + ds 1 +bank$len: + ds 1 +osentry$adr: + ds 2 + +; title 'CP/M 3.0 LDRBDOS Interface, Version 3.1 Nov, 1982' +;***************************************************************** +;***************************************************************** +;** ** +;** B a s i c D i s k O p e r a t i n g S y s t e m ** +;** ** +;** I n t e r f a c e M o d u l e ** +;** ** +;***************************************************************** +;***************************************************************** +; +; Copyright (c) 1978, 1979, 1980, 1981, 1982 +; Digital Research +; Box 579, Pacific Grove +; California +; +; Nov 1982 +; +; +; equates for non graphic characters +; + +rubout equ 7fh ; char delete +tab equ 09h ; tab char +cr equ 0dh ; carriage return +lf equ 0ah ; line feed +ctlh equ 08h ; backspace + + +; +serial: db 0,0,0,0,0,0 +; +; Enter here from the user's program with function number in c, +; and information address in d,e +; + +bdos: +bdose: ; Arrive here from user programs + xchg! shld info! xchg ; info=de, de=info + + mov a,c! cpi 14! jc bdose2 + sta fx ; Save disk function # + xra a! sta dircnt + lda seldsk! sta olddsk ; Save seldsk + +bdose2: + mov a,e! sta linfo ; linfo = low(info) - don't equ + lxi h,0! shld aret ; Return value defaults to 0000 + shld resel ; resel = 0 + ; Save user's stack pointer, set to local stack + dad sp! shld entsp ; entsp = stackptr + + lxi sp,lstack ; local stack setup + + lxi h,goback ; Return here after all functions + push h ; jmp goback equivalent to ret + mov a,c! cpi nfuncs! jnc high$fxs ; Skip if invalid # + mov c,e ; possible output character to c + lxi h,functab! jmp bdos$jmp + + ; look for functions 100 -> +high$fxs: + sbi 100! jc lret$eq$ff ; Skip if function < 100 + +bdos$jmp: + + mov e,a! mvi d,0 ; de=func, hl=.ciotab + dad d! dad d! mov e,m! inx h! mov d,m ; de=functab(func) + lhld info ; info in de for later xchg + xchg! pchl ; dispatched + + +; dispatch table for functions + +functab: + dw func$ret, func1, func2, func3 + dw func$ret, func$ret, func6, func$ret + dw func$ret, func9, func10, func11 +diskf equ ($-functab)/2 ; disk funcs + dw func12,func13,func14,func15 + dw func16,func17,func18,func19 + dw func20,func21,func22,func23 + dw func24,func25,func26,func27 + dw func28,func29,func30,func31 + dw func32,func33,func34,func35 + dw func36,func37,func38,func39 + dw func40,func42,func43 + dw func44,func45,func46,func47 + dw func48,func49,func50 +nfuncs equ ($-functab)/2 + + +entsp: ds 2 ; entry stack pointer + + ; 40 level stack + + dw 0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h + dw 0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h + dw 0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h + dw 0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h + dw 0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h +lstack: + + +page + title 'CP/M 3.0 LDRBDOS Interface, Version 3.1 July, 1982' +;***************************************************************** +;***************************************************************** +;** ** +;** B a s i c D i s k O p e r a t i n g S y s t e m ** +;** ** +;** C o n s o l e P o r t i o n ** +;** ** +;***************************************************************** +;***************************************************************** +; +; July, 1982 +; +; +; console handlers + +conout: + ;compute character position/write console char from C + ;compcol = true if computing column position + lda compcol! ora a! jnz compout + ;write the character, then compute the column + ;write console character from C + push b ;recall/save character + call conoutf ;externally, to console + pop b ;recall the character + compout: + mov a,c ;recall the character + ;and compute column position + lxi h,column ;A = char, HL = .column + cpi rubout! rz ;no column change if nulls + inr m ;column = column + 1 + cpi ' '! rnc ;return if graphic + ;not graphic, reset column position + dcr m ;column = column - 1 + mov a,m! ora a! rz ;return if at zero + ;not at zero, may be backspace or eol + mov a,c ;character back to A + cpi ctlh! jnz notbacksp + ;backspace character + dcr m ;column = column - 1 + ret + + notbacksp: + ;not a backspace character, eol? + cpi lf! rnz ;return if not + ;end of line, column = 0 + mvi m,0 ;column = 0 + ret +; +; +tabout: + ;expand tabs to console + mov a,c! cpi tab! jnz conout ;direct to conout if not + ;tab encountered, move to next tab pos + tab0: + mvi c,' '! call conout ;another blank + lda column! ani 111b ;column mod 8 = 0 ? + jnz tab0 ;back for another if not + ret +; +print: + ;print message until M(BC) = '$' + LXI H,OUTDELIM + ldax b! CMP M! rz ;stop on $ + ;more to print + inx b! push b! mov c,a ;char to C + call tabout ;another character printed + pop b! jmp print +; +; +func2: equ tabout + ;write console character with tab expansion +; +func9: + ;write line until $ encountered + xchg ;was lhld info + mov c,l! mov b,h ;BC=string address + jmp print ;out to console +; +sta$ret: + ;store the A register to aret + sta aret +func$ret: + ret ;jmp goback (pop stack for non cp/m functions) +; +setlret1: + ;set lret = 1 + mvi a,1! jmp sta$ret +; +func1: equ func$ret +; +func3: equ func$ret +; +func6: equ func$ret +; +func10: equ func$ret +func11: equ func$ret +; +; data areas +; + + +compcol:db 0 ;true if computing column position +; end of BDOS Console module + +;********************************************************************** +;***************************************************************** +; +; Error Messages + +md equ 24h + +err$msg: db cr,lf,'BDOS ERR: ',md +err$select: db 'Select',md +err$phys: db 'Perm.',md + +;***************************************************************** +;***************************************************************** +; +; common values shared between bdosi and bdos + + +aret: ds 2 ; address value to return +lret equ aret ; low(aret) + +;***************************************************************** +;***************************************************************** +;** ** +;** b a s i c d i s k o p e r a t i n g s y s t e m ** +;** ** +;***************************************************************** +;***************************************************************** + +; literal constants + +true equ 0ffh ; constant true +false equ 000h ; constant false +enddir equ 0ffffh ; end of directory +byte equ 1 ; number of bytes for "byte" type +word equ 2 ; number of bytes for "word" type + +; fixed addresses in low memory + +tbuff equ 0080h ; default buffer location + +; error message handlers + +sel$error: + ; report select error + lxi b,err$msg + call print + lxi b,err$select + jmp goerr1 + +goerr: + lxi b,err$msg + call print + lxi b,err$phys +goerr1: + call print + di ! hlt + +bde$e$bde$m$hl: + mov a,e! sub l! mov e,a + mov a,d! sbb h! mov d,a + rnc! dcr b! ret + +bde$e$bde$p$hl: + mov a,e! add l! mov e,a + mov a,d! adc h! mov d,a + rnc! inr b! ret + +shl3bv: + inr c +shl3bv1: + dcr c! rz + dad h! adc a! jmp shl3bv1 + +compare: + ldax d! cmp m! rnz + inx h! inx d! dcr c! rz + jmp compare + +; +; local subroutines for bios interface +; + +move: + ; Move data length of length c from source de to + ; destination given by hl + inr c ; in case it is zero + move0: + dcr c! rz ; more to move + ldax d! mov m,a ; one byte moved + inx d! inx h ; to next byte + jmp move0 + +selectdisk: + ; Select the disk drive given by register D, and fill + ; the base addresses curtrka - alloca, then fill + ; the values of the disk parameter block + mov c,d ; current disk# to c + ; lsb of e = 0 if not yet logged - in + call seldskf ; hl filled by call + ; hl = 0000 if error, otherwise disk headers + mov a,h! ora l! rz ; Return with C flag reset if select error + ; Disk header block address in hl + mov e,m! inx h! mov d,m! inx h ; de=.tran + inx h ! inx h + shld curtrka! inx h! inx h ; hl=.currec + shld curreca! inx h! inx h ; hl=.buffa + inx h! inx h + inx h! inx h + ; de still contains .tran + xchg! shld tranv ; .tran vector + lxi h,dpbaddr ; de= source for move, hl=dest + mvi c,addlist! call move ; addlist filled + ; Now fill the disk parameter block + lhld dpbaddr! xchg ; de is source + lxi h,sectpt ; hl is destination + mvi c,dpblist! call move ; data filled + ; Now set single/double map mode + lhld maxall ; largest allocation number + mov a,h ; 00 indicates < 255 + lxi h,single! mvi m,true ; Assume a=00 + ora a! jz retselect + ; high order of maxall not zero, use double dm + mvi m,false + retselect: + ; C flag set indicates successful select + stc + ret + +home: + ; Move to home position, then offset to start of dir + call homef + xra a ; constant zero to accumulator + lhld curtrka! mov m,a! inx h! mov m,a ; curtrk=0000 + lhld curreca! mov m,a! inx h! mov m,a ; currec=0000 + inx h! mov m,a ; currec high byte=00 + + ret + +pass$arecord: + lxi h,arecord + mov e,m! inx h! mov d,m! inx h! mov b,m + ret + +rdbuff: + ; Read buffer and check condition + call pass$arecord + call readf ; current drive, track, sector, dma + + +diocomp: ; Check for disk errors + ora a! rz + mov c,a + cpi 3! jc goerr + mvi c,1! jmp goerr + +seekdir: + ; Seek the record containing the current dir entry + + lhld dcnt ; directory counter to hl + mvi c,dskshf! call hlrotr ; value to hl + + mvi b,0! xchg + + lxi h,arecord + mov m,e! inx h! mov m,d! inx h! mov m,b + ret + +seek: + ; Seek the track given by arecord (actual record) + + lhld curtrka! mov c,m! inx h! mov b,m ; bc = curtrk + push b ; s0 = curtrk + lhld curreca! mov e,m! inx h! mov d,m + inx h! mov b,m ; bde = currec + lhld arecord! lda arecord+2! mov c,a ; chl = arecord +seek0: + mov a,l! sub e! mov a,h! sbb d! mov a,c! sbb b + push h ; Save low(arecord) + jnc seek1 ; if arecord >= currec then go to seek1 + lhld sectpt! call bde$e$bde$m$hl ; currec = currec - sectpt + pop h! xthl! dcx h! xthl ; curtrk = curtrk - 1 + jmp seek0 +seek1: + lhld sectpt! call bde$e$bde$p$hl ; currec = currec + sectpt + pop h ; Restore low(arecord) + mov a,l! sub e! mov a,h! sbb d! mov a,c! sbb b + jc seek2 ; if arecord < currec then go to seek2 + xthl! inx h! xthl ; curtrk = curtrk + 1 + push h ; save low (arecord) + jmp seek1 +seek2: + xthl! push h ; hl,s0 = curtrk, s1 = low(arecord) + lhld sectpt! call bde$e$bde$m$hl ; currec = currec - sectpt + pop h! push d! push b! push h ; hl,s0 = curtrk, + ; s1 = high(arecord,currec), s2 = low(currec), + ; s3 = low(arecord) + xchg! lhld offset! dad d + mov b,h! mov c,l! shld track + call settrkf ; call bios settrk routine + ; Store curtrk + pop d! lhld curtrka! mov m,e! inx h! mov m,d + ; Store currec + pop b! pop d! + lhld curreca! mov m,e! inx h! mov m,d + inx h! mov m,b ; currec = bde + pop b ; bc = low(arecord), de = low(currec) + mov a,c! sub e! mov l,a ; hl = bc - de + mov a,b! sbb d! mov h,a + call shr$physhf + mov b,h! mov c,l + + lhld tranv! xchg ; bc=sector#, de=.tran + call sectran ; hl = tran(sector) + mov c,l! mov b,h ; bc = tran(sector) + shld sector + call setsecf ; sector selected + lhld curdma! mov c,l! mov b,h! jmp setdmaf + +shr$physhf: + lda physhf! mov c,a! jmp hlrotr + + +; file control block (fcb) constants + +empty equ 0e5h ; empty directory entry +recsiz equ 128 ; record size +fcblen equ 32 ; file control block size +dirrec equ recsiz/fcblen ; directory fcbs / record +dskshf equ 2 ; log2(dirrec) +dskmsk equ dirrec-1 +fcbshf equ 5 ; log2(fcblen) + +extnum equ 12 ; extent number field +maxext equ 31 ; largest extent number +ubytes equ 13 ; unfilled bytes field + +namlen equ 15 ; name length +reccnt equ 15 ; record count field +dskmap equ 16 ; disk map field +nxtrec equ fcblen + +; utility functions for file access + +dm$position: + ; Compute disk map position for vrecord to hl + lxi h,blkshf! mov c,m ; shift count to c + lda vrecord ; current virtual record to a + dmpos0: + ora a! rar! dcr c! jnz dmpos0 + ; a = shr(vrecord,blkshf) = vrecord/2**(sect/block) + mov b,a ; Save it for later addition + mvi a,8! sub m ; 8-blkshf to accumulator + mov c,a ; extent shift count in register c + lda extval ; extent value ani extmsk + dmpos1: + ; blkshf = 3,4,5,6,7, c=5,4,3,2,1 + ; shift is 4,3,2,1,0 + dcr c! jz dmpos2 + ora a! ral! jmp dmpos1 + dmpos2: + ; Arrive here with a = shl(ext and extmsk,7-blkshf) + add b ; Add the previous shr(vrecord,blkshf) value + ; a is one of the following values, depending upon alloc + ; bks blkshf + ; 1k 3 v/8 + extval * 16 + ; 2k 4 v/16+ extval * 8 + ; 4k 5 v/32+ extval * 4 + ; 8k 6 v/64+ extval * 2 + ; 16k 7 v/128+extval * 1 + ret ; with dm$position in a + +getdma: + lhld info! lxi d,dskmap! dad d! ret + +getdm: + ; Return disk map value from position given by bc + call getdma + dad b ; Index by a single byte value + lda single ; single byte/map entry? + ora a! jz getdmd ; Get disk map single byte + mov l,m! mov h,b! ret ; with hl=00bb + getdmd: + dad b ; hl=.fcb(dm+i*2) + ; double precision value returned + mov a,m! inx h! mov h,m! mov l,a! ret + +index: + ; Compute disk block number from current fcb + call dm$position ; 0...15 in register a + sta dminx + mov c,a! mvi b,0! call getdm ; value to hl + shld arecord! mov a,l! ora h! ret + +atran: + ; Compute actual record address, assuming index called + +; arecord = shl(arecord,blkshf) + + lda blkshf! mov c,a + lhld arecord! xra a! call shl3bv + shld arecord! sta arecord+2 + + shld arecord1 ; Save low(arecord) + +; arecord = arecord or (vrecord and blkmsk) + + lda blkmsk! mov c,a! lda vrecord! ana c + mov b,a ; Save vrecord & blkmsk in reg b & blk$off + sta blk$off + lxi h,arecord! ora m! mov m,a! ret + + +getexta: + ; Get current extent field address to hl + lhld info! lxi d,extnum! dad d ; hl=.fcb(extnum) + ret + +getrcnta: + ; Get reccnt address to hl + lhld info! lxi d,reccnt! dad d! ret + +getfcba: + ; Compute reccnt and nxtrec addresses for get/setfcb + call getrcnta! xchg ; de=.fcb(reccnt) + lxi h,(nxtrec-reccnt)! dad d ; hl=.fcb(nxtrec) + ret + +getfcb: + ; Set variables from currently addressed fcb + call getfcba ; addresses in de, hl + mov a,m! sta vrecord ; vrecord=fcb(nxtrec) + xchg! mov a,m! sta rcount ; rcount=fcb(reccnt) + call getexta ; hl=.fcb(extnum) + lda extmsk ; extent mask to a + ana m ; fcb(extnum) and extmsk + sta extval + ret + +setfcb: + ; Place values back into current fcb + call getfcba ; addresses to de, hl + mvi c,1 + + lda vrecord! add c! mov m,a ; fcb(nxtrec)=vrecord+seqio + xchg! lda rcount! mov m,a ; fcb(reccnt)=rcount + ret + +hlrotr: + ; hl rotate right by amount c + inr c ; in case zero + hlrotr0: dcr c! rz ; return when zero + + mov a,h! ora a! rar! mov h,a ; high byte + mov a,l! rar! mov l,a ; low byte + jmp hlrotr0 + +hlrotl: + ; Rotate the mask in hl by amount in c + inr c ; may be zero + hlrotl0: dcr c! rz ; return if zero + + dad h! jmp hlrotl0 + +set$cdisk: + ; Set a "1" value in curdsk position of bc + lda seldsk + push b ; Save input parameter + mov c,a ; Ready parameter for shift + lxi h,1 ; number to shift + call hlrotl ; hl = mask to integrate + pop b ; original mask + mov a,c! ora l! mov l,a + mov a,b! ora h! mov h,a ; hl = mask or rol(1,curdsk) + ret + +test$vector: + lda seldsk + mov c,a! call hlrotr + mov a,l! ani 1b! ret ; non zero if curdsk bit on + +getdptra: + ; Compute the address of a directory element at + ; positon dptr in the buffer + + lhld buffa! lda dptr + ; hl = hl + a + add l! mov l,a! rnc + ; overflow to h + inr h! ret + +clr$ext: + ; fcb ext = fcb ext & 1fh + + call getexta! mov a,m! ani 0001$1111b! mov m,a! + ret + + +subdh: + ; Compute hl = de - hl + mov a,e! sub l! mov l,a! mov a,d! sbb h! mov h,a + ret + +get$buffa: + push d! lxi d,10! dad d + mov e,m! inx h! mov d,m + xchg! pop d! ret + + +rddir: + ; Read a directory entry into the directory buffer + call seek$dir + lda phymsk! ora a! jz rddir1 + mvi a,3 + call deblock$dir! jmp setdata + +rddir1: + call setdir ; directory dma + shld buffa! call seek + call rdbuff ; directory record loaded + +setdata: + ; Set data dma address + lhld dmaad! jmp setdma ; to complete the call + +setdir: + ; Set directory dma address + + lhld dirbcba + call get$buffa + +setdma: + ; hl=.dma address to set (i.e., buffa or dmaad) + shld curdma! ret + +end$of$dir: + ; Return zero flag if at end of directory, non zero + ; if not at end (end of dir if dcnt = 0ffffh) + lxi h,dcnt + mov a,m ; may be 0ffh + inx h! cmp m ; low(dcnt) = high(dcnt)? + rnz ; non zero returned if different + ; high and low the same, = 0ffh? + inr a ; 0ffh becomes 00 if so + ret + +set$end$dir: + ; Set dcnt to the end of the directory + lxi h,enddir! shld dcnt! ret + + +read$dir: + ; Read next directory entry, with c=true if initializing + + lhld dirmax! xchg ; in preparation for subtract + lhld dcnt! inx h! shld dcnt ; dcnt=dcnt+1 + + ; while(dirmax >= dcnt) + call subdh ; de-hl + jc set$end$dir + ; not at end of directory, seek next element + ; initialization flag is in c + + lda dcnt! ani dskmsk ; low(dcnt) and dskmsk + mvi b,fcbshf ; to multiply by fcb size + + read$dir1: + add a! dcr b! jnz read$dir1 + ; a = (low(dcnt) and dskmsk) shl fcbshf + sta dptr ; ready for next dir operation + ora a! rnz ; Return if not a new record + + push b ; Save initialization flag c + call rd$dir ; Read the directory record + pop b ; Recall initialization flag + ret +compext: + ; Compare extent# in a with that in c, return nonzero + ; if they do not match + push b ; Save c's original value + push psw! lda extmsk! cma! mov b,a + ; b has negated form of extent mask + mov a,c! ana b! mov c,a ; low bits removed from c + pop psw! ana b ; low bits removed from a + sub c! ani maxext ; Set flags + pop b ; Restore original values + ret + +get$dir$ext: + ; Compute directory extent from fcb + ; Scan fcb disk map backwards + call getfcba ; hl = .fcb(vrecord) + mvi c,16! mov b,c! inr c! push b + ; b=dskmap pos (rel to 0) +get$de0: + pop b + dcr c + xra a ; Compare to zero +get$de1: + dcx h! dcr b ; Decr dskmap position + cmp m! jnz get$de2 ; fcb(dskmap(b)) ~= 0 + dcr c! jnz get$de1 + ; c = 0 -> all blocks = 0 in fcb disk map +get$de2: + mov a,c! sta dminx + lda single! ora a! mov a,b + jnz get$de3 + rar ; not single, divide blk idx by 2 +get$de3: + push b! push h ; Save dskmap position & count + mov l,a! mvi h,0 ; hl = non-zero blk idx + ; Compute ext offset from last non-zero + ; block index by shifting blk idx right + ; 7 - blkshf + lda blkshf! mov d,a! mvi a,7! sub d + mov c,a! call hlrotr! mov b,l + ; b = ext offset + lda extmsk! cmp b! pop h! jc get$de0 + ; Verify computed extent offset <= extmsk + call getexta! mov c,m + cma! ani maxext! ana c! ora b + ; dir ext = (fcb ext & (~ extmsk) & maxext) | ext offset + pop b ; Restore stack + ret ; a = directory extent + + +search: + ; Search for directory element of length c at info + lhld info! shld searcha ; searcha = info + mov a,c! sta searchl ; searchl = c + + call set$end$dir ; dcnt = enddir + call home ; to start at the beginning + +searchn: + ; Search for the next directory element, assuming + ; a previous call on search which sets searcha and + ; searchl + + mvi c,false! call read$dir ; Read next dir element + call end$of$dir! jz lret$eq$ff + ; not end of directory, scan for match + lhld searcha! xchg ; de=beginning of user fcb + + call getdptra ; hl = buffa+dptr + lda searchl! mov c,a ; length of search to c + mvi b,0 ; b counts up, c counts down + + mov a,m! cpi empty! jz searchn + + searchloop: + mov a,c! ora a! jz endsearch + ; Scan next character if not ubytes + mov a,b! cpi ubytes! jz searchok + ; not the ubytes field, extent field? + cpi extnum ; may be extent field + jz searchext ; Skip to search extent + ldax d + sub m! ani 7fh ; Mask-out flags/extent modulus + jnz searchn ; Skip if not matched + jmp searchok ; matched character + searchext: + ldax d + ; Attempt an extent # match + push b ; Save counters + mov c,m ; directory character to c + call compext ; Compare user/dir char + pop b ; Recall counters + ora a ; Set flag + jnz searchn ; Skip if no match + searchok: + ; current character matches + inx d! inx h! inr b! dcr c + jmp searchloop + endsearch: + ; entire name matches, return dir position + xra a + sta lret ; lret = 0 + ; successful search - + ; return with zero flag reset + mov b,a! inr b + ret + lret$eq$ff: + ; unsuccessful search - + ; return with zero flag set + ; lret,low(aret) = 0ffh + mvi a,255 ! mov b,a ! inr b ! jmp sta$ret + +open: + ; Search for the directory entry, copy to fcb + mvi c,namlen! call search + rz ; Return with lret=255 if end + + ; not end of directory, copy fcb information +open$copy: + call getexta ! mov a,m ! push a ; save extent to check for extent + ; folding - move moves entire dir FCB + call getdptra! xchg ; hl = .buff(dptr) + lhld info ; hl=.fcb(0) + mvi c,nxtrec ; length of move operation + call move ; from .buff(dptr) to .fcb(0) + + ; Note that entire fcb is copied, including indicators + + call get$dir$ext! mov c,a + pop a ! mov m,a ; restore extent + + ; hl = .user extent#, c = dir extent# + ; above move set fcb(reccnt) to dir(reccnt) + ; if fcb ext < dir ext then fcb(reccnt) = fcb(reccnt) | 128 + ; if fcb ext = dir ext then fcb(reccnt) = fcb(reccnt) + ; if fcb ext > dir ext then fcb(reccnt) = 0 + +set$rc: ; hl=.fcb(ext), c=dirext + mvi b,0 + xchg! lxi h,(reccnt-extnum)! dad d + ldax d! sub c! jz set$rc2 + mov a,b! jnc set$rc1 + mvi a,128! mov b,m + + set$rc1: + mov m,a! mov a,b! sta actual$rc! ret + set$rc2: + sta actual$rc + mov a,m! ora a! rnz ; ret if rc ~= 0 + lda dminx! ora a! rz ; ret if no blks in fcb + lda fx! cpi 15! rz ; ret if fx = 15 + mvi m,128 ; rc = 128 + ret + +restore$rc: + ; hl = .fcb(extnum) + ; if actual$rc ~= 0 then rcount = actual$rc + push h + lda actual$rc! ora a! jz restore$rc1 + lxi d,(reccnt-extnum)! dad d + mov m,a! xra a! sta actual$rc + +restore$rc1: + pop h! ret + +open$reel: + ; Close the current extent, and open the next one + ; if possible. + + call getexta + mov a,m! mov c,a + inr c! call compext + jz open$reel3 + + mvi a,maxext! ana c! mov m,a ; Incr extent field + mvi c,namlen! call search ; Next extent found? + ; not end of file, open + call open$copy + + open$reel2: + call getfcb ; Set parameters + xra a! sta vrecord! jmp sta$ret ; lret = 0 + open$reel3: + inr m ; fcb(ex) = fcb(ex) + 1 + call get$dir$ext! mov c,a + ; Is new extent beyond dir$ext? + cmp m! jnc open$reel4 ; no + dcr m ; fcb(ex) = fcb(ex) - 1 + jmp set$lret1 + open$reel4: + call restore$rc + call set$rc! jmp open$reel2 + +seqdiskread: + ; Sequential disk read operation + ; Read the next record from the current fcb + + call getfcb ; sets parameters for the read + + lda vrecord! lxi h,rcount! cmp m ; vrecord-rcount + ; Skip if rcount > vrecord + jc recordok + + ; not enough records in the extent + ; record count must be 128 to continue + cpi 128 ; vrecord = 128? + jnz setlret1 ; Skip if vrecord<>128 + call open$reel ; Go to next extent if so + ; Check for open ok + lda lret! ora a! jnz setlret1 ; Stop at eof + + recordok: + ; Arrive with fcb addressing a record to read + + call index ; Z flag set if arecord = 0 + + jz setlret1 ; Reading unwritten data + + ; Record has been allocated + call atran ; arecord now a disk address + + lda phymsk! ora a ; if not 128 byte sectors + jnz read$deblock ; go to deblock + + call setdata ; Set curdma = dmaad + call seek ; Set up for read + call rdbuff ; Read into (curdma) + jmp setfcb ; Update FCB + +curselect: + lda seldsk! inr a! jz sel$error + dcr a! lxi h,curdsk! cmp m! rz + + ; Skip if seldsk = curdsk, fall into select +select: + ; Select disk info for subsequent input or output ops + mov m,a ; curdsk = seldsk + + mov d,a ; Save seldsk in register D for selectdisk call + lhld dlog! call test$vector ; test$vector does not modify DE + mov e,a! push d ; Send to seldsk, save for test below + call selectdisk! pop h ; Recall dlog vector + jnc sel$error ; returns with C flag set if select ok + ; Is the disk logged in? + dcr l ; reg l = 1 if so + rz ; yes - drive previously logged in + + lhld dlog! mov c,l! mov b,h ; call ready + call set$cdisk! shld dlog ; dlog=set$cdisk(dlog) + ret + +set$seldsk: + lda linfo! sta seldsk! ret + +reselectx: + xra a! sta high$ext! jmp reselect1 +reselect: + ; Check current fcb to see if reselection necessary + mvi a,80h! mov b,a! dcr a! mov c,a ; b = 80h, c = 7fh + lhld info! lxi d,7! xchg! dad d + mov a,m! ana b + ; fcb(7) = fcb(7) & 7fh + mov a,m! ana c! mov m,a + ; high$ext = 80h & fcb(8) + inx h! mov a,m! ana b! sta high$ext + ; fcb(8) = fcb(8) & 7fh + mov a,m! ana c! mov m,a + ; fcb(ext) = fcb(ext) & 1fh + call clr$ext + + ; if fcb(rc) & 80h + ; then fcb(rc) = 80h, actual$rc = fcb(rc) & 7fh + ; else actual$rc = 0 + + call getrcnta! mov a,m! ana b! jz reselect1 + mov a,m! ana c! mov m,b + +reselect1: + sta actual$rc + + lxi h,0 + shld fcbdsk ; fcbdsk = 0 + mvi a,true! sta resel ; Mark possible reselect + lhld info! mov a,m ; drive select code + ani 1$1111b ; non zero is auto drive select + dcr a ; Drive code normalized to 0..30, or 255 + sta linfo ; Save drive code + cpi 0ffh! jz noselect + ; auto select function, seldsk saved above + mov a,m! sta fcbdsk ; Save drive code + call set$seldsk + + noselect: + call curselect + mvi a,0 ! lhld info ! mov m,a + ret + +; +; individual function handlers +; + +func12 equ func$ret + +func13: + + ; Reset disk system - initialize to disk 0 + lxi h,0! shld dlog + + xra a! sta seldsk + dcr a! sta curdsk + + lxi h,tbuff! shld dmaad ; dmaad = tbuff + jmp setdata ; to data dma address + +func14: + ; Select disk info + call set$seldsk ; seldsk = linfo + jmp curselect + +func15: + ; Open file + call reselectx + call open! call openx ; returns if unsuccessful, a = 0 + ret + +openx: + call end$of$dir! rz + call getfcba! mov a,m! inr a! jnz openxa + dcx d! dcx d! ldax d! mov m,a +openxa: + ; open successful + pop h ; Discard return address + mvi c,0100$0000b + ret + +func16 equ func$ret + +func17 equ func$ret + +func18 equ func$ret + +func19 equ func$ret + +func20: + ; Read a file + call reselect + jmp seqdiskread + +func21 equ func$ret + +func22 equ func$ret + +func23 equ func$ret + +func24 equ func$ret + +func25: lda seldsk ! jmp sta$ret + +func26: xchg ! shld dmaad + jmp setdata + +func27 equ func$ret + +func28: equ func$ret + +func29 equ func$ret + +func30 equ func$ret + +func31 equ func$ret + +func32 equ func$ret + +func33 equ func$ret + +func34 equ func$ret + +func35 equ func$ret + +func36 equ func$ret + +func37 equ func$ret + +func38 equ func$ret + +func39 equ func$ret + +func40 equ func$ret + +func42 equ func$ret + +func43 equ func$ret + +func44 equ func$ret + +func45 equ func$ret + +func46 equ func$ret + +func47 equ func$ret + +func48 equ func$ret + +func49 equ func$ret + +func50 equ func$ret + +func100 equ func$ret + +func101 equ func$ret + +func102 equ func$ret + +func103 equ func$ret + +func104 equ func$ret + +func105 equ func$ret + +func106 equ func$ret + +func107 equ func$ret + +func108 equ func$ret + +func109 equ func$ret + + +goback: + ; Arrive here at end of processing to return to user + lda fx! cpi 15! jc retmon + lda olddsk! sta seldsk ; Restore seldsk + lda resel! ora a! jz retmon + + lhld info! mvi m,0 ; fcb(0)=0 + lda fcbdsk! ora a! jz goback1 + ; Restore fcb(0) + mov m,a ; fcb(0)=fcbdsk + goback1: + ; fcb(8) = fcb(8) | high$ext + inx h! lda high$ext! ora m! mov m,a + ; fcb(rc) = fcb(rc) | actual$rc + call getrcnta! lda actual$rc! ora m! mov m,a + ; return from the disk monitor +retmon: + lhld entsp! sphl + lhld aret! mov a,l! mov b,h + ret +; +; data areas +; +dlog: dw 0 ; logged-in disks +curdma ds word ; current dma address +buffa: ds word ; pointer to directory dma address + +; +; curtrka - alloca are set upon disk select +; (data must be adjacent, do not insert variables) +; (address of translate vector, not used) +cdrmaxa:ds word ; pointer to cur dir max value (2 bytes) +curtrka:ds word ; current track address (2) +curreca:ds word ; current record address (3) +drvlbla:ds word ; current drive label byte address (1) +lsn$add:ds word ; login sequence # address (1) + ; +1 -> bios media change flag (1) +dpbaddr:ds word ; current disk parameter block address +checka: ds word ; current checksum vector address +alloca: ds word ; current allocation vector address +dirbcba:ds word ; dir bcb list head +dtabcba:ds word ; data bcb list head +hash$tbla: + ds word + ds byte + +addlist equ $-dpbaddr ; address list size + +; +; buffer control block format +; +; bcb format : drv(1) || rec(3) || pend(1) || sequence(1) || +; 0 1 4 5 +; +; track(2) || sector(2) || buffer$add(2) || +; 6 8 10 +; +; link(2) +; 12 +; + +; sectpt - offset obtained from disk parm block at dpbaddr +; (data must be adjacent, do not insert variables) +sectpt: ds word ; sectors per track +blkshf: ds byte ; block shift factor +blkmsk: ds byte ; block mask +extmsk: ds byte ; extent mask +maxall: ds word ; maximum allocation number +dirmax: ds word ; largest directory number +dirblk: ds word ; reserved allocation bits for directory +chksiz: ds word ; size of checksum vector +offset: ds word ; offset tracks at beginning +physhf: ds byte ; physical record shift +phymsk: ds byte ; physical record mask +dpblist equ $-sectpt ; size of area +; +; local variables +; +blk$off: ds byte ; record offset within block +dir$cnt: ds byte ; direct i/o count + +tranv: ds word ; address of translate vector +linfo: ds byte ; low(info) +dminx: ds byte ; local for diskwrite + +actual$rc: + ds byte ; directory ext record count + +single: ds byte ; set true if single byte allocation map + + +olddsk: ds byte ; disk on entry to bdos +rcount: ds byte ; record count in current fcb +extval: ds byte ; extent number and extmsk + +vrecord:ds byte ; current virtual record + +curdsk: + +adrive: db 0ffh ; current disk +arecord:ds word ; current actual record + ds byte + +arecord1: ds word ; current actual block# * blkmsk + +;******** following variable order critical ***************** + +high$ext: ds byte ; fcb high ext bits +;xfcb$read$only: ds byte + +; local variables for directory access +dptr: ds byte ; directory pointer 0,1,2,3 + +; +; local variables initialized by bdos at entry +; +fcbdsk: ds byte ; disk named in fcb + +phy$off: ds byte +curbcba: ds word + +track: ds word +sector: ds word + +read$deblock: + mvi a,1! call deblock$dta + jmp setfcb + +column db 0 +outdelim: db '$' + +dmaad: dw 0080h +seldsk: db 0 +info: dw 0 +resel: db 0 +fx: db 0 +dcnt: dw 0 +searcha: dw 0 +searchl: db 0 + + +; ************************** +; Blocking/Deblocking Module +; ************************** + +deblock$dir: + + lhld dirbcba + + jmp deblock + +deblock$dta: + lhld dtabcba + +deblock: + + ; BDOS Blocking/Deblocking routine + ; a = 1 -> read command + ; a = 2 -> write command + ; a = 3 -> locate command + ; a = 4 -> flush command + ; a = 5 -> directory update + + push a ; Save z flag and deblock fx + + ; phy$off = low(arecord) & phymsk + ; low(arecord) = low(arecord) & ~phymsk + call deblock8 + lda arecord! mov e,a! ana b! sta phy$off + mov a,e! ana c! sta arecord + + shld curbcba! call getbuffa! shld curdma + + call deblock9 + ; Is command flush? + pop a! push a! cpi 4 + jnc deblock1 ; yes + ; Is referenced physical record + ;already in buffer? + call compare! jz deblock45 ; yes + xra a +deblock1: + call deblock10 + ; Read physical record buffer + mvi a,2! call deblock$io + + call deblock9 ; phypfx = adrive || arecord + call move! mvi m,0 ; zero pending flag + +deblock45: + ; recadd = phybuffa + phy$off*80h + lda phy$off! inr a! lxi d,80h! lxi h,0ff80h +deblock5: + dad d! dcr a! jnz deblock5 + xchg! lhld curdma! dad d + ; If deblock command = locate + ; then buffa = recadd; return + pop a! cpi 3! jnz deblock6 + shld buffa! ret +deblock6: + xchg! lhld dmaad! lxi b,80h + ; If deblock command = read + jmp move$tpa ; then move to dma + +deblock8: + lda phymsk! mov b,a! cma! mov c,a! ret + +deblock9: + lhld curbcba! lxi d,adrive! mvi c,4! ret + +deblock10: + lxi d,4 +deblock11: + lhld curbcba! dad d! ret + +deblock$io: + ; a = 0 -> seek only + ; a = 1 -> write + ; a = 2 -> read + push a! call seek + pop a! dcr a + cp rdbuff + ; Move track & sector to bcb + call deblock10! inx h! inx h + lxi d,track! mvi c,4! jmp move + + org base+((($-base)+255) and 0ff00h)-1 + db 0 + +; Bios equates + +bios$pg equ $ + +bootf equ bios$pg+00 ; 00. cold boot +conoutf equ bios$pg+12 ; 04. console output function +homef equ bios$pg+24 ; 08. disk home function +seldskf equ bios$pg+27 ; 09. select disk function +settrkf equ bios$pg+30 ; 10. set track function +setsecf equ bios$pg+33 ; 11. set sector function +setdmaf equ bios$pg+36 ; 12. set dma function +sectran equ bios$pg+48 ; 16. sector translate +movef equ bios$pg+75 ; 25. memory move function +readf equ bios$pg+39 ; 13. read disk function +move$out equ movef +move$tpa equ movef + + end + \ No newline at end of file diff --git a/Source/CPM3/diskio.z80 b/Source/CPM3/diskio.z80 new file mode 100644 index 00000000..154d601d --- /dev/null +++ b/Source/CPM3/diskio.z80 @@ -0,0 +1,806 @@ + title 'HBIOS disk handler' + +; CP/M-80 Version 3 -- Modular BIOS + + maclib options.lib + + dseg + + ; Disk drive dispatching tables for linked BIOS + + public dph0,dph1,dph2,dph3,dph4,dph5,dph6,dph7 + public dph8,dph9,dph10,dph11,dph12,dph13,dph14,dph15 + + ; Linked BIOS variables + + public @sysdr + extrn @bootdu,@bootsl + + ; Variables containing parameters passed by BDOS + + extrn @adrv,@rdrv + extrn @dma,@trk,@sect + extrn @dbnk + + ; System Control Block variables + + extrn @ermde ; BDOS error mode + + ; Utility routines in standard BIOS + + extrn ?wboot ; warm boot vector + extrn ?pmsg ; print message @ up to 00, saves & + extrn ?pdec ; print binary number in from 0 to 99. + extrn ?pderr ; print BIOS disk error header + extrn ?conin,?cono ; con in and out + extrn ?const ; get console status + + extrn ?bnkxlt + + ;extrn phex8, cout + + + ; CP/M 3 Disk definition macros + + maclib cpm3.lib + + ; common control characters + +cr equ 13 +lf equ 10 +bell equ 7 + + + ; Extended Disk Parameter Headers (XPDHs) + + ; All DPH entries below are generic. They are updated during + ; boot to point to available HBIOS disk unit/slices dynamically. + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph0: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph1: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph2: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph3: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph4: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph5: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph6: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph7: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph8: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph9: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph10: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph11: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph12: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph13: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph14: dph 0,dpb$max ; Real DPB filled in at disk login + + dw dsk$write + dw dsk$read + dw dsk$login + dw dsk$init + db 0,0 ; HBIOS Disk Unit/Slice (filled in at boot) +dph15: dph 0,dpb$max ; Real DPB filled in at disk login + + cseg ; DPB must be resident + +dpb$max: + dw 64 ; spt: sectors per track + db 5 ; bsh: block shift factor + db 31 ; blm: block mask + db 1 ; exm: extent mask + dw 2047 ; dsm: total storage in blocks - 1 = (8mb / 4k bls) - 1 = 2047 + dw 511 ; drm: dir entries - 1 = 512 - 1 = 511 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 64 ; cks: directory check vector size - 256 / 4 + dw 0 ; off: reserved tracks = 16 trks * (16 trks * 16 heads * 16 secs * 512 bytes) = 128k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb$start: +dpb$rom: ; 384K ROM Drive + dw 64 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 1 ; exm: extent mask + dw 192 - 1 ; dsm: total storage in blocks - 1 = (384kb / 2k bls) - 1 = 191 + dw 256 - 1 ; drm: dir entries - 1 = 256 - 1 = 255 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 8000h ; cks: directory check vector size - permanent storage = 8000H + dw 0 ; off: reserved tracks = 16 trks * (16 trks * 16 heads * 16 secs * 512 bytes) = 128k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 +dpb$sz equ $ - dpb$start + +dpb$ram: ; 256K RAM Drive + dw 64 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 1 ; exm: extent mask + dw 128 - 1 ; dsm: total storage in blocks - 1 = (256kb / 2k bls) - 1 = 127 + dw 256 - 1 ; drm: dir entries - 1 = 256 - 1 = 255 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 8000h ; cks: directory check vector size - permanent storage = 8000H + dw 0 ; off: reserved tracks = 16 trks * (16 trks * 16 heads * 16 secs * 512 bytes) = 128k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb$rf: ; 4MB RAM Floppy Drive + dw 64 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 0 ; exm: extent mask + dw 2047 ; dsm: total storage in blocks - 1 = (4mb / 2k bls) - 1 = 2047 + dw 255 ; drm: dir entries - 1 = 256 - 1 = 255 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 8000h ; cks: directory check vector size - permanent storage = 8000H + dw 0 ; off: reserved tracks = 0 trks + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb$hd: ; 8MB Hard Disk Drive + dw 64 ; spt: sectors per track + db 5 ; bsh: block shift factor + db 31 ; blm: block mask + db 1 ; exm: extent mask + dw 2047 ; dsm: total storage in blocks - 1 = (8mb / 4k bls) - 1 = 2047 + dw 511 ; drm: dir entries - 1 = 512 - 1 = 511 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 8000h ; cks: directory check vector size - permanent storage = 8000H + dw 16 ; off: reserved tracks = 16 trks * (16 trks * 16 heads * 16 secs * 512 bytes) = 128k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb$fd720: ; 3.5" DS/DD Floppy Drive (720K) + dw 36 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 0 ; exm: extent mask + dw 350 ; dsm: total storage in blocks - 1 blk = ((720k - 18k off) / 2k bls) - 1 = 350 + dw 127 ; drm: dir entries - 1 = 128 - 1 = 127 + db 11000000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 32 ; cks: directory check vector size = 128 / 4 + dw 4 ; off: reserved tracks = 4 trks * (512 b/sec * 36 sec/trk) = 18k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb_fd144: ; 3.5" DS/HD Floppy Drive (1.44M) + dw 72 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 0 ; exm: extent mask + dw 710 ; dsm: total storage in blocks - 1 blk = ((1,440k - 18k off) / 2k bls) - 1 = 710 + dw 255 ; drm: dir entries - 1 = 256 - 1 = 255 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 64 ; cks: directory check vector size = 256 / 4 + dw 2 ; off: reserved tracks = 2 trks * (512 b/sec * 72 sec/trk) = 18k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb_fd360: ; 5.25" DS/DD Floppy Drive (360K) + dw 36 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 1 ; exm: extent mask + dw 170 ; dsm: total storage in blocks - 1 blk = ((360k - 18k off) / 2k bls) - 1 = 170 + dw 127 ; drm: dir entries - 1 = 128 - 1 = 127 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 32 ; cks: directory check vector size = 128 / 4 + dw 4 ; off: reserved tracks = 4 trks * (512 b/sec * 36 sec/trk) = 18k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb_fd120: ; 5.25" DS/HD Floppy Drive (1.2M) + dw 60 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 0 ; exm: extent mask + dw 591 ; dsm: total storage in blocks - 1 blk = ((1,200k - 15k off) / 2k bls) - 1 = 591 + dw 255 ; drm: dir entries - 1 = 256 - 1 = 255 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 64 ; cks: directory check vector size = 256 / 4 + dw 2 ; off: reserved tracks = 2 trks * (512 b/sec * 60 sec/trk) = 15k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + +dpb_fd111: ; 8" DS/DD Floppy Drive (1.11M) + dw 60 ; spt: sectors per track + db 4 ; bsh: block shift factor + db 15 ; blm: block mask + db 0 ; exm: extent mask + dw 569 ; dsm: total storage in blocks - 1 blk = ((1,155k - 15k off) / 2k bls) - 1 = 569 + dw 255 ; drm: dir entries - 1 = 256 - 1 = 255 + db 11110000b ; al0: dir blk bit map, first byte + db 00000000b ; al1: dir blk bit map, second byte + dw 64 ; cks: directory check vector size = 256 / 4 + dw 2 ; off: reserved tracks = 2 trks * (512 b/sec * 60 sec/trk) = 15k + db 2 ; psh: 2 for 512 byte sectors + db 3 ; phm: (512 / 128) - 1 + + dseg ; rest is banked + + + + ; Disk I/O routines for standardized BIOS interface + +; Initialization entry point. + +; called for first time initialization. + +dsk$init: + ;ld a,(@rdrv) ; unit being initialized + ;ld hl,@bootdu + ;cp (hl) ; compare to boot unit + ;ret nz ; done if no match + ; + ;inc de ; point to slice in XDPH + ;inc de + ;inc de + ;ld a,(de) ; get slice + ;ld hl,@bootsl + ;cp (hl) ; compare to boot slice + ;ret nz ; done if not zero + ; + ;ld a,(@adrv) ; get cp/m drive + ;ld (@sysdr),a ; and save it + ret + +; lxi h,init$table +;fd$init$next: +; mov a,m ! ora a ! rz +; mov b,a ! inx h ! mov c,m ! inx h +; outir +; jmp fd$init$next +; +;fd$init1: ; all initialization done by drive 0 +; ret + +;init$table db 4,p$zpio$1A +; db 11001111b, 11000010b, 00010111b,11111111b +; db 4,p$zpio$1B +; db 11001111b, 11011101b, 00010111b,11111111b +; db 0 + + +dsk$login: + ; This entry is called when a logical drive is about to + ; be logged into for the purpose of density determination. + + ; It may adjust the parameters contained in the disk + ; parameter header pointed at by + + ;ret ; we have nothing to do in + ; simple single density only environment. + + + ;ld a,'L' + ;call cout + + push de ; save DPH ptr + + ; check media + ld a,(@rdrv) ; get disk unit + ;halt + ld c,a ; put in C + ld b,18h ; HBIOS Media function + ld e,1 ; Enable media check/discovery + ;rst 08 + call 0FFF0H ; HBIOS call + ld a,e ; Resultant media id to accum + or a ; Set flags + ;halt + ; + ; !!! Need to do something on error !!! + ; + jr nz,dsk$login0 ; continue if OK + pop de ; else error + ret ; return + +dsk$login0: + ld hl,dpb$start - dpb$sz + ld de,dpb$sz + ld b,a ; loop count +dsk$login1: + add hl,de ; next dpb + djnz dsk$login1 ; loop as needed + + ; hl is ptr to desired dpb + pop de ; restore DPH ptr + ;halt + ex de,hl ; de = DPB adr, hl = DPH adr + push de ; save DPB adr + ld de,12 ; offset of DPB in DPH + add hl,de ; hl = adr of DPB field in DPH + pop de ; recover DPB adr + ld (hl),e ; update LSB + inc hl + ld (hl),d ; udpate MSB + ret ; done + + +; disk READ and WRITE entry points. + + ; these entries are called with the following arguments: + + ; relative drive number in @rdrv (8 bits) + ; absolute drive number in @adrv (8 bits) + ; disk transfer address in @dma (16 bits) + ; disk transfer bank in @dbnk (8 bits) + ; disk track address in @trk (16 bits) + ; disk sector address in @sect (16 bits) + ; pointer to XDPH in + + ; they transfer the appropriate data, perform retries + ; if necessary, then return an error code in + +dsk$read: +; ld ix,30H +; halt + + ;ld a,'R' + ;call cout + + push de ; save XDPH pointer + call dsk$seek ; disk seek + pop hl ; restore pointer to HL + ret nz ; abort on seek error +; + dec hl ; point to unit field of XDPH + dec hl + ld c,(hl) ; BIOS Disk Unit in C + ld b,13H ; HBIOS READ function + ld hl,(@dma) ; Dest buffer adr + if banked + ld a,(@dbnk) ; destination bank + call ?bnkxlt + else + ld a,(0FFE0H) ; get current bank + endif + ld d,a ; set desk bank + ld e,1 ; 1 sector + rst 08 ; do it + ;call 0FFF0H + + ;call phex8 + ret ; return + +; lxi h,read$msg ; point at " Read " +; mvi a,88h ! mvi b,01h ; 1797 read + Z80DMA direction +; jmp rw$common + +dsk$write: + ;ld ix,32H + ;halt + + push de ; save XDPH pointer + call dsk$seek ; disk seek + pop hl ; restore pointer to XDPH + ret nz ; abort on seek error +; + dec hl ; point to unit field of XDPH + dec hl + ld c,(hl) ; BIOS Disk Unit in C + ld b,14H ; HBIOS WRITE function + ld hl,(@dma) ; Dest buffer adr + if banked + ld a,(@dbnk) ; destination bank + call ?bnkxlt + else + ld a,(0FFE0H) ; get current bank + endif + ld d,a ; set desk bank + ld e,1 ; 1 sector + rst 08 ; do it + ;call 0FFF0H + ret ; return + +; lxi h,write$msg ; point at " Write " +; mvi a,0A8h ! mvi b,05h ; 1797 write + Z80DMA direction +; ; jmp wr$common + +dsk$seek: + dec de ; point to unit field of XDPH + dec de + ld a,(de) ; get it + ld c,a ; BIOS Disk Unit in C + ld b,12H ; HBIOS SEEK function + push bc ; save it + push de ; save XDPH pointer + ld b,17h ; HBIOS DEVICE function + rst 08 ; Do it, D=device type + ld a,d ; put in accum + and 0F0h ; isolate high bits + ld b,1 ; assume it is floppy, 1 head bit + ld c,01h ; 1 bit head mask + cp 10h ; floppy? + jr z,seek0 ; yup, skip ahead + ld b,4 ; must be hard disk, 4 head bits + ld c,0Fh ; 4 bit head mask +seek0: + pop de ; recover XDPH pointer + push bc ; save bc + inc de ; point to slice field of XDPH + ld a,(de) ; get it + ld e,a ; slice to E + ld h,65 ; number of tracks per slice + call mult8 ; HL now has track offset for slice + pop bc ; recover bc + push hl ; save track offset for now + ld hl,(@trk) ; get track value + ld a,l ; lsb of track to a + and c ; apply mask + ld d,a ; save in d +seek1: + srl h ; shift one bit out + rr l ; ... of hl + djnz seek1 ; do all bits + ld a,(@sect) ; get sector + ld e,a ; stuff it in e + ex de,hl ; de=track, hl=head/sect + ex (sp),hl ; save head/sect, hl = offset + add hl,de ; hl has final track value + pop de ; recover head/sect to de + pop bc ; recover function & unit + rst 08 ; perform seek + ;call 0FFF0H + ret + +; +; multiply 8-bit values +; in: multiply h by e +; out: hl = result, e = 0, b = 0 +; +mult8: + ld d,0 + ld l,d + ld b,8 +mult8_loop: + add hl,hl + jr nc,mult8_noadd + add hl,de +mult8_noadd: + djnz mult8_loop + ret + +;rw$common: ; seek to correct track (if necessary), +; ; initialize DMA controller, +; ; and issue 1797 command. +; +; shld operation$name ; save message for errors +; sta disk$command ; save 1797 command +; mov a,b ! sta zdma$direction ; save Z80DMA direction code +; lhld @dma ! shld zdma$dma ; get and save DMA address +; lda @rdrv ! mov l,a ! mvi h,0 ; get controller-relative disk drive +; lxi d,select$table ! dad d ; point to select mask for drive +; mov a,m ! sta select$mask ; get select mask and save it +; out p$select ; select drive +;more$retries: +; mvi c,10 ; allow 10 retries +;retry$operation: +; push b ; save retry counter +; +; lda select$mask ! lxi h,old$select ! cmp m +; mov m,a +; jnz new$track ; if not same drive as last, seek +; +; lda @trk ! lxi h,old$track ! cmp m +; mov m,a +; jnz new$track ; if not same track, then seek +; +; in p$fdmisc ! ani 2 ! jnz same$track ; head still loaded, we are OK +; +;new$track: ; or drive or unloaded head means we should . . . +; call check$seek ; . . read address and seek if wrong track +; +; lxi b,16667 ; 100 ms / (24 t states*250 ns) +;spin$loop: ; wait for head/seek settling +; dcx b +; mov a,b ! ora c +; jnz spin$loop +; +;same$track: +; lda @trk ! out p$fdtrack ; give 1797 track +; lda @sect ! out p$fdsector ; and sector +; +; lxi h,dma$block ; point to dma command block +; lxi b,dmab$length*256 + p$zdma ; command block length and port address +; outir ; send commands to Z80 DMA +; +; in p$bankselect ; get old value of bank select port +; ani 3Fh ! mov b,a ; mask off DMA bank and save +; lda @dbnk ! rrc ! rrc ; get DMA bank to 2 hi-order bits +; ani 0C0h ! ora b ; merge with other bank stuff +; out p$bankselect ; and select the correct DMA bank +; +; lda disk$command ; get 1797 command +; call exec$command ; start it then wait for IREQ and read status +; sta disk$status ; save status for error messages +; +; pop b ; recover retry counter +; ora a ! rz ; check status and return to BDOS if no error +; +; ani 0001$0000b ; see if record not found error +; cnz check$seek ; if a record not found, we might need to seek +; +; dcr c ! jnz retry$operation +; +; ; suppress error message if BDOS is returning errors to application... +; +; lda @ermde ! cpi 0FFh ! jz hard$error +; +; ; Had permanent error, print message like: +; +; ; BIOS Err on d: T-nn, S-mm, , Retry ? +; +; call ?pderr ; print message header +; +; lhld operation$name ! call ?pmsg ; last function +; +; ; then, messages for all indicated error bits +; +; lda disk$status ; get status byte from last error +; lxi h,error$table ; point at table of message addresses +;errm1: +; mov e,m ! inx h ! mov d,m ! inx h ; get next message address +; add a ! push psw ; shift left and push residual bits with status +; xchg ! cc ?pmsg ! xchg ; print message, saving table pointer +; pop psw ! jnz errm1 ; if any more bits left, continue +; +; lxi h,error$msg ! call ?pmsg ; print ", Retry (Y/N) ? " +; call u$conin$echo ; get operator response +; cpi 'Y' ! jz more$retries ; Yes, then retry 10 more times +;hard$error: ; otherwise, +; mvi a,1 ! ret ; return hard error to BDOS +; +;cancel: ; here to abort job +; jmp ?wboot ; leap directly to warmstart vector +; +; +; ; subroutine to seek if on wrong track +; ; called both to set up new track or drive +; +;check$seek: +; push b ; save error counter +; call read$id ; try to read ID, put track in +; jz id$ok ; if OK, we're OK +; call step$out ; else step towards Trk 0 +; call read$id ; and try again +; jz id$ok ; if OK, we're OK +; call restore ; else, restore the drive +; mvi b,0 ; and make like we are at track 0 +;id$ok: +; mov a,b ! out p$fdtrack ; send current track to track port +; lda @trk ! cmp b ! pop b ! rz ; if its desired track, we are done +; out p$fddata ; else, desired track to data port +; mvi a,00011010b ; seek w/ 10 ms. steps +; jmp exec$command +; +; +; +;step$out: +; mvi a,01101010b ; step out once at 10 ms. +; jmp exec$command +; +;restore: +; mvi a,00001011b ; restore at 15 ms +; ; jmp exec$command +; +; +;exec$command: ; issue 1797 command, and wait for IREQ +; ; return status +; out p$fdcmnd ; send 1797 command +;wait$IREQ: ; spin til IREQ +; in p$fdint ! ani 40h ! jz wait$IREQ +; in p$fdstat ; get 1797 status and clear IREQ +; ret +; +;read$id: +; lxi h,read$id$block ; set up DMA controller +; lxi b,length$id$dmab*256 + p$zdma ; for READ ADDRESS operation +; outir +; mvi a,11000100b ; issue 1797 read address command +; call exec$command ; wait for IREQ and read status +; ani 10011101b ; mask status +; lxi h,id$buffer ! mov b,m ; get actual track number in +; ret ; and return with Z flag true for OK +; +; +;u$conin$echo: ; get console input, echo it, and shift to upper case +; call ?const ! ora a ! jz u$c1 ; see if any char already struck +; call ?conin ! jmp u$conin$echo ; yes, eat it and try again +;u$c1: +; call ?conin ! push psw +; mov c,a ! call ?cono +; pop psw ! cpi 'a' ! rc +; sui 'a'-'A' ; make upper case +; ret +; +; +;disk$command ds 1 ; current wd1797 command +;select$mask ds 1 ; current drive select code +;old$select ds 1 ; last drive selected +;old$track ds 1 ; last track seeked to +; +;disk$status ds 1 ; last error status code for messages +; +;select$table db 0001$0000b,0010$0000b ; for now use drives C and D +; +; +; ; error message components +; +;read$msg db ', Read',0 +;write$msg db ', Write',0 +; +;operation$name dw read$msg +; +; ; table of pointers to error message strings +; ; first entry is for bit 7 of 1797 status byte +; +;error$table dw b7$msg +; dw b6$msg +; dw b5$msg +; dw b4$msg +; dw b3$msg +; dw b2$msg +; dw b1$msg +; dw b0$msg +; +;b7$msg db ' Not ready,',0 +;b6$msg db ' Protect,',0 +;b5$msg db ' Fault,',0 +;b4$msg db ' Record not found,',0 +;b3$msg db ' CRC,',0 +;b2$msg db ' Lost data,',0 +;b1$msg db ' DREQ,',0 +;b0$msg db ' Busy,',0 +; +;error$msg db ' Retry (Y/N) ? ',0 +; +; +; +; ; command string for Z80DMA device for normal operation +; +;dma$block db 0C3h ; reset DMA channel +; db 14h ; channel A is incrementing memory +; db 28h ; channel B is fixed port address +; db 8Ah ; RDY is high, CE/ only, stop on EOB +; db 79h ; program all of ch. A, xfer B->A (temp) +;zdma$dma ds 2 ; starting DMA address +; dw 128-1 ; 128 byte sectors in SD +; db 85h ; xfer byte at a time, ch B is 8 bit address +; db p$fddata ; ch B port address (1797 data port) +; db 0CFh ; load B as source register +; db 05h ; xfer A->B +; db 0CFh ; load A as source register +;zdma$direction ds 1 ; either A->B or B->A +; db 0CFh ; load final source register +; db 87h ; enable DMA channel +;dmab$length equ $-dma$block + + + +;read$id$block db 0C3h ; reset DMA channel +; db 14h ; channel A is incrementing memory +; db 28h ; channel B is fixed port address +; db 8Ah ; RDY is high, CE/ only, stop on EOB +; db 7Dh ; program all of ch. A, xfer A->B (temp) +; dw id$buffer ; starting DMA address +; dw 6-1 ; Read ID always xfers 6 bytes +; db 85h ; byte xfer, ch B is 8 bit address +; db p$fddata ; ch B port address (1797 data port) +; db 0CFh ; load dest (currently source) register +; db 01h ; xfer B->A +; db 0CFh ; load source register +; db 87h ; enable DMA channel +;length$id$dmab equ $-read$id$block + + cseg ; easier to put ID buffer in common + +;id$buffer ds 6 ; buffer to hold ID field +; ; track +; ; side +; ; sector +; ; length +; ; CRC 1 +; ; CRC 2 + + cseg + +@sysdr db 0 ; system boot drive + + end diff --git a/Source/CPM3/drvtbl.z80 b/Source/CPM3/drvtbl.z80 new file mode 100644 index 00000000..27a4fda5 --- /dev/null +++ b/Source/CPM3/drvtbl.z80 @@ -0,0 +1,12 @@ + public @dtbl + extrn dph0,dph1,dph2,dph3,dph4,dph5,dph6,dph7 + extrn dph8,dph9,dph10,dph11,dph12,dph13,dph14,dph15 + + maclib cpm3.lib + + cseg + +@dtbl dw dph0,dph1,dph2,dph3,dph4,dph5,dph6,dph7 + dw dph8,dph9,dph10,dph11,dph12,dph13,dph14,dph15 + + end diff --git a/Source/CPM3/genbnk.dat b/Source/CPM3/genbnk.dat new file mode 100644 index 00000000..4a542bd3 --- /dev/null +++ b/Source/CPM3/genbnk.dat @@ -0,0 +1,158 @@ +PRTMSG = Y +PAGWID = 4F +PAGLEN = 17 +BACKSPC = N +RUBOUT = N +BOOTDRV = A +MEMTOP = FD +BNKSWT = Y +COMBAS = 80 +LERROR = Y +NUMSEGS = 03 +MEMSEG00 = 01,43,00 +MEMSEG01 = 0E,72,02 +MEMSEG02 = 01,7F,03 +MEMSEG03 = 01,7F,04 +MEMSEG04 = 00,C0,05 +MEMSEG05 = 00,C0,06 +MEMSEG06 = 00,C0,07 +MEMSEG07 = 00,C0,08 +MEMSEG08 = 00,C0,09 +MEMSEG09 = 00,C0,0A +MEMSEG0A = 00,C0,0B +MEMSEG0B = 00,C0,0C +MEMSEG0C = 00,C0,0D +MEMSEG0D = 00,C0,0E +MEMSEG0E = 00,C0,0F +MEMSEG0F = 00,C0,10 +HASHDRVA = Y +HASHDRVB = Y +HASHDRVC = Y +HASHDRVD = Y +HASHDRVE = Y +HASHDRVF = Y +HASHDRVG = Y +HASHDRVH = Y +HASHDRVI = Y +HASHDRVJ = Y +HASHDRVK = Y +HASHDRVL = Y +HASHDRVM = Y +HASHDRVN = Y +HASHDRVO = Y +HASHDRVP = Y +ALTBNKSA = Y +ALTBNKSB = Y +ALTBNKSC = Y +ALTBNKSD = Y +ALTBNKSE = Y +ALTBNKSF = Y +ALTBNKSG = Y +ALTBNKSH = Y +ALTBNKSI = Y +ALTBNKSJ = Y +ALTBNKSK = Y +ALTBNKSL = Y +ALTBNKSM = Y +ALTBNKSN = Y +ALTBNKSO = Y +ALTBNKSP = Y +NDIRRECA = 02 +NDIRRECB = 00 +NDIRRECC = 00 +NDIRRECD = 00 +NDIRRECE = 00 +NDIRRECF = 00 +NDIRRECG = 00 +NDIRRECH = 00 +NDIRRECI = 00 +NDIRRECJ = 00 +NDIRRECK = 00 +NDIRRECL = 00 +NDIRRECM = 00 +NDIRRECN = 00 +NDIRRECO = 00 +NDIRRECP = 00 +NDTARECA = 02 +NDTARECB = 00 +NDTARECC = 00 +NDTARECD = 00 +NDTARECE = 00 +NDTARECF = 00 +NDTARECG = 00 +NDTARECH = 00 +NDTARECI = 00 +NDTARECJ = 00 +NDTARECK = 00 +NDTARECL = 00 +NDTARECM = 00 +NDTARECN = 00 +NDTARECO = 00 +NDTARECP = 00 +ODIRDRVA = A +ODIRDRVB = A +ODIRDRVC = A +ODIRDRVD = A +ODIRDRVE = A +ODIRDRVF = A +ODIRDRVG = A +ODIRDRVH = A +ODIRDRVI = A +ODIRDRVJ = A +ODIRDRVK = A +ODIRDRVL = A +ODIRDRVM = A +ODIRDRVN = A +ODIRDRVO = A +ODIRDRVP = A +ODTADRVA = A +ODTADRVB = A +ODTADRVC = A +ODTADRVD = A +ODTADRVE = A +ODTADRVF = A +ODTADRVG = A +ODTADRVH = A +ODTADRVI = A +ODTADRVJ = A +ODTADRVK = A +ODTADRVL = A +ODTADRVM = A +ODTADRVN = A +ODTADRVO = A +ODTADRVP = A +OVLYDIRA = Y +OVLYDIRB = Y +OVLYDIRC = Y +OVLYDIRD = Y +OVLYDIRE = Y +OVLYDIRF = Y +OVLYDIRG = Y +OVLYDIRH = Y +OVLYDIRI = Y +OVLYDIRJ = Y +OVLYDIRK = Y +OVLYDIRL = Y +OVLYDIRM = Y +OVLYDIRN = Y +OVLYDIRO = Y +OVLYDIRP = Y +OVLYDTAA = Y +OVLYDTAB = Y +OVLYDTAC = Y +OVLYDTAD = Y +OVLYDTAE = Y +OVLYDTAF = Y +OVLYDTAG = Y +OVLYDTAH = Y +OVLYDTAI = Y +OVLYDTAJ = Y +OVLYDTAK = Y +OVLYDTAL = Y +OVLYDTAM = Y +OVLYDTAN = Y +OVLYDTAO = Y +OVLYDTAP = Y +CRDATAF = N +DBLALV = Y + \ No newline at end of file diff --git a/Source/CPM3/gencpm.com b/Source/CPM3/gencpm.com new file mode 100644 index 00000000..d9d67499 Binary files /dev/null and b/Source/CPM3/gencpm.com differ diff --git a/Source/CPM3/genres.dat b/Source/CPM3/genres.dat new file mode 100644 index 00000000..d1e1103d --- /dev/null +++ b/Source/CPM3/genres.dat @@ -0,0 +1,158 @@ +PRTMSG = Y +PAGWID = 4F +PAGLEN = 17 +BACKSPC = N +RUBOUT = N +BOOTDRV = A +MEMTOP = FD +BNKSWT = N +COMBAS = 00 +LERROR = Y +NUMSEGS = 03 +MEMSEG00 = 00,80,00 +MEMSEG01 = 00,C0,02 +MEMSEG02 = 00,C0,03 +MEMSEG03 = 00,C0,04 +MEMSEG04 = 00,C0,05 +MEMSEG05 = 00,C0,06 +MEMSEG06 = 00,C0,07 +MEMSEG07 = 00,C0,08 +MEMSEG08 = 00,C0,09 +MEMSEG09 = 00,C0,0A +MEMSEG0A = 00,C0,0B +MEMSEG0B = 00,C0,0C +MEMSEG0C = 00,C0,0D +MEMSEG0D = 00,C0,0E +MEMSEG0E = 00,C0,0F +MEMSEG0F = 00,C0,10 +HASHDRVA = N +HASHDRVB = N +HASHDRVC = N +HASHDRVD = N +HASHDRVE = N +HASHDRVF = N +HASHDRVG = N +HASHDRVH = N +HASHDRVI = N +HASHDRVJ = N +HASHDRVK = N +HASHDRVL = N +HASHDRVM = N +HASHDRVN = N +HASHDRVO = N +HASHDRVP = N +ALTBNKSA = N +ALTBNKSB = N +ALTBNKSC = N +ALTBNKSD = N +ALTBNKSE = N +ALTBNKSF = N +ALTBNKSG = N +ALTBNKSH = N +ALTBNKSI = N +ALTBNKSJ = N +ALTBNKSK = N +ALTBNKSL = N +ALTBNKSM = N +ALTBNKSN = N +ALTBNKSO = N +ALTBNKSP = N +NDIRRECA = 01 +NDIRRECB = 00 +NDIRRECC = 00 +NDIRRECD = 00 +NDIRRECE = 00 +NDIRRECF = 00 +NDIRRECG = 00 +NDIRRECH = 00 +NDIRRECI = 00 +NDIRRECJ = 00 +NDIRRECK = 00 +NDIRRECL = 00 +NDIRRECM = 00 +NDIRRECN = 00 +NDIRRECO = 00 +NDIRRECP = 00 +NDTARECA = 01 +NDTARECB = 00 +NDTARECC = 00 +NDTARECD = 00 +NDTARECE = 00 +NDTARECF = 00 +NDTARECG = 00 +NDTARECH = 00 +NDTARECI = 00 +NDTARECJ = 00 +NDTARECK = 00 +NDTARECL = 00 +NDTARECM = 00 +NDTARECN = 00 +NDTARECO = 00 +NDTARECP = 00 +ODIRDRVA = A +ODIRDRVB = A +ODIRDRVC = A +ODIRDRVD = A +ODIRDRVE = A +ODIRDRVF = A +ODIRDRVG = A +ODIRDRVH = A +ODIRDRVI = A +ODIRDRVJ = A +ODIRDRVK = A +ODIRDRVL = A +ODIRDRVM = A +ODIRDRVN = A +ODIRDRVO = A +ODIRDRVP = A +ODTADRVA = A +ODTADRVB = A +ODTADRVC = A +ODTADRVD = A +ODTADRVE = A +ODTADRVF = A +ODTADRVG = A +ODTADRVH = A +ODTADRVI = A +ODTADRVJ = A +ODTADRVK = A +ODTADRVL = A +ODTADRVM = A +ODTADRVN = A +ODTADRVO = A +ODTADRVP = A +OVLYDIRA = Y +OVLYDIRB = Y +OVLYDIRC = Y +OVLYDIRD = Y +OVLYDIRE = Y +OVLYDIRF = Y +OVLYDIRG = Y +OVLYDIRH = Y +OVLYDIRI = Y +OVLYDIRJ = Y +OVLYDIRK = Y +OVLYDIRL = Y +OVLYDIRM = Y +OVLYDIRN = Y +OVLYDIRO = Y +OVLYDIRP = Y +OVLYDTAA = Y +OVLYDTAB = Y +OVLYDTAC = Y +OVLYDTAD = Y +OVLYDTAE = Y +OVLYDTAF = Y +OVLYDTAG = Y +OVLYDTAH = Y +OVLYDTAI = Y +OVLYDTAJ = Y +OVLYDTAK = Y +OVLYDTAL = Y +OVLYDTAM = Y +OVLYDTAN = Y +OVLYDTAO = Y +OVLYDTAP = Y +CRDATAF = N +DBLALV = N + \ No newline at end of file diff --git a/Source/CPM3/loader.asm b/Source/CPM3/loader.asm new file mode 100644 index 00000000..ef80711d --- /dev/null +++ b/Source/CPM3/loader.asm @@ -0,0 +1,226 @@ +;=============================================================================== +; LOADER.ASM +; +; BOOTLOADER FOR ROMWBW DISK OPERATING SYSTEMS. +; +; CP/M DISK FORMATS ALLOW FOR RESERVED TRACKS THAT CONTAIN AN IMAGE OF THE +; OPERATING SYSTEM TO BE LOADED WHEN THE DISK IS BOOTED. THE OPERATING SYSTEM +; IMAGE ITSELF IS NORMALLY PREFIXED BY A 1-N SECTORS CONTAINING OS BOOTSTRAP +; CODE AND DISK METADATA. +; +; THE RETROBREW COMPUTING GROUP HAS BEEN USING A CONVENTION OF PREFIXING THE +; OS IMAGE WITH 3 SECTORS (512 BYTES X 3 FOR A TOTAL OF 1536 BYTES): +; +; SECTOR 1: IBM-PC STYLE BOOT BLOCK CONTAINING BOOTSTRAP, +; PARTITION TABLE, AND BOOT SIGNATURE +; SECTOR 2: RESERVED +; SECTOR 3: METADATA +; +; THE HARDWARE BIOS IS EXPECTED TO READ AND LOAD THE FIRST TWO SECTORS FROM THE +; DISK TO MEMORY ADDRESS $8000 AND JUMP TO THAT LOCATION TO BEGIN THE BOOT +; PROCESS. THE BIOS IS EXPECTED TO VERIFY THAT A STANDARD BOOT SIGNATURE +; OF $55, $AA IS PRESENT AT OFFSET $1FE-$1FF. IF THE SIGNATURE IS NOT FOUND, +; THE BIOS SHOULD ASSUME THE DISK HAS NOT BEEN PROPERLY INITIALIZED AND SHOULD +; NOT JUMP TO THE LOAD ADDRESS. +; +;=============================================================================== +; +#INCLUDE "../ver.inc" +; +SYS_ENT .EQU $0100 ; SYSTEM (OS) ENTRY POINT ADDRESS +SYS_LOC .EQU $0100 ; STARTING ADDRESS TO LOAD SYSTEM IMAGE +SYS_END .EQU $1480 ; ENDING ADDRESS OF SYSTEM IMAGE +; +SEC_SIZE .EQU 512 ; DISK SECTOR SIZE +BLK_SIZE .EQU 128 ; OS BLOCK/RECORD SIZE +; +PREFIX_SIZE .EQU (SEC_SIZE * 3) ; 3 SECTORS +; +META_SIZE .EQU 32 ; SEE BELOW +META_LOC .EQU (PREFIX_SIZE - META_SIZE) +; +PT_LOC .EQU $1BE +PT_SIZ .EQU $40 +; +;------------------------------------------------------------------------------- +; SECTOR 1 +; +; THIS SECTOR FOLLOWS THE CONVENTIONS OF AN IBM-PC MBR CONTAINING THE OS +; BOOTSTRAP CODE, PARTITION TABLE, AND BOOT SIGNATURE +; +;---------------------------------------------------------------------------- +; +; THE FOLLOWING BOOTSTRAP CODE IS BUILT TO ASSUME IT WILL BE EXECUTED AT A STARTING +; ADDRESS OF $8000. THIS CODE IS *ONLY* FOR UNA. THE ROMWBW ROM BOOTLOADER +; USES THE METADATA TO LOAD THE OS DIRECTLY. +; + .ORG $8000 + JR BOOT +; +BOOT: + LD DE,STR_LOAD ; LOADING STRING + CALL PRTSTR ; PRINT + CALL PRTDOT ; PROGRESS +; + LD BC,$00FC ; UNA FUNC: GET BOOTSTRAP HISTORY + CALL $FFFD ; CALL UNA + JR NZ,ERROR ; HANDLE ERROR + CALL PRTDOT ; PROGRESS + LD B,L ; MOVE BOOT UNIT ID TO B +; + LD C,$41 ; UNA FUNC: SET LBA + LD DE,0 ; HI WORD ALWAYS ZERO + LD HL,3 ; IMAGE STARTS AT FOURTH SECTOR + CALL $FFFD ; SET LBA + JR NZ,ERROR ; HANDLE ERROR + CALL PRTDOT ; PROGRESS +; + LD C,$42 ; UNA FUNC: READ SECTORS + LD DE,$D000 ; STARTING ADDRESS FOR IMAGE + LD L,22 ; READ 22 SECTORS + CALL $FFFD ; DO READ + JR NZ,ERROR ; HANDLE ERROR + CALL PRTDOT ; PROGRESS +; + LD DE,STR_DONE ; DONE MESSAGE + CALL PRTSTR ; PRINT IT +; + LD D,B ; PASS BOOT UNIT TO OS + LD E,0 ; ASSUME LU IS ZERO + JP SYS_ENT ; GO TO SYSTEM +; +PRTCHR: + PUSH BC + PUSH DE + LD BC,$0012 ; UNIT 0, WRITE CHAR + LD E,A ; CHAR TO PRINT + CALL $FFFD ; PRINT + POP DE + POP BC + RET +; +PRTSTR: + PUSH BC + PUSH HL + LD BC,$0015 ; UNIT 0, WRITE CHARS UNTIL TERMINATOR + LD L,0 ; TERMINATOR IS NULL + CALL $FFFD ; PRINT + POP HL + POP BC + RET +; +PRTDOT: + LD A,'.' ; DOT CHARACTER + JR PRTCHR ; PRINT AND RETURN +; +; PRINT THE HEX BYTE VALUE IN A +; +PRTHEXBYTE: + PUSH AF + PUSH DE + CALL HEXASCII + LD A,D + CALL PRTCHR + LD A,E + CALL PRTCHR + POP DE + POP AF + RET +; +; CONVERT BINARY VALUE IN A TO ASCII HEX CHARACTERS IN DE +; +HEXASCII: + LD D,A + CALL HEXCONV + LD E,A + LD A,D + RLCA + RLCA + RLCA + RLCA + CALL HEXCONV + LD D,A + RET +; +; CONVERT LOW NIBBLE OF A TO ASCII HEX +; +HEXCONV: + AND $0F ;LOW NIBBLE ONLY + ADD A,$90 + DAA + ADC A,$40 + DAA + RET +; +ERROR: + LD DE,STR_ERR ; POINT TO ERROR STRING + CALL PRTSTR ; PRINT IT + HALT ; HALT +; +; DATA +; +STR_LOAD .DB "\r\nLoading",0 +STR_DONE .DB "\r\n",0 +STR_ERR .DB " Read Error!",0 +; + .ORG $ - $8000 ; BACK TO ABSOLUTE ADDRESS +; + .FILL PT_LOC - $,0 ; FILL TO START OF PARTITION TABLE +; +; RESERVE SPACE FOR STANDARD IBM-PC PARTITION TABLE. ALTHOUGH A +; PARTITION TABLE IS NOT RELEVANT FOR A FLOPPY DISK, IT DOES NO HARM. +; THE CONTENTS OF THE PARTITION TABLE MUST BE MANAGED BY FDISK80. +; +PARTTBL .FILL PT_SIZ,0 ; PARTITION TABLE, FILL WITH ZEROES +; +; THE END OF THE FIRST SECTOR MUST CONTAIN THE TWO BYTE BOOT SIGNATURE. +; +BOOTSIG .DB $55,$AA ; STANDARD BOOT SIGNATURE +; +;------------------------------------------------------------------------------- +; SECTOR 2 +; +; THIS SECTOR HAS NOT BEEN DEFINED AND IS RESERVED. +; +;---------------------------------------------------------------------------- +; + .FILL SEC_SIZE,0 ; JUST FILL SECTOR WITH ZEROES +; +;------------------------------------------------------------------------------- +; SECTOR 3 +; +; OS AND DISK METADATA +; +;---------------------------------------------------------------------------- +; + .FILL (BLK_SIZE * 3),0 ; FIRST 384 BYTES ARE NOT YET DEFINED +; +; THE FOLLOWING TWO BYTES ARE AN ADDITIONAL SIGNATURE THAT IS VERIFIED BY +; SOME HARDWARE BIOSES. +; +PR_SIG .DB $5A,$A5 ; SIGNATURE GOES HERE +; + .FILL (META_LOC - $),0 +; +; METADATA +; +PR_WP .DB 0 ; (1) WRITE PROTECT BOOLEAN +PR_UPDSEQ .DW 0 ; (2) PREFIX UPDATE SEQUENCE NUMBER (DEPRECATED?) +PR_VER .DB RMJ,RMN,RUP,RTP ; (4) OS BUILD VERSION +PR_LABEL .DB "Unlabeled$$$$$$$","$" ; (17) DISK LABEL (EXACTLY 16 BYTES!!!) + .DW 0 ; (2) DEPRECATED +PR_LDLOC .DW SYS_LOC ; (2) ADDRESS TO START LOADING SYSTEM +PR_LDEND .DW SYS_END ; (2) ADDRESS TO STOP LOADING SYSTEM +PR_ENTRY .DW SYS_ENT ; (2) ADDRESS TO ENTER SYSTEM (OS) +; +#IF (META_SIZE != ($ - META_LOC)) + .ECHO "META_SIZE VALUE IS WRONG!!!\r\n" + !!! +#ENDIF +; +#IF ($ != PREFIX_SIZE) + .ECHO "LOADER PREFIX IS WRONG SIZE!!!\r\n" + !!! +#ENDIF +; + .END diff --git a/Source/CPM3/makedate.lib b/Source/CPM3/makedate.lib new file mode 100644 index 00000000..3fe3f25b --- /dev/null +++ b/Source/CPM3/makedate.lib @@ -0,0 +1,16 @@ +; +; [JCE] Have the date and copyright messages in only one source file +; +@BDATE MACRO + db '101198' + ENDM + +@LCOPY MACRO + db 'Copyright 1998, ' + db 'Caldera, Inc. ' + ENDM + +@SCOPY MACRO + db '(c) 98 Caldera' + ENDM + diff --git a/Source/CPM3/modebaud.lib b/Source/CPM3/modebaud.lib new file mode 100644 index 00000000..29853d35 --- /dev/null +++ b/Source/CPM3/modebaud.lib @@ -0,0 +1,32 @@ + ; equates for mode byte bit fields +; +mb$input equ 00000001b ; device may do input +mb$output equ 00000010b ; device may do output +mb$in$out equ mb$input+mb$output +; +mb$soft$baud equ 00000100b ; software selectable + ; baud rates +; +mb$serial equ 00001000b ; device may use protocol +mb$xonxoff equ 00010000b ; XON/XOFF protocol + ; enabled +; +baud$none equ 0 ; no baud rate associated + ; with this device +baud$50 equ 1 ; 50 baud +baud$75 equ 2 ; 75 baud +baud$110 equ 3 ; 110 baud +baud$134 equ 4 ; 134.5 baud +baud$150 equ 5 ; 150 baud +baud$300 equ 6 ; 300 baud +baud$600 equ 7 ; 600 baud +baud$1200 equ 8 ; 1200 baud +baud$1800 equ 9 ; 1800 baud +baud$2400 equ 10 ; 2400 baud +baud$3600 equ 11 ; 3600 baud +baud$4800 equ 12 ; 4800 baud +baud$7200 equ 13 ; 7200 baud +baud$9600 equ 14 ; 9600 baud +baud$19200 equ 15 ; 19.2k baud +; +; end of file diff --git a/Source/CPM3/move.z80 b/Source/CPM3/move.z80 new file mode 100644 index 00000000..1b5dc067 --- /dev/null +++ b/Source/CPM3/move.z80 @@ -0,0 +1,99 @@ + title 'bank & move module for CP/M3 linked BIOS' + + cseg + + public ?move,?xmove,?bank,?bnkxlt + public ?mvinit,@hbbio,@hbusr + extrn @cbnk + +?mvinit: + ld bc,0F8F2h ; HBIOS GET BNKINFO + ;rst 08 ; D: BIOS Bank, E: User Bank + call 0FFF0h ; D: BIOS Bank, E: User Bank + ld a,d + ld (@hbbio),a + ld a,e + ld (@hbusr),a + ret + +?xmove: + ld (movbnks),bc ; save source & dest banks + or 0FFh ; flag interbank move type + ld (movtyp),a ; save it + ret + +?move: + ld a,(movtyp) ; get move type flag + or a ; set flags + jr nz,xbnkmov ; if so, go to interbank move + + ; Intrabank move + ex de,hl ; we are passed source in DE and dest in HL + ldir ; use Z80 block move instruction + ex de,hl ; need next addresses in same regs + ret + +xbnkmov: + ;ld ix,8888H + ;halt + ; Interbank move + xor a ; zero + ld (movtyp),a ; clear move type flag + ld a,(srcbnk) + call ?bnkxlt + ld (0FFE4h),a + ld a,(dstbnk) + call ?bnkxlt + ld (0FFE7h),a + ex de,hl ; swap address regs for call + call 0FFF6h ; HBIOS BNKCPY + ex de,hl ; next addresses in same regs + ret + +?bank: + call ?bnkxlt ; xlat to HBIOS bank id + jp 0FFF3h ; do it and return + +; +; Convert from CPM3 bank id to HBIOS bank id. +; CPM3 wants TPA for it's bank 0, so that is special +; case mapping to HBIOS BID_USR (8Eh). Otherwise, we index +; down below BID_HBIOS (8Dh). So CPM3 bank usage grows +; downward. +; +; CPM3 HBIOS Typical +; ------------ -------------- ------- +; COMMON BID_COM 8Fh +; 0: OS/BUFS BID_USR 8Eh +; BID_BIOS 8Dh +; 1: TPA BID_AUX 8Ch +; 2: BUFS BID_AUX-1 8Bh +; 3: BUFS BID_AUX-2 8Ah +; ... +; +; N.B., Below BID_AUX is considered RAM disk bank. Need to +; make sure RAM disk is kept small enough to stay below +; banks used for OS buffers. +; +; @hbbio & @hbusr are dynamically updated during init +; to adjust for real size of RAM in system +; +?bnkxlt: + or a + jr z,bank0 + neg ; 2 -> -2 + add a,8Dh ; 8Dh - 2 = 8Bh +@hbbio equ $ - 1 ; BID_BIOS + ret +bank0: + ld a,8Eh ; 0 -> 8Eh +@hbusr equ $ - 1 ; BID_USR + ret + +movtyp db 0 ; non-zero for interbank move + +movbnks: +srcbnk db 0 +dstbnk db 0 + + end diff --git a/Source/CPM3/optbnk.lib b/Source/CPM3/optbnk.lib new file mode 100644 index 00000000..93dbb3c3 --- /dev/null +++ b/Source/CPM3/optbnk.lib @@ -0,0 +1,8 @@ + ; global assembler options for BANKED BIOS + +true equ -1 +false equ not true + +banked equ true + +zpm equ false diff --git a/Source/CPM3/optcmd.lib b/Source/CPM3/optcmd.lib new file mode 100644 index 00000000..3f74e713 --- /dev/null +++ b/Source/CPM3/optcmd.lib @@ -0,0 +1,7 @@ + ; global assembler options for CP/M loader + ; when loading from a command line + +true equ -1 +false equ not true + +cmdline equ true diff --git a/Source/CPM3/optdsk.lib b/Source/CPM3/optdsk.lib new file mode 100644 index 00000000..c2caa8b3 --- /dev/null +++ b/Source/CPM3/optdsk.lib @@ -0,0 +1,7 @@ + ; global assembler options for CP/M loader + ; when loading from a boot disk + +true equ -1 +false equ not true + +cmdline equ false diff --git a/Source/CPM3/optres.lib b/Source/CPM3/optres.lib new file mode 100644 index 00000000..1c08bd24 --- /dev/null +++ b/Source/CPM3/optres.lib @@ -0,0 +1,8 @@ + ; global assembler options for NONBANKED BIOS + +true equ -1 +false equ not true + +banked equ false + +zpm equ false diff --git a/Source/CPM3/optzpm.lib b/Source/CPM3/optzpm.lib new file mode 100644 index 00000000..eba22e9b --- /dev/null +++ b/Source/CPM3/optzpm.lib @@ -0,0 +1,9 @@ + ; global assembler options for BANKED BIOS + ; with Boot Drive swapped into Drive A + +true equ -1 +false equ not true + +banked equ true + +zpm equ true diff --git a/Source/CPM3/readme.1st b/Source/CPM3/readme.1st new file mode 100644 index 00000000..1277e897 --- /dev/null +++ b/Source/CPM3/readme.1st @@ -0,0 +1,39 @@ +CP/M 3 +====== + + This archive contains an almost complete build of CP/M 3. + + If you have the source distribution, the file MAKING.DOC explains how to +set up the build environment on your computer. + +Differences from Digital Research CP/M 3 +======================================== + + All the CP/M 3 patches described in the document CPM3FIX.PAT have been +applied to the source code, except those to INITDIR. Patches 1-18 (except +nos. 5 and 9) were applied. + + CP/M 3 is now fully Year 2000 compliant. This affects the programs +DATE.COM, DIR.COM and SHOW.COM. + + Dates can be displayed in US, UK or Year-Month-Day format. This is set by +SETDEF: + + SETDEF [US] + SETDEF [UK] + SETDEF [YMD] respectively. + + The CCP has a further bug fix: A command sequence such as: + + C1 + :C2 + :C3 + + will now not execute the command C3 if the command C1 failed. + +What's missing? +=============== +INITDIR.COM - because it is written in PL/I and I can't make the + PL/I compiler at compile it. + Apparently a more recent version of the compiler is + required. \ No newline at end of file diff --git a/Source/CPM3/resbdos3.spr b/Source/CPM3/resbdos3.spr new file mode 100644 index 00000000..a5f21969 Binary files /dev/null and b/Source/CPM3/resbdos3.spr differ diff --git a/Source/CPM3/scb.asm b/Source/CPM3/scb.asm new file mode 100644 index 00000000..d2fdd427 --- /dev/null +++ b/Source/CPM3/scb.asm @@ -0,0 +1,55 @@ + title 'System Control Block Definition for CP/M3 BIOS' + + public @civec, @covec, @aivec, @aovec, @lovec, @bnkbf + public @crdma, @crdsk, @vinfo, @resel, @fx, @usrcd + public @mltio, @ermde, @erdsk, @media, @bflgs + public @date, @hour, @min, @sec, ?erjmp, @mxtpa + public @ccpdr + public @srch1, @srch2, @srch3, @srch4 + + +scb$base equ 0FE00H ; Base of the SCB + +@CCPDR equ scb$base+13h ; CCP Current Drive +@CIVEC equ scb$base+22h ; Console Input Redirection + ; Vector (word, r/w) +@COVEC equ scb$base+24h ; Console Output Redirection + ; Vector (word, r/w) +@AIVEC equ scb$base+26h ; Auxiliary Input Redirection + ; Vector (word, r/w) +@AOVEC equ scb$base+28h ; Auxiliary Output Redirection + ; Vector (word, r/w) +@LOVEC equ scb$base+2Ah ; List Output Redirection + ; Vector (word, r/w) +@BNKBF equ scb$base+35h ; Address of 128 Byte Buffer + ; for Banked BIOS (word, r/o) +@CRDMA equ scb$base+3Ch ; Current DMA Address + ; (word, r/o) +@CRDSK equ scb$base+3Eh ; Current Disk (byte, r/o) +@VINFO equ scb$base+3Fh ; BDOS Variable "INFO" + ; (word, r/o) +@RESEL equ scb$base+41h ; FCB Flag (byte, r/o) +@FX equ scb$base+43h ; BDOS Function for Error + ; Messages (byte, r/o) +@USRCD equ scb$base+44h ; Current User Code (byte, r/o) +@MLTIO equ scb$base+4Ah ; Current Multi-Sector Count + ; (byte,r/w) +@ERMDE equ scb$base+4Bh ; BDOS Error Mode (byte, r/o) +@SRCH1 equ scb$base+4Ch ; BDOS Drive Search Chain 1 +@SRCH2 equ scb$base+4Dh ; BDOS Drive Search Chain 2 +@SRCH3 equ scb$base+4Eh ; BDOS Drive Search Chain 3 +@SRCH4 equ scb$base+4Fh ; BDOS Drive Search Chain 4 +@ERDSK equ scb$base+51h ; BDOS Error Disk (byte,r/o) +@MEDIA equ scb$base+54h ; Set by BIOS to indicate + ; open door (byte,r/w) +@BFLGS equ scb$base+57h ; BDOS Message Size Flag (byte,r/o) +@DATE equ scb$base+58h ; Date in Days Since 1 Jan 78 + ; (word, r/w) +@HOUR equ scb$base+5Ah ; Hour in BCD (byte, r/w) +@MIN equ scb$base+5Bh ; Minute in BCD (byte, r/w) +@SEC equ scb$base+5Ch ; Second in BCD (byte, r/w) +?ERJMP equ scb$base+5Fh ; BDOS Error Message Jump + ; (word, r/w) +@MXTPA equ scb$base+62h ; Top of User TPA + ; (address at 6,7)(word, r/o) + end diff --git a/Source/CPM3/util.z80 b/Source/CPM3/util.z80 new file mode 100644 index 00000000..28b39244 --- /dev/null +++ b/Source/CPM3/util.z80 @@ -0,0 +1,149 @@ + title 'Utility module for RomWBW' + + maclib options.lib + + public addhla, bcd2bin, bin2bcd + public phex16, phex8, cout, crlf, crlf2 + + cseg + +addhla: + add a,l + ld l,a + ret nc + inc h + ret + +bcd2bin: + ; convert A from packed bcd to binary + push bc + ld c,a + and 0F0h + srl a + ld b,a + srl a + srl a + add a,b + ld b,a + ld a,c + and 0Fh + add a,b + pop bc + ret + +bin2bcd: + ; convert A from binary to packed bcd + push bc + ld b,10 + ld c,-1 +bin2bcd1: + inc c + sub b + jr nc,bin2bcd1 + add a,b + ld b,a + ld a,c + add a,a + add a,a + add a,a + add a,a + or b + pop bc + ret + + if 0 +; +; Print the hex word value in HL +; +phex16: + push af + ld a,h + call phex8 + ld a,l + call phex8 + pop af + ret +; +; Print the hex byte value in A +; +phex8: + push af + push de + call hexascii + ld a,d + call cout + ld a,e + call cout + pop de + pop af + ret + +; +; Convert binary value in A to ascii hex characters in DE +; +hexascii: + ld d,a + call hexconv + ld e,a + ld a,d + rlca + rlca + rlca + rlca + call hexconv + ld d,a + ret +; +; convert low nibble of A to ascii hex +; +hexconv: + and 0Fh ;low nibble only + add a,90h + daa + adc a,40h + daa + ret +; +; output character from A +; +cout: + ; save all incoming registers + push af + push bc + push de + push hl + ld e,a + ld bc,0100h + rst 08 + pop hl + pop de + pop bc + pop af + ret +; +; output 1 or 2 newlines +; +crlf2: + call crlf +crlf: + ; save all incoming registers + push af + ld a,13 + call cout + ld a,10 + call cout + pop af + ret + + else + +phex16: +phex8: +cout: +crlf2: +crlf: + halt + + endif + + end diff --git a/Source/Clean.cmd b/Source/Clean.cmd index 63f011c0..b097dc88 100644 --- a/Source/Clean.cmd +++ b/Source/Clean.cmd @@ -7,9 +7,11 @@ setlocal & cd ZCPR && call Clean.cmd & endlocal setlocal & cd ZCPR-DJ && call Clean.cmd & endlocal setlocal & cd ZSDOS && call Clean.cmd & endlocal setlocal & cd CBIOS && call Clean.cmd & endlocal +setlocal & cd CPM3 && call Clean.cmd & endlocal +setlocal & cd ZPM3 && call Clean.cmd & endlocal setlocal & cd Forth && call Clean.cmd & endlocal +setlocal & cd Fonts && call Clean.cmd & endlocal setlocal & cd BPBIOS && call Clean.cmd & endlocal setlocal & cd HBIOS && call Clean.cmd & endlocal -setlocal & cd Doc && call Clean.cmd & endlocal setlocal & cd Images && call Clean & endlocal setlocal & cd Prop && call Clean & endlocal diff --git a/Source/Doc/Applications.md b/Source/Doc/Applications.md new file mode 100644 index 00000000..4a145533 --- /dev/null +++ b/Source/Doc/Applications.md @@ -0,0 +1,963 @@ +!include(Common.inc) +!def(document)(Applications) +--- +title: !product !document +author: !author (mailto:!authmail) +date: !date +institution: !orgname +documentclass: article +toc: true +toc-depth: 1 +classoption: + - oneside +papersize: letter +geometry: + - top=1.5in + - bottom=1.5in + - left=1.5in + - right=1.5in +# - showframe +linestretch: 1.25 +colorlinks: true +fontfamily: helvet +fontsize: 12pt +header-includes: + - | + ```{=latex} + \renewcommand*{\familydefault}{\sfdefault} + ``` +--- + +`\clearpage % new page after TOC`{=latex} + +# Summary + +RomWBW includes a small suite of custom applications to maximize the +features available. In general, these applications are operating +system agnostic -- they run under any of the included operating +systems. However, they all require RomWBW -- they are not generic CP/M +applications. + +Most of the applications are custom written for RomWBW. However, some +are standard CP/M applications that have been adapted to run under +RomWBW (e.g., XModem). The applications are generally matched to the +version of RomWBW they are distributed with. So, if you upgrade the +version of RomWBW in your system ROM, you will want to copy the +corresponding applications to any storage devices you are using. + +Most of the applications are included on the RomWBW ROM disk, so they +are easy to access. + +The applications are also included with all of the operating system +disk images provided with RomWBW. So, a simple way to ensure you have +matching applications is to write the disk images onto your disk media +when upgrading your ROM. Of course, this will destroy any existing +data on your disk media, so don't do this if you are saving any data +on the media. + +Most of the applications are included as source code in the RomWBW +distribution and are built in the normal build process. The source +code is found in the Source\\Apps directory of the distribution. The +binary executable applications are found in the Binary\\Apps directory. + +The following table clarifies where each of the applications can be +found: + +| Application | ROM Disk | Boot Disks | Apps Dir | +| ----------- | -------- | ---------- | -------- | +| ASSIGN | Yes | Yes | Yes | +| SYSCOPY | Yes | Yes | Yes | +| MODE | Yes | Yes | Yes | +| FDU | Yes | Yes | Yes | +| FORMAT | Yes | Yes | Yes | +| XM | Yes | Yes | Yes | +| FLASH | Yes | Yes | Yes | +| FDISK80 | Yes | Yes | Yes | +| TALK | Yes | Yes | Yes | +| RTC | Yes | Yes | Yes | +| TIMER | Yes | Yes | Yes | +| INTTEST | Yes | Yes | Yes | +| FAT | No | Yes | Yes | +| TUNE | No | Yes | Yes | + +`\clearpage`{=latex} + +# ASSIGN + +RomWBW includes a flexible mechanism for associating the operating +system drive letters (A: - P:) to the physical devices in the system. +Drive letter assignments can be changed on a running operating system +without rebooting. The ASSIGN command facilitates this by allowing you +to display, assign, reassign, or remove the drive letter assignments. + +## Syntax + +| `ASSIGN /?` +| `ASSIGN /L` +| `ASSIGN [`*``*`],...` +| `ASSIGN `*``*`=[`*``*`:[`*``*`]],...` +| `ASSIGN `*``*`=`*``*`,...` + +## Usage + +`ASSIGN /?` will display brief command usage and version information. + +`ASSIGN /L` will display a list of all the devices available to be +used in drive assignments in the running system. The devices listed +may or may not contain media. Although some device types support the +use of slices, the list does not indicate this. + +`ASSIGN` with no parameters will list all of the current drive +assignments. + +`ASSIGN `*``* will display the assignment for the specific drive +For example, `ASSIGN C:` will display the assignment for drive C:. + +`ASSIGN `*``*`=`*``*`[:`*``*`]` will assign (or +reassign) a drive letter to a new device and (optionally) slice. If no +slice is specified, then slice 0 is assumed. For example, `ASSIGN +C:=IDE0` will assign drive letter C: to device IDE0, slice 0. `ASSIGN +D:=IDE0:3` will assign drive letter D: to device IDE0 slice 3. + +`ASSIGN `*``*`=` can be used to remove the assignment from a +drive letter. So, `ASSIGN E:=` will remove the association of drive +letter E: from any previous device. + +`ASSIGN `*``*`=`*``* is used to swap the assignments +of two drive letters. For example, `ASSIGN C:=D:` will swap the device +assignments of C: and D:. + +The `ASSIGN` command supports "stacking" of instructions. For example, +`ASSIGN C:=IDE0:0,D:=IDE0:1,E:=` will assign C: and D: to the first +two slices of IDE 0 and will unassign E:. + +When the command runs it will echo the resultant assignments to the +console to confirm it's actions. It will also display the remaining +space available in disk buffers. + +## Notes + +If the `ASSIGN` command encounters any rule violations or errors, it +will abort with an error and **none** of the drive assignments will be +implemented. In other words, the command is atomic and will either +completely succeed or completely fail. + +All assigned drives utilize disk buffer space from a limited pool. The +ASSIGN command will display the amount of buffer space remaining +after an assign command is executed. Buffer space is freed if a drive +is unassigned. If the total assignments exceed the available disk +buffer space available, the command will abort with an error message. + +The `ASSIGN` command does not check to see if the device and slice +being assigned actually contains readable media. If the assigned +device has no media, you will receive an I/O error when you attempt to +use the drive letter. + +The `ASSIGN` command will not allow you to specify a slice (other than +zero) for devices that do not support slices (such as floppy drives +or RAM/ROM disks). + +The `ASSIGN` command does not check that the media is large enough to +support the slice you specify. In other words, you could potentially +assign a drive letter to a slice that is beyond the end of the media +in a device. In this case, subsequent attempts to use that drive +letter will result in an I/O error. + +Additionally, the `ASSIGN` command does not check to see if the slice +specified refers to an area on your media that is occupied by other +data (such as a FAT filesystem). + +You will not be allowed to assign multiple drive letters to a single +device and slice. In other words, only one drive letter may refer to a +single filesystem at a time. + +Drive letter A: must always be assigned to a device and slice. The +`ASSIGN` command will enforce this. + +The changes made by this command are not permanent. The assignments +will persist through a warm start, but when you reboot your system, +all drive letters will return to their default assignments. A SUBMIT +batch file can be used to setup desired drive assignments +automatically at boot. + +Floppy disk drives and RAM/ROM drives do not have slices. A slice +should only be specified for hard disk devices (SD, IDE, PPIDE). + +Only one drive letter may be assigned to a specific device/unit/slice +at a time. Attempts to assign a duplicate drive letter will fail and +display an error. If you wish to assign a different drive letter to a +device/unit/slice, unassign the the existing drive letter first. + +Be aware that this command will allow you to reassign or remove the +assignment of your system drive letter. This can cause your operating +system to fail and force you to reboot. + +This command is particularly sensitive to being matched to the +appropriate version of the RomWBW ROM you are using. Be very careful +to keep all copies of `ASSIGN.COM` up to date with your ROM. + +## Etymology + +The `ASSIGN` command is an original product and the source code is +provided in the RomWBW distribution. + +`\clearpage`{=latex} + +# SYSCOPY + +To make disk media bootable, you must write a system boot image onto +the system tracks of the of the media. The `SYSCOPY` allows you to +read or write the system boot image of disk media. + +## Syntax + +| `SYSCOPY `*``*`=`*``* + +*``* is the drive to receive the operating system image or +alternatively a filename to save the operating system image + +*``* is the drive containing an operating system image or +alternatively a filename containing the system image to be placed on +the destination + +## Usage + +Both *``* and *``* can refer to either a drive letter or a +file. If a drive letter is specified, the system boot image will be +read or written to the system tracks of the drive. If a filename is +specified, the system boot image will be read or written to the +specified filename. + +`SYSCOPY C:=ZSYS.SYS` will read a system boot image from the file +ZSYS.SYS and write it onto the system tracks of drive C:. + +`SYSCOPY A:OS.SYS=C:` will capture the system boot image from the +system tracks of drive C: and store it in the file A:OS.SYS. + +`SYSCOPY D:=C:` will copy the system tracks from drive C: onto the +system tracks of drive D:. + +## Notes + +The RomWBW ROM disk contains files with the system boot image for +Z-System and CP/M 2.2. These files are called CPM.SYS and ZSYS.SYS +respectively. These files can be used as the source of a `SYSCOPY` +command to make a disk bootable with the corresponding operating +system. + +CP/M 3 uses a two phase boot process. To make a CP/M 3 drive bootable, +you need to put "CPMLDR.SYS" on the boot tracks of the disk and be +sure that the drive also contains the "CPM.SYS" file. The "CPMLDR.SYS" +file is not included on the ROM disk, but is found on the CP/M 3 disk +image. + +ZPM3 is similar to CP/M 3. You also put "CPMLDR.SYS" on the system +tracks of the drive to make it bootable. The ZPM3 operating system is +in the file called "CPM3.SYS" on the ZPM3 disk image. It may seem +confusing that ZPM3 is in the file called CPM3.SYS, but it is normal +for ZPM3. + +For the purposes of booting an operating system, each disk slice is +considered it's own operating system. Each slice can be made bootable +with it's own system tracks. + +`SYSCOPY` uses drive letters to specify where to read/write the system +boot images. However, at startup, the boot loaded will require you to +enter the actual disk device and slice to boot from. So, you need to +be careful to pay attention to the device and slice that is assigned +to a drive letter so you will know what to enter at the boot loader +prompt. By way of explanation, the boot loader does not know about +drive letters because the operating system is not loaded yet. + +If you want to put a a boot system image on a device and slice that is +not currently assigned to a drive letter, you will need to assign a +drive letter first. + +Not all disk formats include space for system tracks. Such disk +formats cannot contains a system boot image and, therefore, cannot be +made bootable. The best example of such disk formats are the ROM and +RAM disks. To maximize usable file space on these drives, they do not +have system tracks. Obviously, ROM operating system is supported by +choosing a ROM operating system at the boot loader prompt. Any attempt +to write a system boot image to disk media with no system tracks will +cause SYSCOPY to fail with an error message. + +The system boot images are paired with the ROM version in your system. +So, you must take care to update the system tracks of any bootable +disk when you upgrade your ROM firmware. + +The system boot images are **not** tied to specific hardware +configurations. System boot images and operating systems provided with +RomWBW will work with any supported RomWBW platform or hardware as +long as they are the same version as the RomWBW firmware. + +## Etymology + +The `SYSCOPY` command is an original product and the source code is +provided in the RomWBW distribution. + +`\clearpage`{=latex} + +# MODE + +The MODE command allows you to adjust the operating characteristics +such as baud rate, data bits, stop bits, and parity bits of serial +ports dynamically. + +## Syntax + +`MODE /?` +`MODE COM`*``*`: [`*``*`[,`*``*`[,`*``*`[,`*``*`]]]] [/P]` + +`/?` displays command usage and version information + +*``* is the character device unit number + +*``* is numerical baudrate + +*``* is (N)one, (O)dd, (E)ven, (M)ark, or (S)pace + +*``* is number of data bits, typically 7 or 8 + +*``* is number of stop bits, typically 1 or 2 + +`/P` prompts user prior to setting new configuration + +## Usage + +`MODE /?` will display basic command usage and version information. + +`MODE` with no parameters will list all devices and their current +configuration. + +`MODE <`*n*`>` will display the current configuration of the specified +character device unit. + +`MODE COM`*``*`: [`*``*`[,`*``*`[,`*``*`[,` +*``*`]]]] [/P]` requests that the specified configuration +be set on the character device unit. You can use commas with no values +to leave some values unchanged. As an example, `MODE COM0: 9600,,,2` +will setup character device unit 0 for 9600 baud and 2 stop bits while +leaving data bits and stop bits as is. + +Appending `/P` in a command specifying a new configuration will cause +the terminal output to pause and wait for the user to press a key. +This allows the user to change the local terminal setup before +continuing. + +## Notes + +Specified baud rate and line characteristics must be supported by the +serial unit. Any parameters not specified will remain unchanged. + +Changes are not persisted and will revert to system defaults at next +system boot. + +Not all character devices support all `MODE` options. Some devices +(notably ASCI devices) have limited baud rate divisors. An attempt to +set a baud rate that the device cannot support will fail with an error +message. + +## Etymology + +The `SYSCOPY` command is an original product and the source code is +provided in the RomWBW distribution. + +`\clearpage`{=latex} + +# FDU + +The FDU application is a Floppy Disk Utility that provides functions +to format and test floppy disk media. + +## Syntax + +`FDU` + +## Usage + +This application has an interactive user interface. At startup, you +will be prompted to select the floppy interface hardware in your +system. Following this, you will see the main menu of the program with +many functions to manage floppy disk drives. + +The primary documentation for this application is in a file called +"FDU.txt" in the Doc directory of the RomWBW distribution. Please +consult this file for usage information. + +## Notes + +This application interfaces directly to the floppy hardware in your +system. It does not use the RomWBW HBIOS. This means that even if your +system is not configured for floppy drives, you can still use `FDU` +to test your floppy drives and format floppy media. This also means it +is critical that you choose the correct hardware interface from the +initial selection when starting the application. + +## Etymology + +The `FDU` command is an original product and the source code is +provided in the RomWBW distribution. + +`\clearpage`{=latex} + +# FORMAT + +This application is just a placeholder for a future version that will +make it simpler to format media including floppy disks. + +## Syntax + +`FORMAT` + +## Notes + +This application currently just displays a few lines of information +briefly instructing a user how to format media. It performs no actual +function beyond this display currently. + +## Etymology + +The `FORMAT` command is an original product and the source code is +provided in the RomWBW distribution. + +`\clearpage`{=latex} + +# XM + +An adaptation of Ward Christensen's X-Modem protocol for transferring +files between systems using a serial port. + +## Syntax + +| `XM S `*``* +| `XM SK `*``* +| `XM L `*` `* +| `XM LK `*` `* +| `XM R `*``* + +`S`: Send a file +`L`: Send a file from a library +`R`: Receive a file +`K`: Use 1K blocksize for transfer + +*``* is the name of a file to send or receive + +*``* is the name of a library (.lbr) to extract a file to send + +## Usage + +To transfer a file from your host computer to your RomWBW computer, do +the following: + +1. Enter one of the `XM` receive commands specifying the name you want +to give to the received file. + +2. On your host computer select a file to send and initiate the XModem +send operation. + +To transfer a file from your RomWBW computer to your host computer, do +the following: + +1. Enter one of the `XM` send commands specifying the name of the file +to be sent. + +2. On your host computer, specify the name to assign to the received +file and initiate and XModem receive operation. + +Please refer to the documentation of your host computer's terminal +emulation software for specific instructions on how to use XModem. + +## Notes + +The XModem adaptation that comes with RomWBW will automatically use +the primary character device unit (character device unit 0) for the +file transfer. + +`XM` attempts to determine the best way to drive the serial port based +on your hardware configuration. When possible, it will bypass the +HBIOS for faster operation. However, in many cases, it will use HBIOS +so that flow control can be used. + +`XM` is dependent on a reliable communications channel. You must +ensure that the serial port can be serviced fast enough by either +using a baud rate that is low enough or ensuring that hardware flow +control is fully functional (end to end). + +## Etymology + +The `XM` application provided in RomWBW is an adaptation of a +pre-existing XModem application. Based on the source code comments, it +was originally adapted from Ward Christensen's MODEM2 by Keith +Petersen and is labeled version 12.5. + +The original source of the application was found in the Walnut Creek +CD-ROM and is called XMDM125.ARK dated 7/15/86. + +The actual application is virtually untouched in the RomWBW +adaptation. The majority of the work was in the modem driver which was +enhanced to detect the hardware being used and dynamically choose the +appropriate driver. + +The source code is provided in the RomWBW distribution. + +`\clearpage`{=latex} + +# FLASH + +Most of the hardware platforms that run RomWBW support the use of +EEPROMs -- Electronically Erasable Programmable ROMs. The `FLASH` +application can be used to reprogram such ROMS in-situ (in-place), +thus making it possible to upgrade ROMs without a programmer or even +removing the ROM from your system. + +This application is provided by Will Sowerbutts. + +## Syntax + +| `FLASH READ `*``*` [options]` +| `FLASH VERIFY `*``*` [options]` +| `FLASH WRITE `*``*` [options]` + +*``* is the filename of the ROM image file + +Options: (access method is auto-detected by default) + +| `/PARTIAL`: Allow flashing a large ROM from a smaller image file +| `/ROM`: Allow read-only use of unknown chip types +| `/Z180DMA`: Force Z180 DMA engine +| `/UNABIOS`: Force UNA BIOS bank switching +| `/ROMWBW`: Force RomWBW (v2.6+) bank switching +| `/ROMWBWOLD`: Force RomWBW (v2.5 and earlier) bank switching +| `/P112`: Force P112 bank switching + +## Usage + +To program your EEPROM ROM chip, first transfer the file to your +RomWBW system. Then use the command `FLASH WRITE *``*. The +application will auto-detect the type of EEPROM chip you have, +program it, and verify it. + +You can use the `FLASH READ` form of the command to read the ROM image +from your system into a file. This is useful if you want to save a +copy of your current ROM before reprogramming it. + +Although `FLASH WRITE` automatically performs a verification, you can +manually perform a verification function with the `FLASH VERIFY` form +of the command. + +The author's documentation for the application is found in the RomWBW +distribution in the Doc\\Contrib directory. + +## Notes + +The application supports a significant number of EEPROM parts. It +should automatically detect your part. If it does not recognize your +chip, make sure that you do not have a write protect jumper set -- +this jumper will cause the ROM chip type to be unrecognized. + +Reprogramming a ROM chip in-place is inherently dangerous. If anything +goes wrong, you will be left with a non-functional system and no +ability to run the `FLASH` application again. Use this application +with caution and be prepared to use a hardware ROM programmer to +restore your system if needed. + +## Etymology + +This application was written and provided by Will Sowerbutts. He +provides it in binary format and is included in the RomWBW +distribution as a binary file. + +The source code for this application can be found at the [FLASH4 +GitHub repository](https://github.com/willsowerbutts/flash4). + +`\clearpage`{=latex} + +# FDISK80 + +RomWBW supports disk media with MS-DOS FAT filesystems (see FAT +application). If you wish to put a FAT filesystem on your media, the +FDISK80 application can be used to partition your media which is +required in order to add a FAT filesystem. + +This application is provided by John Coffman. + +## Usage + +`FDISK80` is an interactive application. At startup it will ask you +for the disk unit that you want to partition. When your RomWBW system +boots, it will display a table with the disk unit numbers. Use the +disk unit numbers from that table to enter the desired disk unit to +partition. + +`FDISK80` operates very much like other FDISK disk partitioning +applications. Please refer to the file called "FDisk Manual.pdf" in +the Doc directory of the RomWBW distribution for further instructions. + +There is also more information on using FAT partitions with RomWBW in +the "RomWBW Getting Started.pdf" document in the Doc directory of the +distribution. + +## Notes + +Partitioning of RomWBW media is **only** required if you want to add a +FAT filesystem to your media. Do not partition your media if you are +simply using it for RomWBW. To be clear, RomWBW slices do not require +partitioning. + +As described in "RomWBW Getting Started.pdf", you should be careful +when adding a FAT partition to your media that the partition does not +overlap with the area of the media being used for RomWBW slices. The +"(R)eserve" function in `FDISK80` can help prevent this. + +## Etymology + +The source for this application was provided directly by John Coffman. +It is a C program and requires a build environment that includes the +SDCC compiler. As such, it is not included in the RomWBW build +process, only the binary executable is included. + +Please contact John Coffman if you would like a copy of the source. + +`\clearpage`{=latex} + +# TALK + +It is sometimes useful to direct your console input/output to a +designated serial port. For example, if you were to connect a modem +to your second serial port, you might want to connect directly to it +and have everything you type sent to it and everything it sends be +shown on your console. The `TALK` application does this. + +## Syntax + +`TALK [TTY:|CRT:|BAT:UC1:]` + +## Usage + +`TALK` operates at the operating system level (not HBIOS). + +The parameter to `TALK` refers to logical CP/M serial devices. Upon +execution all characters types at the console will be sent to the +device specified and all characters received by the specified device +will be echoes on the console. + +Press Control+Z on the console to terminate the application. + +## Notes + +This application is designed for CP/M 2.2 or Z-System. Use on later +operating systems such as CP/M 3 is not supported. + +## Etymology + +The `TALK` command is an original product and the source code is +provided in the RomWBW distribution. + +`\clearpage`{=latex} + +# RTC + +Many RomWBW systems provide real time clock hardware. The RTC +application is a simple, interactive program allowing you to display +and set the time and registers of the RTC. + +## Syntax + +`RTC` + +## Usage + +After startup, the application provides the following options: + +| Option | Function | +| ----------- | ------------------------------------------------------ | +| `E)xit` | will terminate the application. | +| `T)ime` | will display the time as read from the RTC hardware. | +| `st(A)rt` | will restart the clock running if it is stopped. | +| `S)et` | will program the RTC clock with the date/time previously entered using the I)nit option. | +| `R)aw` | will read the minute/second of the RTC clock iteratively every time the space key is pressed. Press enter to end. | +| `L)oop` | will read the full date/time of the RTC clock iteratively every time the space key is pressed. Press enter to end. | +| `C)harge` | will enable the battery charging function of the RTC. | +| `N)ocharge` | will disable the battery charging functino of the RTC. | +| `D)elay` | allows you to test the built-in timing delay in the program. It is not unusual for it to be wrong. | +| `I)nit` | allows you to enter a date/time value for subsequent programming of the RTC using the S)et option. | +| `G)et` | allows you to read the value of a non-volatile register in the RTC. | +| `P)ut` | allows you to write the value of a non-volatile register in the RTC. | +| `B)oot` | will reboot your system. | +| `H)elp` | displays brief help. | + +## Notes + +When using Get and Put options, the register number to read/write is +entered in hex. The non-volatile ram register numbers are 0x20-0x3F. + +When entering values, you must enter exactly two hex characters. The +backspace key is not supported. You do not use enter after entering +the two hex characters. Yes, this should be improved. + +The `RTC` application interacts directly with the RTC hardware +bypassing HBIOS. + +## Etymology + +The `RTC` application was originally written by Andrew Lync as part of +the original ECB SBC board development. It has since been modified to +support most of the hardware variations included with RomWBW. + +`\clearpage`{=latex} + +# TIMER + +Most RomWBW systems have a 50Hz periodic system timer. A counter is +incremented every time a timer tick occurs. The `TIMER` application +displays the value of the counter. + +## Syntax + +`TIMER` +`TIMER /?` +`TIMER /C` + +## Usage + +Use `TIMER` to display the current value of the counter. + +Use `TIMER /C` to display the value of the counter continuously. + +The display of the counter will be something like this: + +`00045444 Ticks, 0000162A.10 Seconds` + +The first number is the total number of ticks since system startup. +The second number is the total number of seconds since system startup. + +## Notes + +The seconds value is displayed with a fractional value which is not a +an actual fraction, but rather the number of ticks past the seconds +rollover. All values are in hex. + +The primary use of the `TIMER` application is to test the system +timer functionality of your system. + +In theory, you could capture the value before and after some process +you want to time. + +## Etymology + +The `TIMER` command is an original product and the source code is +provided in the RomWBW distribution. + +`\clearpage`{=latex} + +# INTTEST + +RomWBW includes an API allowing applications to "hook" interrupts. +The `INTTEST` application allows you to test this functionality. + +## Syntax + +`INTTEST` + +## Usage + +`INTTEST` is an interactive application. At startup, it will display +a list of the interrupt vector slots in your system along with the +current vector address for each of them. + +It then prompts you to enter the slot number (in hex) of a vector to +hook. After entering this, the application will watch the hooked +vector and countdown from 0xFF to 0x00 as interrupts are noted. + +When the counter reaches 0x00, the interrupt is unhooked and the +application terminates. The application can also be terminated by +pressing . + +## Notes + +If your system is running without interrupts active, the application +will terminate immediately. + +All slots have vectors even if the corresponding interrupt is not +doing anything. In this case, the vector is pointing to the "bad +interrupt" handler. + +If you hook a vector that is not receiving any interrupts, the +downcounter will not do anything. + +## Etymology + +The `INTTEST` command is an original product and the source code is +provided in the RomWBW distribution. + +`\clearpage`{=latex} + +# FAT + +The operating systems included with RomWBW do not have any native +ability to access MS-DOS FAT filesystems. The FAT application can be +used overcome this. It will allow you to transfer files between CP/M +and FAT filesystems (wildcards supported). It can also erase files, +format, and list directories of FAT filesystems. + +## Syntax + +| `FAT DIR `*``* +| `FAT COPY `*` `* +| `FAT REN `*` `* +| `FAT DEL `*`[|]`* +| `FAT MD `*``* +| `FAT FORMAT `*``* + +| *``* is a FAT path +| *``*, *``* are FAT or CP/M filenames +| *``*, *``* are FAT filenames +| *``* is a FAT filename +| *``* is a FAT directory name +| *``* is a RomWBW disk unit number + +| CP/M filespec: *``*`:FILENAME.EXT` (*``* is CP/M drive letter A-P) +| FAT filespec: *``*`:/DIR/FILENAME.EXT` (*``* is RomWBW disk unit #) + +## Usage + +The `FAT` application determines whether you are referring to a CP/M +filesystem or a FAT filesystem based on the way you specify the file +or path. If the file or path is prefixed with a number (n:), then it +is assumed this is a FAT filesystem reference and is referring to the +FAT filesystem on RomWBW disk unit 'n'. Otherwise, the file +specification is assumed to be a normal CP/M file specification. + +If you wanted to list the directory of the FAT filesystem on RomWBW +disk unit 2, you would use `FAT DIR 2:`. If you only wanted to see the +".TXT" files, you would use `FAT DIR 2:*.TXT`. + +If you wanted to copy all of the files on CP/M drive B: to the FAT +filesystem on RomWBW disk unit 4, you would use the command `FAT COPY +B:*.* 4:` If you wanted to copy the files to the "FOO" directory, then +you would use `FAT COPY B:*.* 4:\FOO`. To copy files in the opposite +direction, you just reverse the parameters. + +To rename the file "XXX.DAT" to "YYY.DAT" on a FAT filesystem, you +could use a command like "FAT REN 2:XXX.DAT 2:YYY.DAT". + +To delete a file "XXX.DAT" on a FAT filesystem in directory "FOO", you +would use a command like `FAT DEL 2:\FOO\XXX.DAT`. + +To make a directory called "FOO2" on a FAT filesystem, you would use a +command line `FAT MD 2:\FOO2`. + +To format the filesystem on a FAT partition, you would use a command +like `FAT FORMAT 2:`. Use this with caution because it will destroy +all data on any pre-existing FAT filesystem on disk unit 2. + +## Notes + +Partitioned or non-partitioned media is handled automatically. A +floppy drive is a good example of a non-partitioned FAT filesystem and +will be recognized. Larger media will typically have a partition +table which will be recognized by the application to find the FAT +filesystem. + +Although RomWBW-style CP/M media does not know anything about +partition tables, it is entirely possible to have media that has both +CP/M and FAT file systems on it. This is accomplished by creating a +FAT filesystem on the media that starts on a track beyond the last +track used by CP/M. Each CP/M slice on a media will occupy 8,320K +(16,640 sectors). So, make sure to start your FAT partition beyond (< +slice count> * 8,320K) or (`* + +*``* is the name of a sound file ending in .PT2, .PT3, or +.MYM + +## Usage + +The TUNE application supports PT and YM sound file formats. It +determines the format of the file from the extension of the file, so +your tune filenames should end in .PT2, .PT3, or .MYM. + +To play a sound file, just use the command and specify the file to +play after the command. So, for example, `TUNE ATTACK.PT2` will +immediately begin playing the PT sound file "ATTACK.PT2". + +## Notes + +The `TUNE` application automatically probes for compatible hardware at +well known port addresses at startup. It will auto-configure itself +for the hardware found. If no hardware is detected, it will abort with +an error message. + +On Z180 systems, I/O wait states are added when writing to the sound +chip to avoid exceeding it's speed limitations. On Z80 systems, you +will need to ensure that the CPU clock speed of your system does not +exceed the timing limitations of your sound chip. + +The application probes for an active system timer and uses it to +accurately pace the sound file output. If no system timer is +available, a delay loop is calculated instead. The delay loop will not +be as accurate as the system timer. + +All RomWBW operating system boot disks include a selection of sound +files in user area 3. + +## Etymology + +The `TUNE` application was custom written for RomWBW. All of the +hardware interface code is specific to RomWBW. The sound file decoding +software was adapted and embedded from pre-existing sources. The YM +player code is from MYMPLAY 0.4 by Lieves!Tuore and the PT player code +is (c)2004-2007 S.V.Bulba . + +The source code is provided in the RomWBW distribution. \ No newline at end of file diff --git a/Source/Doc/Architecture.md b/Source/Doc/Architecture.md new file mode 100644 index 00000000..90d531ba --- /dev/null +++ b/Source/Doc/Architecture.md @@ -0,0 +1,1723 @@ +!include(Common.inc) +!def(document)(Architecture) +--- +title: | + | !product + | + | !document +author: !author (mailto:!authmail) +date: !date +institution: !orgname +documentclass: book +classoption: +- oneside +toc: true +toc-depth: 1 +numbersections: true +secnumdepth: 1 +papersize: letter +geometry: +- top=1.5in +- bottom=1.5in +- left=1.5in +- right=1.5in +# - showframe +# - pass +linestretch: 1.25 +colorlinks: true +fontfamily: helvet +fontsize: 12pt +header-includes: +- \setlength{\headheight}{15pt} +- | + ```{=latex} + \usepackage{fancyhdr} + \usepackage{xcolor} + \usepackage{xhfill} + \renewcommand*{\familydefault}{\sfdefault} + \renewcommand{\maketitle}{ + \begin{titlepage} + \centering + \par + \vspace*{0pt} + \includegraphics[width=\paperwidth]{Graphics/Logo.pdf} \par + \vfill + \raggedleft + {\scshape \bfseries \fontsize{48pt}{56pt} \selectfont !product \par} + {\bfseries \fontsize{32pt}{36pt} \selectfont !document \par} + \vspace{24pt} + {\huge Version !ver \\ !date \par} + \vspace{24pt} + {\large \itshape !orgname \\ \href{http://!orgurl}{!orgurl} \par} + \vspace{12pt} + {\large \itshape !author \\ \href{mailto:authmail}{!authmail} \par} + \end{titlepage} + } + \pagestyle{empty} + ``` +include-before: +- \renewcommand{\chaptername}{Section} +- | + ```{=latex} + \pagestyle{fancyplain} + \fancyhf{} + \lfoot{\small RetroBrew Computing Group ~~ {\xrfill[3pt]{1pt}[cyan]} ~~ \thepage} + \pagenumbering{roman} + ``` +--- + +```{=latex} +\clearpage +\pagenumbering{arabic} +\lhead{\fancyplain{}{\nouppercase{\footnotesize \bfseries \leftmark \hfill !product !document}}} +``` + +Overview +======== + +RomWBW provides a complete firmware package for all of the Z80 and Z180 +based systems that are available in the RetroBrew Computers Community +(see +[http://www.retrobrewcomputers.org](http://www.retrobrewcomputers.org/)) +as well as support for the RC2014 platform. Each of these systems +provides for a fairly large ROM memory (typically, 512KB or more). +RomWBW allows you to configure and build appropriate contents for such a +ROM. + +Typically, a computer will contain a small ROM that contains the BIOS +(Basic Input/Output System) functions as well as code to start the +system by booting an operating system from a disk. Since the RetroBrew +Computers Projects provide a large ROM space, RomWBW provides a much +more comprehensive software package. In fact, it is entirely possible to +run a fully functioning RetroBrew Computers System with nothing but the +ROM. + +RomWBW firmware includes: + +* System startup code (bootstrap) + +* A basic system/debug monitor + +* HBIOS (Hardware BIOS) providing support for the vast majority of +RetroBrew Computers I/O components + +* A complete operating system (either CP/M 2.2 or ZSDOS 1.1) + +* A built-in CP/M filesystem containing the basic applications and +utilities for the operating system and hardware being used + +It is appropriate to note that much of the code and components that make +up a complete RomWBW package are derived from pre-existing work. Most +notably, the embedded operating system is simply a ROM-based copy of +generic CP/M or ZSDOS. Much of the hardware support code was originally +produced by other members of the RetroBrew Computers Community. + +The remainder of this document will focus on the HBIOS portion of the +ROM. HBIOS contains the vast majority of the custom-developed code for +the RetroBrew Computers hardware platforms. It provides a formal, +structured interface that allows the operating system to be hosted with +relative ease. + +Background +========== + +The Z80 CPU architecture has a limited, 64K address range. In general, +this address space must accommodate a running application, disk +operating system, and hardware support code. + +All RetroBrew Computers Z80 CPU platforms provide a physical address +space that is much larger than the CPU address space (typically 512K or +1MB physical RAM). This additional memory can be made available to the +CPU using a technique called bank switching. To achieve this, the +physical memory is divided up into chunks (banks) of 32K each. A +designated area of the CPU's 64K address space is then reserved to "map" +any of the physical memory chunks. You can think of this as a window +that can be adjusted to view portions of the physical memory in 32K +blocks. In the case of RetroBrew Computers platforms, the lower 32K of +the CPU address space is used for this purpose (the window). The upper +32K of CPU address space is assigned a fixed 32K area of physical memory +that never changes. The lower 32K can be "mapped" on the fly to any of +the 32K banks of physical memory at a time. The only constraint is that +the CPU cannot be executing code in the lower 32K of CPU address space +at the time that a bank switch is performed. + +By cleverly utilizing the pages of physical RAM for specific purposes +and swapping in the correct page when needed, it is possible to utilize +substantially more than 64K of RAM. Because the RetroBrew Computers +Project has now produced a very large variety of hardware, it has become +extremely important to implement a bank switched solution to accommodate +the maximum range of hardware devices and desired functionality. + +General Design Strategy +======================= + +The design goal is to locate as much of the hardware dependent code as +possible out of normal 64KB CP/M address space and into a bank switched +area of memory. A very small code shim (proxy) is located in the top 512 +bytes of CPU memory. This proxy is responsible for redirecting all +hardware BIOS (HBIOS) calls by swapping the "driver code" bank of +physical RAM into the lower 32K and completing the request. The +operating system is unaware this has occurred. As control is returned to +the operating system, the lower 32KB of memory is switched back to the +original memory bank. + +HBIOS is completely agnostic with respect to the operating system (it +does not know or care what operating system is using it). The operating +system makes simple calls to HBIOS to access any desired hardware +functions. Since the HBIOS proxy occupies only 512 bytes at the top of +memory, the vast majority of the CPU memory is available to the +operating system and the running application. As far as the operating +system is concerned, all of the hardware driver code has been magically +implemented inside of a small 512 byte area at the top of the CPU +address space. + +Unlike some other Z80 bank switching schemes, there is no attempt to +build bank switching into the operating system itself. This is +intentional so as to ensure that any operating system can easily be +adapted without requiring invasive modifications to the operating system +itself. This also keeps the complexity of memory management completely +away from the operating system and applications. + +There are some operating systems that have built-in support for bank +switching (e.g., CP/M 3). These operating systems are allowed to make +use of the bank switched memory and are compatible with HBIOS. However, +it is necessary that the customization of these operating systems take +into account the banks of memory used by HBIOS and not attempt to use +those specific banks. + +Note that all code and data are located in RAM memory during normal +execution. While it is possible to use ROM memory to run code, it would +require that more upper memory be reserved for data storage. It is +simpler and more memory efficient to keep everything in RAM. At startup +(boot) all required code is copied to RAM for subsequent execution. + +Runtime Memory Layout +===================== + +![Banked Switched Memory Layout](Graphics/Bank Switched Memory){ width=80% } + +System Boot Process +=================== + +A multi-phase boot strategy is employed. This is necessary because at +cold start, the CPU is executing code from ROM in lower memory which is +the same area that is bank switched. + +Boot Phase 1 copies the phase 2 code to upper memory and jumps to it to +continue the boot process. This is required because the CPU starts at +address \$0000 in low memory. However, low memory is used as the area +for switching ROM/RAM banks in and out. Therefore, it is necessary to +relocate execution to high memory in order to initialize the RAM memory +banks. + +Boot Phase 2 manages the setup of the RAM page banks for HBIOS +operation, performs hardware initialization, and then executes the boot +loader. + +Boot Phase 3 is the loading of the selecting operating system (or debug +monitor) by the Boot Loader. The Boot Loader is responsible for +prompting the user to select a target operating system to load, loading +it into RAM, then transferring control to it. The Boot Loader is capable +of loading a target operating system from a variety of locations +including disk drives and ROM. + +Note that the entire boot process is entirely operating system agnostic. +It is unaware of the operating system being loaded. The Boot Loader +prompts the user for the location of the binary image to load, but does +not know anything about what is being loaded (the image is usually an +operating system, but could be any executable code image). Once the Boot +Loader has loaded the image at the selected location, it will transfer +control to it. Assuming the typical situation where the image was an +operating system, the loaded operating system will then perform it's own +initialization and begin normal operation. + +There are actually two ways to perform a system boot. The first, and +most commonly used, method is a "ROM Boot". This refers to booting the +system directly from the startup code contained on the physical ROM +chip. A ROM Boot is always performed upon power up or when a hardware +reset is performed. + +Once the system is running (operating system loaded), it is possible to +reboot the system from a system image contained on the file system. This +is referred to as an "Application Boot". This mechanism allows a +temporary copy of the system to be uploaded and stored on the file +system of an already running system and then used to boot the system. +This boot technique is useful to: 1) test a new build of a system image +before programming it to the ROM; or 2) easily switch between system +images on the fly. + +A more detailed explanation of these two boot processes is presented +below. + +ROM Boot +-------- + +At power on (or hardware reset), ROM page 0 is automatically mapped to +lower memory by hardware level system initialization. Page Zero (first +256 bytes of the CPU address space) is reserved to contain dispatching +instructions for interrupt instructions. Address \$0000 performs a jump +to the start of the phase 1 code so that this first page can be +reserved. + +The phase 1 code now copies the phase 2 code from lower memory to upper +memory and jumps to it. The phase 2 code now initializes the HBIOS by +copying the ROM resident HBIOS from ROM to RAM. It subsequently calls +the HBIOS initialization routine. Finally, it starts the Boot Loader +which prompts the user for the location of the target system image to +execute. + +Once the boot loader transfers control to the target system image, all +of the Phase 1, Phase 2, and Boot Loader code is abandoned and the space +it occupied is normally overwritten by the operating system. + +Application Boot +---------------- + +When a new system image is built, one of the output files produced is an +actual CP/M application (an executable .COM program file). Once you have +a running CP/M (or compatible) system, you can upload/copy this +application file to the filesystem. By executing this file, you will +initiate an Application Boot using the system image contained in the +application file itself. + +Upon execution, the Application Boot program is loaded into memory by +the previously running operating system starting at \$0100. Note that +program image contains a copy of the HBIOS to be installed and run. Once +the Application Boot program is loaded by the previous operating system, +control is passed to it and it performs a system initialization similar +to the ROM Boot, but using the image loaded in RAM. + +Specifically, the code at \$0100 (in low memory) copies phase 2 boot +code to upper memory and transfers control to it. The phase 2 boot code +copies the HBIOS image from application RAM to RAM, then calls the HBIOS +initialization routine. At this point, the prior HBIOS code has been +discarded and overwritten. Finally, the Boot Loader is invoked just like +a ROM Boot. + +Notes +----- + +1. Size of ROM disk and RAM disk will be decreased as needed to +accommodate RAM and ROM memory bank usage for the banked BIOS. + +2. There is no support for interrupt driven drivers at this time. Such +support should be possible in a variety of ways, but none are yet +implemented. + +Driver Model +============ + +The framework code for bank switching also allows hardware drivers to be +implemented mostly without concern for memory management. Drivers are +coded to simply implement the HBIOS functions appropriate for the type +of hardware being supported. When the driver code gets control, it has +already been mapped to the CPU address space and simply performs the +requested function based on parameters passed in registers. Upon return, +the bank switching framework takes care of restoring the original memory +layout expected by the operating system and application. + +However, the one constraint of hardware drivers is that any data buffers +that are to be returned to the operating system or applications must be +allocated in high memory. Buffers inside of the driver's memory bank +will be swapped out of the CPU address space when control is returned to +the operating system. + +If the driver code must make calls to other code, drivers, or utilities +in the driver bank, it must make those calls directly (it must not use +RST 08). This is to avoid a nested bank switch which is not supported at +this time. + +Character / Emulation / Video Services +====================================== + +In addition to a generic set of routines to handle typical character +input/output, HBIOS also includes functionality for managing built-in +video display adapters. To start with there is a basic set of character +input/output functions, the CIOXXX functions, which allow for simple +character data streams. These functions fully encompass routing byte +stream data to/from serial ports. Note that there is a special character +pseudo-device called "CRT". When characters are read/written to/from the +CRT character device, the data is actually passed to a built-in terminal +emulator which, in turn, utilizes a set of VDA (Video Display Adapter) +functions (such as cursor positioning, scrolling, etc.). + +Figure 7.1 depicts the relationship between these components +of HBIOS video processing: + +![Character / Emulation / Video Services](Graphics/Character Emulation Video Services){ width=100% } + +Normally, the operating system will simply utilize the CIOXXX functions +to send and receive character data. The Character I/O Services will +route I/O requests to the specified physical device which is most +frequently a serial port (such as UART or ASCI). As shown above, if the +CRT device is targeted by a CIOXXX function, it will actually be routed +to the Emulation Services which implement TTY, ANSI, etc. escape +sequences. The Emulation Services subsequently rely on the Video Display +Adapter Services as an additional layer of abstraction. This allows the +emulation code to be completely unaware of the actual physical device +(device independent). Video Display Adapter (VDA) Services contains +drivers as needed to handle the available physical video adapters. + +Note that the Emulation and VDA Services API functions are available to +be called directly. Doing so must be done carefully so as to not corrupt +the "state" of the emulation logic. + +Before invoking CIOXXX functions targeting the CRT device, it is +necessary that the underlying layers (Emulation and VDA) be properly +initialized. The Emulation Services must be initialized to specify the +desired emulation and specific physical VDA device to target. Likewise, +the VDA Services may need to be initialized to put the specific video +hardware into the proper mode, etc. + +HBIOS Reference +=============== + +Invocation +---------- + +HBIOS functions are invoked by placing the required parameters in CPU +registers and executing an RST 08 instruction. Note that HBIOS does not +preserve register values that are unused. However, it will not modify the +Z80 alternate registers or IX/IY (these registers may be used within HBIOS, +but will be saved and restored internally). + +Normally, applications will not call HBIOS functions directly. It is +intended that the operating system makes all HBIOS function calls. +Applications that are considered system utilities may use HBIOS, but must +be careful not to modify the operating environment in any way that the +operating system does not expect. + +In general, the desired function is placed in the B register. Register C is +frequently used to specify a subfunction or a target device unit number. +Additional registers are used as defined by the specific function. Register +A should be used to return function result information. A=0 should +indicate success, other values are function specific. + +The character, disk, and video device functions all refer to target devices +using a logical device unit number that is passed in the C register. Keep +in mind that these unit numbers are assigned dynamically at HBIOS +initialization during the device discovery process. The assigned unit +numbers are displayed on the consoled at the conclusion of device +initialization. The unit assignments will never change after HBIOS +initialization. However, they can change at the next boot if there have +been hardware or BIOS customization changes. Code using HBIOS functions +should not assume fixed unit assignments. + +Some functions utilize pointers to memory buffers. Unless otherwise stated, +such buffers can be located anywhere in the Z80 CPU 64K address space. +However, performance sensitive buffers (primarily disk I/O buffers) will +require double-buffering if the caller’s buffer is in the lower 32K of CPU +address space. For optimal performance, such buffers should be placed in +the upper 32K of CPU address space. + +`\clearpage`{=latex} + +Character Input/Output (CIO) +---------------------------- + +Character input/output functions require that a Character Unit be specified +in the C register. This is the logical device unit number assigned during +the boot process that identifies all character I/O devices uniquely. A +special value of 0x80 can be used for Unit to refer to the current console +device. + +Character devices can usually be configured with line characteristics +such as speed, framing, etc. A word value (16 bit) is used to describe +the line characteristics as indicated below: + +_Bits_ | _Function_ +------ | ---------- +15-14 | Reserved (set to 0) +13 | RTS +12-8 | Baud Rate (see below) +7 | DTR +6 | XON/XOFF Flow Control +5-3 | Parity (???) +2 | Stop Bits (???) +1-0 | Data Bits (???) + +The 5-bit baud rate value (V) is encoded as V = 75 \* 2\^X \* 3\^Y. The +bits are defined as YXXXX. + +### Function 0x00 -- Character Input (CIOIN) + +| _Entry Parameters_ +| B: 0x00 +| C: Serial Device Unit Number + +| _Exit Results_ +| A: Status (0=OK, else error) +| E: Character Received + +Read a character from the device unit specified in register C and return +the character value in E. If no character(s) are available, this function +will wait indefinitely. + +### Function 0x01 -- Character Output (CIOOUT) + +| _Entry Parameters_ +| B: 0x01 +| C: Serial Device Unit Number +| E: Character to Send + +| _Exit Results_ +| A: Status (0=OK, else error) + +Send character value in register E to device specified in register C. If +device is not ready to send, function will wait indefinitely. + +### Function 0x02 -- Character Input Status (CIOIST) + +| _Entry Parameters_ +| B: 0x02 +| C: Serial Device Unit Number + +| _Exit Results_ +| A: Bytes Pending + +Return the number of characters available to read in the input buffer of +the unit specified. If the device has no input buffer, it is acceptable to +return simply 0 or 1 where 0 means there is no character available to read +and 1 means there is at least one character available to read. + +### Function 0x03 -- Character Output Status (CIOOST) + +| _Entry Parameters_ +| B: 0x03 +| C: Serial Device Unit Number + +| _Exit Results_ +| A: Output Buffer Bytes Available + +Return the space available in the output buffer expressed as a character +count. If a 16 byte output buffer contained 6 characters waiting to be +sent, this function would return 10, the number of positions available in +the output buffer. If the port has no output buffer, it is acceptable to +return simply 0 or 1 where 0 means the port is busy and 1 means the port is +ready to output a character. + +### Function 0x04 -- Character IO Initialization (CIOINIT) + +| _Entry Parameters_ +| B: 0x04 +| C: Serial Device Unit Number +| DE: Line Characteristics + +| _Exit Results_ +| A: Status (0=OK, else error) + +Setup line characteristics (baudrate, framing, etc.) of the specified unit. +Register pair DE specifies line characteristics. If DE contains -1 +(0xFFFF), then the device will be reinitialized with the last line +characteristics used. Result of function is returned in A with zero +indicating success. + +### Function 0x05 -- Character IO Query (CIOQUERY) + +| _Entry Parameters_ +| B: 0x05 +| C: Serial Device Unit Number + +| _Exit Results_ +| A: Status (0=OK, else error) +| DE: Line Characteristics + +Reports the line characteristics (baudrate, framing, etc.) of the specified +unit. Register pair DE contains the line characteristics upon return. + +### Function 0x06 -- Character IO Device (CIODEVICE) + +| _Entry Parameters_ +| B: 0x06 +| C: Serial Device Unit Number + +| _Exit Results_ +| A: Status (0=OK, else error) +| C: Serial Device Attributes +| D: Serial Device Type +| E: Serial Device Number + +Reports information about the character device unit specified. Register C +indicates the device attributes: 0=RS-232 and 1=Terminal. Register D +indicates the device type (driver) and register E indicates the physical +device number assigned by the driver. + +Each character device is handled by an appropriate driver (UART, ASCI, +etc.). The driver can be identified by the Device Type. The assigned Device +Types are listed below. + +_Id_ | _Device Type / Driver_ +---- | ---------------------- +0x00 | UART +0x10 | ASCI +0x20 | Terminal +0x30 | PropIO VGA +0x40 | ParPortProp VGA +0x50 | SIO +0x60 | ACIA +0x70 | PIO +0x80 | UF + +`\clearpage`{=latex} + +Disk Input/Output (DIO) +----------------------- + +Character input/output functions require that a character unit be specified +in the C register. This is the logical disk unit number assigned during +the boot process that identifies all disk i/o devices uniquely. + +A fixed set of media types are defined. The currently defined media types +are listed below. Each driver will support a subset of the defined media +types. + +**Media ID** | **Value** | **Format** +------------ | --------- | ---------- +MID\_NONE | 0 | No media installed +MID\_MDROM | 1 | ROM Drive +MID\_MDRAM | 2 | RAM Drive +MID\_RF | 3 | RAM Floppy (LBA) +MID\_HD | 4 | Hard Disk (LBA) +MID\_FD720 | 5 | 3.5" 720K Floppy +MID\_FD144 | 6 | 3.5" 1.44M Floppy +MID\_FD360 | 7 | 5.25" 360K Floppy +MID\_FD120 | 8 | 5.25" 1.2M Floppy +MID\_FD111 | 9 | 8" 1.11M Floppy + +### Function 0x10 -- Disk Status (DIOSTATUS) + +| _Entry Parameters_ +| B: 0x10 + +| _Exit Results_ +| A: Status (0=OK, else error) + +### Function 0x11 -- Disk Status (DIORESET) + +| _Entry Parameters_ +| B: 0x11 +| C: Disk Device Unit ID + +| _Exit Results_ +| A: Status (0=OK, else error) + +Reset the physical interface associated with the specified unit. Flag +all units associated with the interface for unit initialization at next +I/O call. Clear media identified unless locked. Reset result code of all +associated units of the physical interface. + +### Function 0x12 -- Disk Seek (DIOSEEK) + +| _Entry Parameters_ +| B: 0x12 +| C: Disk Device Unit ID +| D7: Address Type (0=CHS, 1=LBA) + +| if CHS: +| D6-0: Head +| E: Sector +| HL: Track + +| if LBA: +| DE:HL: Block Address + +| _Exit Results_ +| A: Status (0=OK, else error) + +Update target CHS or LBA for next I/O request on designated unit. Physical +seek is typically deferred until subsequent I/O operation. + +Bit 7 of D indicates whether the disk seek address is specified as +cylinder/head/sector (CHS) or Logical Block Address (LBA). If D:7=1, then +the remaining bits of of the 32 bit register set DE:HL specify a linear, +zero offset, block number. If D:7=0, then the remaining bits of D specify +the head, E specifies sector, and HL specifies track. + +Note that not all devices will accept both types of addresses. +Specifically, floppy disk devices must have CHS addresses. All other +devices will accept either CHS or LBA. The DIOGEOM function can be used to +determine if the device supports LBA addressing. + +### Function 0x13 -- Disk Read (DIOREAD) + +| _Entry Parameters_ +| B: 0x13 +| C: Disk Device Unit ID +| E: Block Count +| HL: Buffer Address + +| _Exit Results_ +| A: Status (0=OK, else error) +| E: Blocks Read + +Read Block Count sectors to buffer address starting at current target +sector. Current sector must be established by prior seek function; however, +multiple read/write/verify function calls can be made after a seek +function. Current sector is incremented after each sector successfully +read. On error, current sector is sector is sector where error occurred. +Blocks read indicates number of sectors successfully read. + +Caller must ensure: 1) buffer address is large enough to contain data for +all sectors requested, and 2) entire buffer area resides in upper 32K of +memory. + +### Function 0x14 -- Disk Write (DIOWRITE) + +| _Entry Parameters_ +| B: 0x14 +| C: Disk Device Unit ID +| E: Block Count +| HL: Buffer Address + +| _Exit Results_ +| A: Status (0=OK, else error) +| E: Blocks Written + +Write Block Count sectors to buffer address starting at current target +sector. Current sector must be established by prior seek function; however, +multiple read/write/verify function calls can be made after a seek +function. Current sector is incremented after each sector successfully +written. On error, current sector is sector is sector where error occurred. +Blocks written indicates number of sectors successfully written. + +Caller must ensure: 1) buffer address is large enough to contain data for +all sectors being written, and 2) entire buffer area resides in upper 32K +of memory. + +### Function 0x15 -- Disk Verify (DIOVERIFY) + +| _Entry Parameters_ +| B: 0x15 +| C: Disk Device Unit ID +| HL: Buffer Address +| E: Block Count + +| _Exit Results_ +| A: Status (0=OK, else error) +| E: Blocks Verified + +\*\*\*Not Implemented\*\*\* + +### Function 0x16 -- Disk Format (DIOFORMAT) + +| _Entry Parameters_ +| B: 0x16 +| C: Disk Device Unit ID +| D: Head +| E: Fill Byte +| HL: Cylinder + +| _Exit Results_ +| A: Status (0=OK, else error) + +\*\*\*Not Implemented\*\*\* + +### Function 0x17 -- Disk DEVICE (DIODEVICE) + +| _Entry Parameters_ +| B: 0x17 +| C: Disk Device Unit ID + +| _Exit Results_ +| A: Status (0=OK, else error) +| C: Attributes +| D: Device Type +| E: Device Number + +Reports information about the character device unit specified. Register D +indicates the device type (driver) and register E indicates the physical +device number assigned by the driver. + +Register C reports the following device attributes: + +Bit 7: 1=Floppy, 0=Hard Disk (or similar, e.g. CF, SD, RAM) + +| If Floppy: +| Bits 6-5: Form Factor (0=8", 1=5.25", 2=3.5", 3=Other) +| Bit 4: Sides (0=SS, 1=DS) +| Bits 3-2: Density (0=SD, 1=DD, 2=HD, 3=ED) +| Bits 1-0: Reserved + +| If Hard Disk: +| Bit 6: Removable\ +| Bits: 5-3: Type (0=Hard, 1=CF, 2=SD, 3=USB, +| 4=ROM, 5=RAM, 6=RAMF, 7=Reserved) +| Bits 2-0: Reserved + +Each disk device is handled by an appropriate driver (IDE, SD, +etc.) which is identified by a device type id from the table below. + +**Type ID** | **Disk Device Type** +----------- | -------------------- +0x00 | Memory Disk +0x10 | Floppy Disk +0x20 | RAM Floppy +0x30 | IDE Disk +0x40 | ATAPI Disk (not implemented) +0x50 | PPIDE Disk +0x60 | SD Card +0x70 | PropIO SD Card +0x80 | ParPortProp SD Card +0x90 | SIMH HDSK Disk + +### Function 0x18 -- Disk Media (DIOMEDIA) + +| _Entry Parameters_ +| B: 0x18 +| C: Disk Device Unit ID +| E0: Enable Media Discovery + +| _Exit Results_ +| A: Status (0=OK, else error) +| E: Media ID + +Report the media definition for media in specified unit. If bit 0 of E is +set, then perform media discovery or verification. If no media in device, +function will return an error status. + +### Function 0x19 -- Disk Define Media (DIODEFMED) + +| _Entry Parameters_ +| B: 0x19 +| C: Disk Device Unit ID +| E: Media ID + +| _Exit Results_ +| A: Status (0=OK, else error) + +\*\*\* Not implemented \*\*\* + +### Function 0x1A -- Disk Capacity (DIOCAPACITY) + +| _Entry Parameters_ +| B: 0x1A +| C: Disk Device Unit ID +| HL: Buffer Address + +| _Exit Results_ +| A: Status (0=OK, else error) +| DE:HL: Blocks on Device +| BC: Block Size + +Report current media capacity information. DE:HL is a 32 bit number +representing the total number of blocks on the device. BC contains the +block size. If media is unknown, an error will be returned. + +### Function 0x1B -- Disk Geometry (DIOGEOMETRY) + +| _Entry Parameters_ +| B: 0x1B +| C: Disk Device Unit ID + +| _Exit Results_ +| A: Status (0=OK, else error) +| HL: Cylinders +| D7: LBA Capability +| BC: Block Size + +Report current media geometry information. If media is unknown, return +error (no media). + +`\clearpage`{=latex} + +Real Time Clock (RTC) +--------------------- + +The Real Time Clock functions provide read/write access to the clock and +related Non-Volatile RAM. + +The time functions (RTCGTM and RTCSTM) require a 6 byte date/time buffer +of the following format. Each byte is BCD encoded. + +**Offset** | **Contents** +---------- | ------------ +0 | Year (00-99) +1 | Month (01-12) +2 | Date (01-31) +3 | Hours (00-24) +4 | Minutes (00-59) +5 | Seconds (00-59) + +### Function 0x20 -- RTC Get Time (RTCGETTIM) + +| _Entry Parameters_ +| B: 0x20 +| HL: Time Buffer Address + +| _Exit Results_ +| A: Status (0=OK, else error) + +Read the current value of the clock and store the date/time in the +buffer pointed to by HL. + +### Function 0x21 -- RTC Set Time (RTCSETTIM) + +| _Entry Parameters_ +| B: 0x21 +| HL: Time Buffer Address + +| _Exit Results_ +| A: Status (0=OK, else error) + +Set the current value of the clock based on the date/time in the buffer +pointed to by HL. + +### Function 0x22 -- RTC Get NVRAM Byte (RTCGETBYT) + +| _Entry Parameters_ +| B: 0x22 +| C: Index + +| _Exit Results_ +| A: Status (0=OK, else error) +| E: Value + +Read a single byte value from the Non-Volatile RAM at the index specified +by C. The value is returned in register E. + +### Function 0x23 -- RTC Set NVRAM Byte (RTCSETBYT) + +| _Entry Parameters_ +| B: 0x23 +| C: Index + +| _Exit Results_ +| A: Status (0=OK, else error) +| E: Value + +Write a single byte value into the Non-Volatile RAM at the index specified +by C. The value to be written is specified in E. + +### Function 0x24 -- RTC Get NVRAM Block (RTCGETBLK) + +| _Entry Parameters_ +| B: 0x24 +| HL: Buffer + +| _Exit Results_ +| A: Status (0=OK, else error) + +Read the entire contents of the Non-Volatile RAM into the buffer pointed +to by HL. HL must point to a location in the top 32K of CPU address space. + +### Function 0x25 -- RTC Set NVRAM Block (RTCSETBLK) + +| _Entry Parameters_ +| B: 0x25 +| HL: Buffer + +| _Exit Results_ +| A: Status (0=OK, else error) + +Write the entire contents of the Non-Volatile RAM from the buffer pointed +to by HL. HL must point to a location in the top 32K of CPU address space. + +`\clearpage`{=latex} + +Video Display Adapter (VDA) +--------------------------- + +The VDA functions are provided as a common interface to Video Display +Adapters. Not all VDAs will include keyboard hardware. In this case, the +keyboard functions should return a failure status. + +Depending on the capabilities of the hardware, the use of colors and +attributes may or may not be supported. If the hardware does not support +these capabilities, they will be ignored. + +Color byte values are constructed using typical RGBI +(Red/Green/Blue/Intensity) bits. The high four bits of the value determine +the background color and the low four bits determine the foreground color. +This results in 16 unique color values for both foreground and background. +The following table illustrates the color byte value construction: + +  | **Bit** | **Color** +---------- | ------- | --------- +Background | 7 | Intensity +  | 6 | Blue +  | 5 | Green +  | 4 | Red +Foreground | 3 | Intensity +  | 2 | Blue +  | 1 | Green +  | 0 | Red + +The following table illustrates the resultant color for each of the +possible 16 values for foreground or background: + +**Foreground** | **Background** | **Color** +------------------ | ------------------ | ---------------- +\_0 \_\_\_\_0000 | 0\_ 0000\_\_\_\_ | Black +\_1 \_\_\_\_0001 | 1\_ 0001\_\_\_\_ | Red +\_2 \_\_\_\_0010 | 2\_ 0010\_\_\_\_ | Green +\_3 \_\_\_\_0011 | 3\_ 0011\_\_\_\_ | Brown +\_4 \_\_\_\_0100 | 4\_ 0100\_\_\_\_ | Blue +\_5 \_\_\_\_0101 | 5\_ 0101\_\_\_\_ | Magenta +\_6 \_\_\_\_0110 | 6\_ 0110\_\_\_\_ | Cyan +\_7 \_\_\_\_0111 | 7\_ 0111\_\_\_\_ | White +\_8 \_\_\_\_1000 | 8\_ 1000\_\_\_\_ | Gray +\_9 \_\_\_\_1001 | 9\_ 1001\_\_\_\_ | Light Red +\_A \_\_\_\_1010 | A\_ 1010\_\_\_\_ | Light Green +\_B \_\_\_\_1011 | B\_ 1011\_\_\_\_ | Yellow +\_C \_\_\_\_1100 | C\_ 1100\_\_\_\_ | Light Blue +\_D \_\_\_\_1101 | D\_ 1101\_\_\_\_ | Light Magenta +\_E \_\_\_\_1110 | E\_ 1110\_\_\_\_ | Light Cyan +\_F \_\_\_\_1111 | F\_ 1111\_\_\_\_ | Bright White + +Attribute byte values are constructed using the following bit encoding: + +**Bit** | **Effect** +------- | ---------- +7 | n/a (0) +6 | n/a (0) +5 | n/a (0) +4 | n/a (0) +3 | n/a (0) +2 | Reverse +1 | Underline +0 | Blink + +The following codes are returned by a keyboard read to signify non-ASCII +keystrokes: + +**Value** | **Keystroke** | **Value** | **Keystroke** +--------- | ------------- | --------- | ------------- +0xE0 | F1 | 0xF0 | Insert +0xE1 | F2 | 0xF1 | Delete +0xE2 | F3 | 0xF2 | Home +0xE3 | F4 | 0xF3 | End +0xE4 | F5 | 0xF4 | PageUp +0xE5 | F6 | 0xF5 | PadeDown +0xE6 | F7 | 0xF6 | UpArrow +0xE7 | F8 | 0xF7 | DownArrow +0xE8 | F9 | 0xF8 | LeftArrow +0xE9 | F10 | 0xF9 | RightArrow +0xEA | F11 | 0xFA | Power +0xEB | F12 | 0xFB | Sleep +0xEC | SysReq | 0xFC | Wake +0xED | PrintScreen | 0xFD | Break +0xEE | Pause | 0xFE | +0xEF | App | 0xFF | + +### Function 0x40 -- Video Initialize (VDAINI) + +| _Entry Parameters_ +| B: 0x40 +| C: Video Device Unit ID +| E: Video Mode (device specific) +| HL: Font Bitmap Buffer Address (optional) + +| _Exit Results_ +| A: Status (0=OK, else error) + +Performs a full (re)initialization of the specified video device. The +screen is cleared and the keyboard buffer is flushed. If the specified +VDA supports multiple video modes, the requested mode can be specified +in E (set to 0 for default/not specified). Mode values are specific to +each VDA. + +HL may point to a location in memory with the character bitmap to be +loaded into the VDA video processor. The location MUST be in the top 32K +of the CPU memory space. HL must be set to zero if no character bitmap +is specified (the VDA video processor will utilize a default character +bitmap). + +### Function 0x41 -- Video Query (VDAQRY) + +| _Entry Parameters_ +| B: 0x41 +| C: Video Device Unit ID +| HL: Font Bitmap Buffer Address (optional) + +| _Exit Results_ +| A: Status (0=OK, else error) +| C: Video Mode +| D: Row Count +| E: Column Count +| HL: Font Bitmap Buffer Address (0 if N/A) + +Return information about the specified video device. C will be set to +the current video mode. DE will return the dimensions of the video +display as measured in rows and columns. Note that this is the **count** +of rows and columns, not the **last** row/column number. + +If HL is not zero, it must point to a suitably sized memory buffer in +the upper 32K of CPU address space that will be filled with the current +character bitmap data. It is critical that HL be set to zero if it does +not point to a proper buffer area or memory corruption will result. The +video device driver may not have the ability to provide character bitmap +data. In this case, on return, HL will be set to zero. + +### Function 0x42 -- Video Reset (VDARES) + +| _Entry Parameters_ +| B: 0x42 +| C: Video Device Unit ID + +| _Exit Results_ +| A: Status (0=OK, else error) + +Performs a soft reset of the Video Display Adapter. Should clear the +screen, home the cursor, restore active attribute and color to defaults. +Keyboard should be flushed. + +### Function 0x43 -- Video Device (VDADEV) + +| _Entry Parameters_ +| B: 0x43 +| C: Video Device Unit ID + +| _Exit Results_ +| A: Status (0=OK, else error) +| D=Device Type +| E=Device Number + +Reports information about the video device unit specified. + +Register D reports the video device type (see below). + +Register E reports the driver relative physical device number. + +The currently defined video device types are: + +VDA ID | Value | Device +---------- | ----- | ------ +VDA\_NONE | 0x00 | No VDA +VDA\_VDU | 0x10 | ECB VDU board +VDA\_CVDU | 0x20 | ECB Color VDU board +VDA\_7220 | 0x30 | ECB uPD7220 video display board +VDA\_N8 | 0x40 | TMS9918 video display built-in to N8 +VDA\_VGA | 0x50 | ECB VGA board + +### Function 0x44 -- Video Set Cursor Style (VDASCS) + +| _Entry Parameters_ +| B: 0x44 +| C: Video Device Unit ID +| D: Start/End Pixel Row +| E: Style + +| _Exit Results_ +| A: Status (0=OK, else error) + +If supported by the video hardware, adjust the format of the cursor such +that the cursor starts at the pixel specified in the top nibble of D and +end at the pixel specified in the bottom nibble of D. So, if D=\$08, a +block cursor would be used that starts at the top pixel of the character +cell and ends at the ninth pixel of the character cell. + +Register E is reserved to control the style of the cursor (blink, +visibility, etc.), but is not yet implemented. + +Adjustments to the cursor style may or may not be possible for any given +video hardware. + +### Function 0x45 -- Video Set Cursor Position (VDASCP) + +| _Entry Parameters_ +| B: 0x45 +| C: Video Device Unit ID +| D: Row (0 indexed) +| E: Column (0 indexed) + +| _Exit Results_ +| A: Status (0=OK, else error) + +Reposition the cursor to the specified row and column. Specifying a +row/column that exceeds the boundaries of the display results in +undefined behavior. Cursor coordinates are 0 based (0,0 is the upper +left corner of the display). + +### Function 0x46 -- Video Set Character Attribute (VDASAT) + +| _Entry Parameters_ +| B: 0x46 +| C: Video Device Unit ID +| E: Character Attribute Code + +| _Exit Results_ +| A: Status (0=OK, else error) + +Assign the specified character attribute code to be used for all +subsequent character writes/fills. This attribute is used to fill new +lines generated by scroll operations. Refer to the character attribute +for a list of the available attribute codes. Note that a given video +display may or may not support any/all attributes. + +### Function 0x47 -- Video Set Character Color (VDASCO) + +| _Entry Parameters_ +| B: 0x47 +| C: Video Device Unit ID +| E: Character Color Code + +| _Exit Results_ +| A: Status (0=OK, else error) + +Assign the specified color code to be used for all subsequent character +writes/fills. This color is also used to fill new lines generated by +scroll operations. Refer to color code table for a list of the available +color codes. Note that a given video display may or may not support +any/all colors. + +### Function 0x48 -- Video Set Write Character (VDAWRC) + +| _Entry Parameters_ +| B: 0x48 +| C: Video Device Unit ID +| E: Character + +| _Exit Results_ +| A: Status (0=OK, else error) + +Write the character specified in E. The character is written starting at +the current cursor position and the cursor is advanced. If the end of +the line is encountered, the cursor will be advanced to the start of the +next line. The display will **not** scroll if the end of the screen is +exceeded. + +### Function 0x49 -- Video Fill (VDAFIL) + +| _Entry Parameters_ +| B: 0x49 +| C: Video Device Unit ID +| E: Character +| HL: Count + +| _Exit Results_ +| A: Status (0=OK, else error) + +Write the character specified in E to the display the number of times +specified in HL. Characters are written starting at the current cursor +position and the cursor is advanced by the number of characters written. +If the end of the line is encountered, the characters will continue to +be written starting at the next line as needed. The display will **not** +scroll if the end of the screen is exceeded. + +### Function 0x4A -- Video Copy (VDACPY) + +| _Entry Parameters_ +| B: 0x4A +| C: Video Device Unit ID +| D: Source Row +| E: Source Column +| L: Count + +| _Exit Results_ +| A: Status (0=OK, else error) + +Copy count (L) bytes from the source row/column (DE) to current cursor +position. The cursor position is not updated. The maximum count is 255. +Copying to/from overlapping areas is not supported and will have an +undefined behavior. The display will **not** scroll if the end of the +screen is exceeded. Copying beyond the active screen buffer area is not +supported and results in undefined behavior. + +### Function 0x4B -- Video Scroll (VDASCR) + +| _Entry Parameters_ +| B: 0x4B +| C: Video Device Unit ID +| E: Scroll Distance (Line Count) + +| _Exit Results_ +| A: Status (0=OK, else error) + +Scroll the video display by the number of lines specified in E. If E +contains a negative number, then reverse scroll should be performed. + +### Function 0x4C -- Video Keyboard Status (VDAKST) + +| _Entry Parameters_ +| B: 0x4C +| C: Video Device Unit ID + +| _Exit Results_ +| A:Count of Key Codes in Keyboard Buffer + +Return a count of the number of key codes in the keyboard buffer. If it +is not possible to determine the actual number in the buffer, it is +acceptable to return 1 to indicate there are key codes available to read +and 0 if there are none available. + +### Function 0x4D -- Video Keyboard Flush (VDAKFL) + +| _Entry Parameters_ +| B: 0x4D +| C: Video Device Unit ID + +| _Exit Results_ +| A: Status (0=OK, else error) + +If a keyboard buffer is in use, it should be purged and all contents +discarded. + +### Function 0x4E -- Video Keyboard Read (VDAKRD) + +| _Entry Parameters_ +| B: 0x4E +| C: Video Device Unit ID + +| _Exit Results_ +| A: Status (0=OK, else error) +| C: Scancode +| D: Keystate +| E: Keycode + +Read next key code from keyboard. If a keyboard buffer is used, return +the next key code in the buffer. If no key codes are available, wait for +a keypress and return the keycode. + +The scancode value is the raw scancode from the keyboard for the +keypress. Scancodes are from scancode set 2 standard. + +The keystate is a bitmap representing the value of all modifier keys and +shift states as they existed at the time of the keystroke. The bitmap is +defined as: + +Bit | Keystate Indication +--- | -------------------------------- +7 | Key pressed was from the num pad +6 | Caps Lock was active +5 | Num Lock was active +4 | Scroll Lock was active +3 | Windows key was held down +2 | Alt key was held down +1 | Control key was held down +0 | Shift key was held down + +Keycodes are generally returned as appropriate ASCII values, if +possible. Special keys, like function keys, are returned as reserved +codes as described at the start of this section. + +`\clearpage`{=latex} + +System (SYS) +------------ + +### Function 0xF0 -- System Reset (SYSRESET) + +| _Entry Parameters_ +| B: 0xF0 + +| _Exit Results_ +| A: Status (0=OK, else error) + +Perform a soft reset of HBIOS. Releases all HBIOS memory allocated by +current OS. Does not reinitialize physical devices. + +### Function 0xF1 -- System Version (SYSVER) + +| _Entry Parameters_ +| B: 0xF1 +| C: Reserved (set to 0) + +| _Exit Results_ +| A: Status (0=OK, else error) +| DE: Version (Maj/Min/Upd/Pat) +| L: Platform ID + +This function will return the HBIOS version number. The version number +is returned in DE. High nibble of D is the major version, low nibble of +D is the minor version, high nibble of E is the patch number, and low +nibble of E is the build number. + +The hardware platform is identified in L: + +Id | Platform +-- | --------------------------------------------------- +1 | SBC V1 or V2 +2 | Zeta +3 | Zeta V2 +4 | N8 +5 | Mark IV +6 | UNA +7 | RC2014 w/ Z80 +8 | RC2014 w/ Z180 & banked memory module +9 | RC2014 w/ Z180 & linear memory module +10 | SCZ180 (SC126, SC130, SC131) +11 | Dyno + +### Function 0xF2 -- System Set Bank (SYSSETBNK) + +| _Entry Parameters_ +| B: 0xF2 +| C: Bank ID + +| _Exit Results_ +| A: Status (0=OK, else error) +| C: Previously Active Bank ID + +Activates the Bank ID specified in C and returns the previously active +Bank ID in C. The caller MUST be invoked from code located in the upper +32K and the stack **must** be in the upper 32K. + +### Function 0xF3 -- System Get Bank (SYSGETBNK) + +| _Entry Parameters_ +| B: 0xF3 + +| _Exit Results_ +| A: Status (0=OK, else error) +| C: Active Bank ID + +Returns the currently active Bank ID in C. + +### Function 0xF4 -- System Set Copy (SYSSETCPY) + +| _Entry Parameters_ +| B: 0xF4 +| D: Destination Bank ID +| E: Source Bank ID +| HL: Count of Bytes to Copy + +| _Exit Results_ +| A: Status (0=OK, else error) + +Prepare for a subsequent interbank memory copy (SYSBNKCPY) function by +setting the source bank, destination bank, and byte count for the copy. +The bank id's are not range checked and must be valid for the system in +use. + +No bytes are copied by this function. The SYSBNKCPY must be called to +actually perform the copy. The values setup by this function will remain +unchanged until another call is make to this function. So, after calling +SYSSETCPY, you may make multiple calls to SYSBNKCPY as long as you want +to continue to copy between the already established Source/Destination +Banks and the same size copy if being performed. + +### Function 0xF5 -- System Bank Copy (SYSBNKCPY) + +| _Entry Parameters_ +| B: 0xF5 +| DE: Destination Address +| HL: Source Address + +| _Exit Results_ +| A: Status (0=OK, else error) + +Copy memory between banks. The source bank, destination bank, and byte +count to copy MUST be established with a prior call to SYSSETCPY. +However, it is not necessary to call SYSSETCPY prior to subsequent calls +to SYSBNKCPY if the source/destination banks and copy length do not +change. + +WARNINGS: + +* This function is inherently dangerous and does not prevent you from +corrupting critical areas of memory. Use with **extreme** caution. + +* Overlapping source and destination memory ranges are not supported and +will result in undetermined behavior. + +* Copying of byte ranges that cross bank boundaries is undefined. + +### Function 0xF6 -- System Alloc (SYSALLOC) + +| _Entry Parameters_ +| B: 0xF6 +| HL: Size in Bytes + +| _Exit Results_ +| A: Status (0=OK, else error) +| HL: Address of Allocated Memory + +This function will attempt to allocate a block of memory of HL bytes +from the internal HBIOS heap. The HBIOS heap resides in the HBIOS bank +in the area of memory left unused by HBIOS. If the allocation is +successful, the address of the allocated memory block is returned in HL. +You will typically want to use the SYSBNKCPY function to read/write the +allocated memory. + +### Function 0xF7 -- System Free (SYSFREE) + +| _Entry Parameters_ +| B: 0xF7 +| HL: Address of Memory Block to Free + +| _Returned Values_ +| A: Status (0=OK, else error) + +\*\*\* This function is not yet implemented \*\*\* + +### Function 0xF8 -- System Get (SYSGET) + +| _Entry Parameters_ +| B: 0xF8 +| C: Subfunction (see below) + +| _Returned Values_ +| A: Status (0=OK, else error) + +This function will report various system information based on the +sub-function value. The following lists the subfunctions +available along with the registers/information returned. + +#### SYSGET Subfunction 0x00 -- Get Serial Device Unit Count (CIOCNT) + +| _Entry Parameters_ +| BC: 0xF800 + +| _Returned Values_ +| A: Status (0=OK, else error) +| E: Count of Serial Device Units + +#### SYSGET Subfunction 0x10 -- Get Disk Device Unit Count (DIOCNT) + +| _Entry Parameters_ +| BC: 0xF810 + +| _Returned Values_ +| A: Status (0=OK, else error) +| E: Count of Disk Device Units + +#### SYSGET Subfunction 0x40 -- Get Video Device Unit Count (VDACNT) + +| _Entry Parameters_ +| BC: 0xF840 + +| _Returned Values_ +| A: Status (0=OK, else error) +| E: Count of Video Device Units + +#### SYSGET Subfunction 0xD0 -- Get Timer Tick Count (TIMER) + +| _Entry Parameters_ +| BC: 0xF8D0 + +| _Returned Values_ +| A: Status (0=OK, else error) +| DE:HL: Current Timer Tick Count Value + +#### SYSGET Subfunction 0xD1 -- Get Seconds Count (SECONDS) + +| _Entry Parameters_ +| BC: 0xF8D1 + +| _Returned Values_ +| A: Status (0=OK, else error) +| DE:HL: Current Seconds Count Value +| C: Ticks within Second Value + +#### SYSGET Subfunction 0xE0 -- Get Boot Information (BOOTINFO) + +| _Entry Parameters_ +| BC: 0xF8E0 + +| _Returned Values_ +| A: Status (0=OK, else error) +| L: Boot Bank ID +| D: Boot Disk Device Unit ID +| E: Boot Disk Slice + +#### SYSGET Subfunction 0xF0 -- Get CPU Information (CPUINFO) + +| _Entry Parameters_ +| BC: 0xF8F0 + +| _Returned Values_ +| A: Status (0=OK, else error) +| H: Z80 CPU Variant +| L: CPU Speed in MHz +| DE: CPU Speed in KHz + +#### SYSGET Subfunction 0xF1 -- Get Memory Information (MEMINFO) + +| _Entry Parameters_ +| BC: 0xF8F1 + +| _Returned Values_ +| A: Status (0=OK, else error) +| D: Count of 32K ROM Banks +| E: Count of 32K RAM Banks + +#### SYSGET Subfunction 0xF2 -- Get Bank Information (BNKINFO) + +| _Entry Parameters_ +| BC: 0xF8F2 + +| _Returned Values_ +| A: Status (0=OK, else error) +| D: BIOS Bank ID +| E: User Bank ID + +### Function 0xF9 -- System Set (SYSSET) + +| _Entry Parameters_ +| B: 0xF9 +| C: Subfunction (see below) + +| _Returned Values_ +| A: Status (0=OK, else error) + +This function will set various system parameters based on the +sub-function value. The following lists the subfunctions +available along with the registers/information used as input. + +#### SYSSET Subfunction 0xD0 -- Set Timer Tick Count (TIMER) + +| _Entry Parameters_ +| BC: 0xF9D0 +| DE:HL: Timer Tick Count Value + +| _Returned Values_ +| A: Status (0=OK, else error) + + +#### SYSSET Subfunction 0xD1 -- Set Seconds Count (SECONDS) + +| _Entry Parameters_ +| BC: 0xF9D1 +| DE:HL: Seconds Count Value + +| _Returned Values_ +| A: Status (0=OK, else error) + + +#### SYSSET Subfunction 0xE0 -- Set Boot Information (BOOTINFO) + +| _Entry Parameters_ +| BC: 0xF9E0 +| L: Boot Bank ID +| D: Boot Disk Device Unit ID +| E: Boot Disk Slice + +| _Returned Values_ +| A: Status (0=OK, else error) + +### Function 0xFA -- System Peek (SYSPEEK) + +| _Entry Parameters_ +| B: 0xFA +| D: Bank ID +| HL: Memory Address + +| _Returned Values_ +| A: Status (0=OK, else error) +| E: Byte Value + +This function gets a single byte value at the specified bank/address. +The bank specified is not range checked. + +### Function 0xFB -- System Poke (SYSPOKE) + +| _Entry Parameters_ +| B: 0xFB +| D: Bank ID +| E: Value +| HL: Memory Address + +| _Returned Values_ +| A: Status (0=OK, else error) + +This function sets a single byte value at the specified bank/address. +The bank specified is not range checked. + +### Function 0xFC -- System Interrupt Management (SYSINT) + +| _Entry Parameters_ +| B: 0xFC +| C: Subfunction (see below) + +| _Returned Values_ +| A: Status (0=OK, else error) + +This function allows the caller to query information about the interrupt +configuration of the running system and allows adding or hooking interrupt +handlers dynamically. Register C is used to specify a subfunction. +Additional input and output registers may be used as defined by the +sub-function. + +Note that during interrupt processing, the lower 32K of CPU address space +will contain the RomWBW HBIOS code bank, not the lower 32K of application +TPA. As such, a dynamically installed interrupt handler does not have +access to the lower 32K of TPA and must be careful to avoid modifying the +contents of the lower 32K of memory. Invoking RomWBW HBIOS functions +within an interrupt handler is not supported. + +Interrupt handlers are different for IM1 or IM2. + +For IM1: + +> The new interrupt handler is responsible for chaining (JP) to the +previous vector if the interrupt is not handled. If the interrupt is +handled, the new handler may simply return (RET). When chaining to the +previous interrupt handler, ZF must be set if interrupt is handled and +ZF cleared if not handled. The interrupt management framework takes care +of saving and restoring AF, BC, DE, HL, and IY. Any other registers +modified must be saved and restored by the interrupt handler. + +For IM2: + +> The new interrupt handler may either replace or hook the previous +interrupt handler. To replace the previous interrupt handler, the new +handler just returns (RET) when done. To hook the previous handler, the +new handler can chain (JP) to the previous vector. Note that initially +all IM2 interrupt vectors are set to be handled as “BAD†meaning that the +interrupt is unexpected. In most cases, you do not want to chain to the +previous vector because it will cause the interrupt to display a “BAD +INT†system panic message. + +The interrupt framework will take care of issuing an EI and RETI +instruction. Do not put these instructions in your new handler. +Additionally, interrupt management framework takes care of saving and +restoring AF, BC, DE, HL, and IY. Any other registers modified must be +saved and restored by the interrupt handler. + +If the caller is transient, then the caller must remove the new interrupt +handler and restore the original one prior to termination. This is +accomplished by calling this function with the Interrupt Vector set to the +Previous Vector returned in the original call. + +The caller is responsible for disabling interrupts prior to making an +INTSET call and enabling them afterwards. The caller is responsible for +ensuring that a valid interrupt handler is installed prior to enabling any +hardware interrupts associated with the handler. Also, if the handler is +transient, the caller must disable the hardware interrupt(s) associated +with the handler prior to uninstalling it. + +#### SYSINT Subfunction 0x00 -- Interrupt Info (INTINF) + +| _Entry Parameters_ +| BC: 0xFC00 + +| _Returned Values_ +| A: Status (0=OK, else error) +| D: Interrupt Mode +| E: Size (# entries) of Interrupt Vector Table + +Return interrupt mode in D and size of interrupt vector table in E. For +IM1, the size of the table is the number of vectors chained together. +For IM2, the size of the table is the number of slots in the vector +table. + +#### SYSINT Subfunction 0x10) -- Get Interrupt (INTGET) + +| _Entry Parameters_ +| BC: 0xFC10 +| E: Interrupt Vector Table Index + +| _Returned Values_ +| A: Status (0=OK, else error) +| HL: Current Interrupt Vector Address + +On entry, register E must contain an index into the interrupt vector +table. On return, HL will contain the address of the current interrupt +vector at the specified index. + +#### SYSINT Subfunction 0x20) -- Set Interrupt (INTSET) + +| _Entry Parameters_ +| BC: 0xFC20 +| E: Interrupt Vector Table Index +| HL: Interrupt Address to be Assigned + +| _Returned Values_ +| A: Status (0=OK, else error) +| HL: Previous Interrupt Vector Address +| DE: Interrupt Routing Engine Address (IM2) + +On entry, register E must contain an index into the interrupt vector table +and register HL must contain the address of the new interrupt vector to +be inserted in the table at the index. On return, HL will contain the +previous address in the table at the index. diff --git a/Source/Doc/Build.cmd b/Source/Doc/Build.cmd deleted file mode 100644 index 5a595d65..00000000 --- a/Source/Doc/Build.cmd +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -setlocal - -setlocal & cd "ZCPR Manual" && call Build.cmd || exit /b 1 & endlocal -rem setlocal & cd "RomWBW User Guide" && call Build.cmd || exit /b 1 & endlocal -rem setlocal & cd "RomWBW System Guide" && call Build.cmd || exit /b 1 & endlocal \ No newline at end of file diff --git a/Source/Doc/Build.docx b/Source/Doc/Build.docx deleted file mode 100644 index 31c11c39..00000000 Binary files a/Source/Doc/Build.docx and /dev/null differ diff --git a/Source/Doc/Clean.cmd b/Source/Doc/Clean.cmd deleted file mode 100644 index 1c04ba1b..00000000 --- a/Source/Doc/Clean.cmd +++ /dev/null @@ -1,6 +0,0 @@ -@echo off -setlocal - -setlocal & cd ZCPR Manual && call Clean.cmd & endlocal -setlocal & cd RomWBW User Guide && call Clean.cmd & endlocal -setlocal & cd RomWBW System Guide && call Clean.cmd & endlocal diff --git a/Source/Doc/Common.inc b/Source/Doc/Common.inc new file mode 100644 index 00000000..542c53ff --- /dev/null +++ b/Source/Doc/Common.inc @@ -0,0 +1,7 @@ +!def(ver)(3.1 Pre-release) +!def(date)(!mdate) +!def(product)(RomWBW) +!def(author)(Wayne Warthen) +!def(authmail)(wwarthen@gmail.com) +!def(orgname)(RetroBrew Computers Group) +!def(orgurl)(www.retrobrewcomputers.org) \ No newline at end of file diff --git a/Source/Doc/GettingStarted.md b/Source/Doc/GettingStarted.md new file mode 100644 index 00000000..345c768b --- /dev/null +++ b/Source/Doc/GettingStarted.md @@ -0,0 +1,1213 @@ +!include(Common.inc) +!def(document)(Getting Started) +--- +title: | + | !product + | + | !document +author: !author (mailto:!authmail) +date: !date +institution: !orgname +documentclass: article +classoption: + - oneside +toc: true +papersize: letter +geometry: + - top=1.5in + - bottom=1.5in + - left=1.5in + - right=1.5in +# - showframe +linestretch: 1.25 +colorlinks: true +fontfamily: helvet +fontsize: 12pt +header-includes: + - | + ```{=latex} + \renewcommand*{\familydefault}{\sfdefault} + \setstretch{1.25} % for TOC + ``` +--- + +`\clearpage % new page after TOC`{=latex} + +# RomWBW + +## Z80/Z180 System Software + +| Version !ver +| !date + +!author() [!authmail](mailto:!authmail) + +### Download + +* [RomWBW Distribution Package](https://github.com/wwarthen/RomWBW/releases) + +### Related Pages + +* [RomWBW Architecture Document](https://www.retrobrewcomputers.org/lib/exe/fetch.php?media=software:firmwareos:romwbw:romwbw_architecture.pdf) +* [RomWBW Applications](https://www.retrobrewcomputers.org/doku.php?id=software:firmwareos:romwbw:apps) +* [RomWBW Errata](https://www.retrobrewcomputers.org/doku.php?id=software:firmwareos:romwbw:errata) + +# Overview + +RomWBW provides a complete software system for a wide variety of hobbyist +Z80/Z180 CPU-based systems produced by these developer communities: + +* [RetroBrew Computers](https://www.retrobrewcomputers.org) +* [RC2014](https://rc2014.co.uk) +* [retro-comp](https://groups.google.com/forum/#!forum/retro-comp) + +General features include: + +* Banked memory services for several banking designs +* Disk drivers for RAM, ROM, Floppy, IDE, CF, and SD +* Serial drivers including UART (16550-like), ASCI, ACIA, SIO +* Video drivers including TMS9918, SY6545, MOS8563, HD6445 +* Real time clock drivers including DS1322, BQ4845 +* Multiple OS support including CP/M 2.2, ZSDOS, CP/M 3, ZPM3 +* Built-in VT-100 terminal emulation support + +RomWBW is distributed as both source code and pre-built ROM and disk +images. Some of the provided software can be launched directly from the +ROM firmware itself: + +* System Monitor +* Operating Systems (CP/M 2.2, ZSDOS) +* ROM BASIC (Nascom BASIC and Tasty BASIC) +* ROM Forth + +A dynamic disk drive letter assignment mechanism allows mapping +operating system drive letters to any available disk media. +Additionally, mass media devices (IDE Disk, CF Card, SD Card) support +the use of multiple slices (up to 256 per device). Each slice contains +a complete CP/M filesystem and can be mapped independently to any +drive letter. This overcomes the inherent size limitations in legacy +OSes and allows up to 2GB of accessible storage on a single device. + +The pre-built ROM firmware images are generally optimal for most +users. However, it is also very easy to modify and build custom ROM +images that fully tailor the firmware to your specific preferences. +All tools required to build custom ROM firmware are included -- no +need to install assemblers, etc. Any modern computer running Windows, +Linux, or MacOS can be used. + +Multiple disk images are provided in the distribution. Most disk +images contain a complete, bootable, ready-to-run implementation of a +specific operating system. A "combo" disk image contains multiple +slices, each with a full operating system implementation. If you use +this disk image, you can easily pick whichever operating system you +want to boot without changing media. + +# Installation + +The latest RomWBW distribution downloads are maintained on GitHub in the +[RomWBW Repository](https://github.com/wwarthen/RomWBW). The fully-built +distributions are found on the +[releases page](https://github.com/wwarthen/RomWBW/releases) of the +repository. On this page, you will probably see both pre-releases as +well as normal releases. Unless you have a specific reason, I suggest +you stick to the most recent normal release (not pre-release). Expand +the "Assets" drop-down for the release you want to download, then +select the asset named RomWBW-vX.X.X-Package.zip. The Package asset +includes all pre-built ROM and Disk images as well as full source +code. The other assets are Source Code only and do not have the +pre-built ROM or disk images. + +The pre-built ROM images will automatically detect and support a +reasonable range of devices including serial ports, video adapters, +on-board disk interfaces, and PropIO/ParPortProp boards without +building a custom ROM. The distribution is a .zip archive. After +downloading it to a working directory on your modern computer +(Windows/Linux/Mac) use any zip tool to extract the contents of the +archive. + +In general, you will just program your system's ROM chip with the +appropriate ROM image from the RomWBW distribution. Depending on how +you got your system, you may have already been provided with a +pre-programmed ROM chip. If so, use that initially. Otherwise, you +will need to use a ROM programmer to initially program your ROM chip. +Please refer to the documentation that came with your ROM programmer +for more information. Once you have a running RomWBW system, you can +generally update your ROM to a newer version in-situ with an included +ROM Flashing tool (Will Sowerbutts' FLASH application) as described in +the Upgrading section below. + +Looking at the extracted distribution archive, You will see that the +distribution is broken up into a few sub-directories. The Binary +directory contains the pre-built ROM and disk images. The ROM image +files all end in ".rom". Based on the table below, **carefully** pick +the appropriate ROM image for your hardware. + +| Platform | ROM Image File | Baud | Description | +| -------------- | ------------------------ | ----------: | ------------------------------------------------ | +| SBC | SBC_std.rom | 38400 | RetroBrew SBC v1 or v2 ECB Z80 | +| Zeta V1 | ZETA_std.rom | 38400 | RetroBrew Zeta V1 Z80, ParPortProp (optional) | +| Zeta V2 | ZETA2_std.rom | 38400 | RetroBrew Zeta V2 Z80, ParPortProp (optional) | +| N8 | N8_std.rom | 38400 | RetroBrew N8 Z180, date code >= 2312 | +| Mark IV | MK4_std.rom | 38400 | RetroBrew Mark IV ECB Z180 | +| RC Z80 | RCZ80_std.rom | 115200 | RC2014 w/ Z80 CPU, requires 512K RAM/ROM module | +| RC Z180\* | RCZ180_ext.rom | 115200 | RC2014 w/ Z180 CPU & 512K banked RAM/ROM module | +| RC Z180\* | RCZ180_nat.rom | 115200 | RC2014 w/ Z180 CPU & 512K native RAM/ROM module | +| Easy Z80 | EZZ80_std.rom | 115200 | Sergey Kiselev's Easy Z80 | +| SC126 | SCZ180_126.rom | 115200 | Stephen Cousin's SC126 Z180 | +| SC130 | SCZ180_130.rom | 115200 | Stephen Cousin's SC130 Z180 | +| SC131 | SCZ180_131.rom | 115200 | Stephen Cousin's SC131 Z180 | +| Dyno | DYNO_std.rom | 38400 | Steve Garcia's Z180 Dyno Computer | + +\*The RC2014 Z180 requires a separate RAM/ROM memory module. There are +two types of these modules and you must pick the ROM for your type of +memory module. The "ext" ROM supports Spencer's official 512K RAM/ROM +banked memory module. The "nat" ROM supports any of the third-party +Z180 native memory modules. + +RomWBW will automatically attempt to detect and support typical add-on +components for each of the systems supported. More information on the +required system configuration and optional supported components for +each ROM is found in the file called "RomList.txt" in the Binary +directory. All pre-built ROM images are simple 512KB binary images. If +your system utilizes a larger ROM chip, you can just program the +image into the first 512KB of the ROM. + +Connect a serial terminal or computer with terminal emulation software +to the primary serial port of your CPU board. You may need to refer +to your hardware provider's documentation for details. A null-modem +connection may be required. Set the baud rate as indicated in the +table above. Set the line characteristics to 8 data bits, 1 stop bit, +no parity, and no flow control. If possible, select VT-100 terminal +emulation. + +Upon power-up, your terminal should display a sign-on banner within 2 +seconds followed by hardware inventory and discovery information. When +hardware initialization is completed, a boot loader prompt allows you to +choose a ROM-based operating system, system monitor, application, or boot +from a disk device. + +Initially, you should try the ROM boot options. By selecting either +CP/M 2.2 or Z-System, the selected operating system will be loaded +from ROM and you will see the a `B>` disk prompt. In this scenario, A: +will be an empty RAM disk and B: will refer to your ROM disk +containing some common applications. This provides a simple +environment for learning to use your system. Be aware that files saved +to the RAM disk (A:) will disappear at the next power on (RAM is +generally not persistent). Also note that attempts to save files to +the ROM disk (B:) will fail because ROM is not writable. + +# General Usage + +Each of the operating systems and ROM applications included with +RomWBW are sophisticated tools in their own right. It is not +reasonable to document their usage here. However, you will find +complete manuals in PDF format in the Doc directory of the +distribution. The intention of this section is to document the RomWBW +specific enhancements to these operating systems. + +## Inbuilt ROM Applications + +In addition to CP/M 2.2 and Z-System, there are several ROM +applications that can be launched directly from ROM. These +applications are not hosted by an operating system and so they are +unable to save files to disk devices. + +The following ROM applications are available at the boot loader prompt: + +| Application | | +| ----------- | -------------------------------------------------------------- | +| Monitor | Z80 system debug monitor w/ Intel Hex loader | +| Forth | Brad Rodriguez's ANSI compatible Forth language | +| Basic | Nascom 8K BASIC language | +| Tasty BASIC | Dimitri Theuling's Tiny BASIC implementation | +| Play | A simple video game (requires ANSI terminal emulation) | + +In general, the command to exit these applications and restart the +system is `BYE`. The exceptions are the Monitor which uses `B` and +Play which uses `Q`. + +Space is available in the ROM image for the inclusion of other +software. Any inbuilt application can be set up to launch +automatically at startup. + +## Devices and Units + +In order to support a wide variety of hardware, RomWBW HBIOS uses a +modular approach to implementing device drivers and presenting devices +to the operating system. In general, all devices are classified as +one of the following: + +* Disk (Hard Disk, CF Card, SD Card, RAM/ROM Disk, etc.) +* Character (Serial Ports, Parallel Ports, etc.) +* Video (Video Display/Keyboard Interfaces) +* RTC/NVRAM (Real Time Clock, Non-volatile RAM) + +HBIOS uses the concept of unit numbers to present a complex set of +hardware devices to the operating system. As an example, a typical +system might have a ROM Disk, RAM Disk, Floppy Drives, and Disk +Drives. All of these are considered Disk devices and are presented +to the operating system as generic block devices. This means that +the operating system does not need to understand the difference between +a floppy drive and a ROM disk. + +As RomWBW boots, it assigns a unit number to each device. This unit +number is used by the operating system to refer to the device. It is, +therefore, important to know the unit number assigned to each device. +This information is displayed in the unit summary table at startup. +Here is an example: + +``` +Unit Device Type Capacity/Mode +---------- ---------- ---------------- -------------------- +Char 0 UART0: RS-232 38400,8,N,1 +Char 1 UART1: RS-232 38400,8,N,1 +Disk 0 MD1: RAM Disk 384KB,LBA +Disk 1 MD0: ROM Disk 384KB,LBA +Disk 2 FD0: Floppy Disk 3.5",DS/HD,CHS +Disk 3 FD1: Floppy Disk 3.5",DS/HD,CHS +Disk 4 IDE0: CompactFlash 3815MB,LBA +Disk 5 IDE1: Hard Disk -- +Disk 6 PRPSD0: SD Card 1886MB,LBA +Video 0 CVDU0: CRT Text,80x25 +``` + +In this example, you can see that the system has a total of 7 Disk +Units numbered 0-6. There are also 2 Character Units and 1 Video +Unit. The table shows the unit numbers assigned to each of the +devices. Notice how the unit numbers are assigned sequentially +regardless of the specific device. + +There may or may not be media in the disk devices listed. For example, +the floppy disk devices (Disk Units 2 & 3) may not have a floppy in +the drive. Also note that Disk Unit 4 shows a disk capacity, but +Disk Unit 5 does not. This is because the PPIDE interface of the +system supports up to two drives, but there is only one actual drive +attached. A unit number is assigned to all possible devices +regardless of whether they have actual media installed at boot time. + +Note that Character Unit 0 is **always** the initial system console +by definition. + +If your system has an RTC/NVRAM device, it will not be listed in the +unit summary table. Since only a single RTC/NVRAM device can exist in +one system, unit numbers are not required nor used for this type of +device. + +## Drive Letter Assignment + +In legacy CP/M-type operating systems, drive letters were generally +mapped to disk drives in a completely fixed way. For example, drive A: +would **always** refer to the first floppy drive. Since RomWBW +supports a wide variety of hardware configurations, it implements a +much more flexible drive letter assignment mechanism so that any drive +letter can be assigned to any disk device. + +At boot, you will notice that RomWBW automatically assigns drive +letters to the available disk devices. These assignments are +displayed during the startup of the selected operating system. +Additionally, you can review the current drive assignments at any +time using the `ASSIGN` command. CP/M 3 and ZPM3 do not automatically +display the assignments at startup, but you can use `ASSIGN` do +display them. + +The drive letter assignments **do not** change during an OS session +unless you use the `ASSIGN` command yourself to do it. Additionally, +the assignments at boot will stay the same on each boot as long as you +do not make changes to your hardware configuration. Note that the +assignments **are** dependent on the media currently inserted in hard +disk drives. So, notice that if you insert or remove an SD Card or CF +Card, the drive assignments will change. Since drive letter +assignments can change, you must be careful when doing destructive +things like using `CLRDIR` to make sure the drive letter you use is +referring to the desired media. + +When performing a ROM boot of an operating system, note that A: will +be your RAM disk and B: will be your ROM disk. When performing a disk +boot, the disk you are booting from will be assigned to A: and the +rest of the drive letters will be offset to accommodate this. This is +done because most legacy operating systems expect that A: will be the +boot drive. + +## Slices + +The vintage operating systems included with RomWBW were produced at a +time when mass storage devices were quite small. CP/M 2.2 could only +handle filesystems up to 8MB. In order to achieve compatibility across +all of the operating systems supported by RomWBW, the hard disk +filesystem format used is 8MB. This ensures any filesystem will be +accessible to any of the operating systems. + +Since storage devices today are quite large, RomWBW implements a +mechanism called slicing to allow up to 256 8MB filesystems on a +single large storage device. This allows up to 2GB of usable space on +a single media. You can think of slices as a way to refer to any of +the first 256 8MB chunks of space on a single media. + +Of course, the problem is that CP/M-like operating systems have only +16 drive letters (A:-P:) available. Under the covers, RomWBW allows +you to use any drive letter to refer to any slice of any media. The +`ASSIGN` command is allows you to view or change the drive letter +mappings at any time. At startup, the operating system will +automatically allocate a reasonable number of drive letters to the +available storage devices. The allocation will depend on the number of +large storage devices available at boot. For example, if you have +only one hard disk type media, you will see that 8 drive letters are +assigned to the first 8 slices of that media. If you have two large +storage devices, you will see that each device is allocated four drive +letters. + +Referring to slices within a storage device is done by appending a : +** where ** is the device relative slice number from 0-255. For +example, if you have an IDE device, it will show up as IDE0: in the +boot messages meaning the first IDE device. To refer to the fourth +slice of IDE0, you would type "IDE0:3". Here are some examples: + +| | | +| -------- | ---------------------------- | +| `IDE0:0` | First slice of disk in IDE0 | +| `IDE0:` | First slice of disk in IDE0 | +| `IDE0:3` | Fourth slice of disk in IDE0 | + +So, if I wanted to use drive letter L: to refer to the fourth slice +of IDE0, I could use the command `ASSIGN L:=IDE0:3`. There are a +couple of rules to be aware of when assigning drive letters. First, +you may only refer to a specific device/slice with one drive letter. +Said another way, you cannot have multiple drive letters referring +to a single device/slice at the same time. Second, there must always +be a drive assigned to A:. Any attempt to violate these rules will +be blocked by the `ASSIGN` command. + +Unlike MS-DOS partitions, slices are not allocated -- there is no +partitioning for slices. Think of every hard disk type device as +having a pre-allocated set of 256 8MB slices at the start of the +media. You can refer to any of them simply by assigning a drive +letter. RomWBW will not check to see if there is anything else on the +hard disk in the slice you are referring to, nor will it verify that +the hard disk media is large enough to have a slice at the location +you refer to. If you attempt to write past the end of your media, you +will get an I/O error displayed, so you will know if you make a +mistake. There is no tracking of your use of slices -- you will need +to keep track of your use of slices yourself. + +Nothing automatically initializes a slice as a file system. You must +do that yourself using `CLRDIR`. Since `CLRDIR` works on drive +letters, make absolutely sure you know what media and slice are +assigned to that drive letter before using `CLRDIR`. + +While it is probably obvious, you cannot use slices on any media less +than 8MB in size. Specifically, you cannot slice RAM disks, ROM +disks, floppy disks, etc. + +# RomWBW Custom Applications + +The operation of the RomWBW hosted operating systems is enhanced +through several custom applications. These applications are +functional on all of the OS variants included with RomWBW. + +The following custom applications are found on the ROM disk and are, +therefore, globally available. + +| Application | Description | +| ----------- | ---------------------------------------------------------------------------------------------------- | +| ASSIGN | Add, change, and delete drive letter assignments. Use ASSIGN /? for usage instructions. | +| SYSCOPY | Copy system image to a device to make it bootable. Use SYSCOPY with no parms for usage instructions. | +| MODE | Reconfigures serial ports dynamically. | +| FDU | Format and test floppy disks. Menu driven interface. | +| FORMAT | Will someday be a command line tool to format floppy disks. Currently does nothing! | +| XM | XModem file transfer program adapted to hardware. Automatically uses primary serial port on system. | +| FLASH | Will Sowerbutts' in-situ ROM programming utility. | +| FDISK80 | John Coffman's Z80 hard disk partitioning tool. See documentation in Doc directory. | +| TALK | Direct console I/O to a specified character device. | +| RTC | Manage and test the Real Time Clock hardware. | +| TIMER | Display value of running periodic system timer. | +| INTTEST | Test interrupt vector hooking. | + +Some custom applications do not fit on the ROM disk. They are found on the +disk image files or the individual files can be found in the Binary\\Apps +directory of the distribution. + +| Application | Description | +| ----------- | -------------------------------------------------------------- | +| TUNE | Play .PT2, .PT3, .MYM audio files. | +| FAT | Access MS-DOS FAT filesystems from RomWBW (based on FatFs). | + +Additional documentation on all of these applications can be found in +"RomWBW Applications.pdf" in the Doc directory of the distribution. + +# Using Disks + +## ROM & RAM Disks + +RomWBW utilizes a portion of the ROM and RAM memory in your system to +implement small memory-based disks. + +The RAM disk provides a small CP/M filesystem that you can use for the +temporary storage of files. Unless your system has a battery backed +mechanism for persisting your RAM contents, the RAM disk contents will +be lost at each power-off. However, the RAM disk is an excellent +choice for storing temporary files because it is very fast. + +Like the RAM disk, the ROM disk also provides a small CP/M +filesystem, but it's contents are static -- they are part of the +ROM. As such, you cannot save files to the ROM disk. Any attempt to +do this will result in a disk I/O error. The contents of the ROM +disk have been chosen to provide a core set of tools and +applications that are helpful for either CP/M 2.2 or ZSDOS. Since +ZSDOS is CP/M 2.2 compatible, this works fairly well. However, you +will find some files on the ROM disk that will work with ZSDOS, but +will not work on CP/M 2.2. For example, `LDDS`, which loads the +ZSDOS date/time stamper will only run on ZSDOS. + +## Disk Devices + +While the RAM/ROM disks provide a functional system, they are not +useful in the long term because you cannot save data across power +cycles. They are also constrained by limited space. + +The systems supported by RomWBW all have the ability to use persistent +disk media. A wide variety of disk devices are supported including +floppy drives, hard disks, CF Cards, and SD Cards. Some systems have +disk interfaces built-in, while others will require add-in cards. You +will need to refer to the documentation for your system for your +specific options. + +In the RomWBW boot messages, you will see hardware discovery messages. +If you have a disk drive interface, you should see messages listing +device types like FD:, IDE:, PPIDE:, SD:. Additionally, you will see +messages indicating the media that has been found on the interfaces. +As an example, here are the messages you might see if you have an IDE +interface in your system with a single CF Card inserted in the +primary side of the interface: + +``` +IDE: IO=0x80 MODE=MK4 +IDE0: 8-BIT LBA BLOCKS=0x00773800 SIZE=3815MB +IDE1: NO MEDIA +``` + +The messages you see will vary depending on your hardware and the +media you have installed. But, they will all have the same general +format as the example above. + +Once your your system has working disk devices, you can boot an +operating system and the operating system will have access to the +media. At the boot loader prompt, select either either CP/M 2.2 or +Z-System to boot from ROM. As the operating system starts up, you +should see a list of drive letters assigned to the disk media you have +installed. Here is an example of this: + +``` +Configuring Drives... + + A:=MD1:0 + B:=MD0:0 + C:=IDE0:0 + D:=IDE0:1 +``` + +You will probably see more drive letters than this. The drive letter +assignment process is described above in the Drive Letter Assignment +section. Be aware that RomWBW will only assign drive letters to disk +interfaces that actually have media in them. If you do not see drive +letters assigned as expected, refer to the prior system boot messages +to ensure media has been detected in the interface. Actually, there +is one exception to this rule: floppy drives will be assigned a drive +letter regardless of whether there is any media inserted at boot. + +Notice how each drive letter refers back to a specific disk hardware +interface like IDE0. This is important as it is telling you what each +drive letter refers to. Also notice that mass storage disks (like IDE) +will normally have multiple drive letters assigned. The extra drive +letters refer to additional "slices" on the disk. The concept of slices +is described above in the Slices section. + +Once you are seeing drive letters referring to your disk media, you +can follow the instructions below to begin using the disk media with +the operating system. Your disk media **must** be initialized prior +to being used. There are two ways to initialize your media for use. + +One option is to initialize the media in-place using your RomWBW +system. This process is described below under Disk Initialization. In +this scenario, you will need to subsequently copy any files you want +to use onto the newly initialized disk (see Transferring Files). + +Alternatively, you can use your modern Windows, Linux, or Mac computer +to copy a disk image onto the disk media. RomWBW comes with a variety +of disk images that are ready to use and have a much more complete +set of files than you will find on the ROM disk. This process is +covered below under Disk Images. + +## Disk Initialization + +To use a disk device, you will need to initialize the directory of +the filesystem. On RomWBW, the initialization is done using the +CLRDIR application. For example if your C: drive has been assigned to +a storage device, you would use `CLRDIR C:` to initialize C: and +prepare it hold files. Note that CLRDIR will prompt you for +confirmation and you must respond with a **capital** 'Y' to confirm. +Once `CLDIR` has completed, you can copy files onto the drive, for +example `COPY *.* C:`. Be very careful to pay attention to your +drive letter assignments prior to running `CLRDIR` to avoid +accidentally wiping out a filesystem that has data on it. + +Running `CLRDIR` on a disk device is roughly equivalent to running +FORMAT on MS-DOS. Note that unlike MS-DOS you do **not** partition +your mass storage device. CP/M knows nothing about disk partitions. +You may notice a partitioning application on your ROM disk (FDISK80), +but this is strictly for an advanced technique of adding an MS-DOS +FAT filesystem to your media in addition to the CP/M area. Do not +use FDISK80 unless you are specifically attempting to add an MS-DOS +FAT filesystem to your media. + +If you are using a floppy drive, you will need to physically format +your floppy disk prior to use. This is only required for floppy +disks, not hard disk, CF Cards, or SD Cards, etc. To format a floppy +drive, you can use the interactive application `FDU`. FDU is not +terribly user friendly, but is generally documented in the file +"FDU.txt" found in the Doc directory of the distribution. It is not +necessary to run `CLRDIR` on a floppy disk after physically +formatting it -- the directory is cleared as part of the formatting. + +Once you have initialized a disk device and copied your desired files +onto it, you may want to make the disk bootable. On CP/M filesystems, +you must perform one additional step to make a disk bootable. +Specifically, you need to place a copy of the operating system on the +system tracks of the disk. This is done using the `SYSCOPY` command. +Let's say you have prepared drive C: by initializing it with `CLRDIR` +and copied some files onto it. You can now make C: bootable by +running the following command: + +`B>SYSCOPY C:=B:ZSYS.SYS` + +This command means: copy the Z-System operating system onto the +system tracks of drive C:. In this example, it is assumed that you +have booted from ROM, so B: is the ROM disk drive. Additionally, this +example assumes you want the Z-System operating system to be booted +from C:. If you want CP/M 2.2 instead, you would replace `B:ZSYS.SYS` +with `B:CPM.SYS`. Here is a full example of this process. + +``` +B>SYSCOPY C:=B:ZSYS.SYS + +SYSCOPY v2.0 for RomWBW CP/M, 17-Feb-2020 (CP/M 2 Mode) +Copyright 2020, Wayne Warthen, GNU GPL v3 + +Transfer system image from B:ZSYS.SYS to C: (Y/N)? Y +Reading image... Writing image... Done +``` + +Once this process succeeds, you will be able to boot directly to the +disk from the boot loader prompt. See the instructions in Booting +Disks for details on this. + +## Disk Images + +As mentioned previously, RomWBW includes a variety of disk images +that contain a full set of applications for the operating systems +supported. It is generally easier to use these disk images instead +of copying all the files over using XModem. You use your modern +computer (Windows, Linux, MacOS) to place the disk image onto the +disk media, then just move the media over to your system. In this +scenario you **do not** run `CLRDIR` or `SYSCOPY` on the drive(s). +The directory is prepared and the disk is already bootable, if it is +an operating system boot disk image. + +To copy the disk image files onto your actual media (floppy disk, CF +Card, SD Card, etc.), you need to use an image writing utility on +your modern computer. Your modern computer will need to have an +appropriate interface or slot that accepts the media. To actually +copy the image, you can use the `dd` command on Linux or MacOS. On +Windows, in the "Tools" directory of the distribution there are two +tools you can use. For floppy media, you can use RawWriteWin and for +hard disk media, you can use Win32DiskImager. In all cases, the image +file should be written to the media starting at the very first block +or sector of the media. This will **destroy** any other data on the +media. + +The disk image files are found in the Binary directory of the +distribution. Floppy disk images are prefixed with "fd_" and hard +disk images are prefixed with "hd_". The floppy images are +specifically for 1.44M floppy media only. Each disk image has the +complete set of normal applications and tools distributed with the +associated operating system or application suite. + +The following table shows the disk image files available. Note that +the images in the "Hard" column are fine for use on CF Cards, SD +Cards, as well as real spinning hard disks. + +| Floppy | Hard | Description | +| --------------- | --------------- | -------------------------------------- | +| fd_cpm22.img | hd_cpm22.img | DRI CP/M 2.2 boot disk | +| fd_zsdos.img | hd_zsdos.img | ZSDOS 1.1 boot disk | +| fd_nzcom.img | hd_nzcom.img | NZCOM boot disk | +| fd_cpm3 | hd_cpm3.img | DRI CP/M 3 boot disk | +| fd_zpm3 | hd_zpm3.img | ZPM3 boot disk | +| fd_ws4 | hd_ws4.img | WordStar v4 application disk | + +In addition to the disk images above, there is also a special hard +disk image called hd_combo.img. This image contains all of the images +above, but in a single image with 6 slices. At the boot loader +prompt, you can choose a disk with the combo image, then select the +specific slice you want. This allows a single disk to have all of the +possible operating system options. + +This is the layout of the hd_combo disk image: + +| Slice | Description | +| ------- | ---------------------------------------------------------------- | +| Slice 0 | DRI CP/M 2.2 boot disk | +| Slice 1 | ZSDOS 1.1 boot disk | +| Slice 2 | NZCOM boot disk | +| Slice 3 | DRI CP/M 3 boot disk | +| Slice 4 | ZPM3 boot disk | +| Slice 5 | WordStar v4 application disk | + +Note that unlike the ROM firmware, you do **not** need to choose a +disk image specific to your hardware. Because the RomWBW firmware +provides a hardware abstraction layer, all hard disk images will work +on all hardware variations. Yes, this means you can remove an SD Card +from one system and put it in a different system. The only constraint +is that the applications on the disk media must be up to date with +the firmware on the system being used. + +All of the disk images that indicate they are bootable (boot disk) will +boot from disk as is. You do not need to run `SYSCOPY` on them to make +them bootable. However, if you upgrade your ROM, you should use `SYSCOPY` +to update the system tracks. + +## Booting Disks + +When starting your system, following the hardware initialization, you +will see the Boot Loader prompt. In addition, to the ROM boot +options, you will see another line listing the Disk boot options. +This line lists the disk devices that you can choose to boot directly. + +You will notice that you do not have an option to boot a drive letter +here (like C:). This is because the operating system is not yet +loaded. When you ran `SYSCOPY` previously, remember that C: was +assigned to IDE0:0 which means device IDE0, slice 0. So, to boot the +disk that you just setup with `SYSCOPY`, you would choose option 2. +You will then be prompted for the slice on IDE0 that you want to +boot. For now, just press enter to choose slice 0. Once you are +familiar with slices, you can `SYSCOPY` and boot alternate slices. +Here is what you would see when booting to a disk device: + +``` +MARK IV Boot Loader + +ROM: (M)onitor (C)P/M (Z)-System (F)orth (B)ASIC (T)-BASIC (P)LAY (U)SER ROM +Disk: (0)MD1 (1)MD0 (2)IDE0 (3)IDE1 + +Boot Selection? 2 Slice(0-9)[0]? + +Booting Disk Unit 2, Slice 0... + +Reading disk information... +Loc=D000 End=FE00 Ent=E600 Label=Unlabeled Drive + +Loading... +``` + +Following this, you would see the normal operating system startup +messages. However, your operating system prompt will be `A>` and +when you look at the drive letter assignments, you should see that A: +has been assigned to the disk you selected to boot. + +If you receive the error message "Disk not bootable!", you have +either failed to properly run `SYSCOPY` on the target disk or you +have selected the wrong disk/slice. + +Note that although MD1 (RAM disk) and MD0 (ROM disk) drives are +listed in the Disk boot line, they are not "bootable" disks because +they have no system tracks on them. Attempting to boot to one of +them, will fail with a "Disk not bootable!" error message and return +to the loader prompt. + +# Operating Systems + +One of the primary goals of RomWBW is to expose a set of generic +hardware functions that make it easy to adapt operating systems to +any hardware supported by RomWBW. As a result, there are now 5 +operating systems that have been adapted to run under RomWBW. The +adaptations are identical for all hardware supported by RomWBW +because RomWBW hides all hardware specifics from the operating system. + +Note that all of the operating systems included with RomWBW support +the same basic filesystem format. As a result, a formatted filesystem +will be accessible to any operating system. The only possible issue +is that if you turn on date/time stamping using the newer OSes, the +older OSes will not understand this. Files will not be corrupted, but +the date/time stamps may be lost. + +The following sections briefly describe the operating system options +currently available. + +## Digital Research CP/M 2.2 + +This is the most widely used variant of the Digital Research +operating system. It has the most basic feature set, but is +essentially the compatibility metric for all other CP/M-like +operating systems including all of those listed below. The Doc +directory contains a manual for CP/M usage ("CPM Manual.pdf"). If you +are new to the CP/M world, I would recommend using this CP/M variant +to start with simply because it is the most stable and you are less +likely to encounter problems. + +### Notes + +- The original versions of DDT, DDTZ, and ZSID used the RST 38 +vector which conflicts with interrupt mode 1 use of this vector. +The DDT, DDTZ, and ZSID applications in RomWBW have been modified +to use RTS 30 to avoid this issue. + +- Z-System applications will not run under CP/M 2.2. For example, +the `LDDS` date stamper with not run. + +## ZSDOS 1.1 + +ZSDOS is the most popular non-DRI CP/M "clone" which is generally +referred to as Z-System. Z-System is intended to be an enhanced +version of CP/M and should run all CP/M 2.2 applications. It is +optimized for the Z80 CPU (as opposed to 8080 for CP/M) and has some +significant improvements such as date/time stamping of files. For +further information on the RomWBW implementation of Z-System, see the +wiki page +[Z-System Notes](https://www.retrobrewcomputers.org/doku.php?id=software:firmwareos:romwbw:zsystem). +Additionally, the official documentation for Z-System is included in +the RomWBW distribution Doc directory ("ZSDOS Manual.pdf" and "ZCPR +Manual.pdf"). + +### Notes + +- Although most CP/M 2.2 applications will run under Z-System, some +may not work as expected. The best example is PIP which is not aware +of the ZSDOS paths and will fail in some scenarios (use `COPY` instead). + +## NZCOM Automatic Z-System + +NZCOM is a much further refined version of Z-System (ZCPR 3.4). NZCOM +was sold as an enhancement for existing users of CP/M 2.2 or ZSDOS. +For this reason, (by design) NZCOM does not provide a way to boot +directly from disk. Rather, it is loaded after the system boots into +a host OS. On the RomWBW NZCOM disk images, the boot OS is ZSDOS 1.1. +After you configure NZCOM, you can add a `PROFILE.SUB` file to +automatically launch NZCOM at boot. + +NZCOM is not pre-configured. You must run through a simple +configuration process before loading it. Run MKZCM to do this. + +NZCOM has substantially more functionality than CP/M or basic +Z-System. It is important to read the the "NZCOM Users +Manual.pdf" file in the RomWBW Doc directory. + +### Notes + +- There is no `DIR` command, you must use `SDZ` instead. If you don't +like this, look into the ALIAS facility. + +## Digital Research CP/M 3 + +This is the Digital Research follow-up product to their very popular +CP/M 2.2 operating system. While highly compatible with CP/M 2.2, it +features many enhancements. It makes direct use of banked memory to +increase the user program space (TPA). It also has a new suite of +support tools and help system. + +Note that to make a CP/M 3 boot disk, you actually place CPMLDR.SYS +on the system tracks of the disk. You do not place CPM3.SYS on the +system tracks. `CPMLDR.SYS` chain loads `CPM3.SYS`. + +### Notes + +- The `DATE` command cannot yet be used to **set** the RTC. The RTC is +used to read the current date/time for file stamping, etc. You can +use the `RTC` app to set the RTC clock. + +## Simeon Cran's ZPM3 + +ZPM3 is an interesting combination of the features of both CP/M 3 and +ZCPR 3. Essentially, it has the features of and compatibility with +both. + +Like CP/M 3, to make ZPM3 boot disk, you put CPMLDR.SYS on the system +tracks of the disk. + +### Notes + +- `ZPMLDR` is included with ZPM3, but it is not working correctly. + +- The ZPM operating system is contained in the file called CPM3.SYS +which is confusing, but it is the author's intended way of using ZPM3. + +## FreeRTOS + +Phillip Stevens has ported FreeRTOS to run under RomWBW. FreeRTOS is +not provided in the RomWBW distribution. FreeRTOS is available under +the +[MIT licence](https://www.freertos.org/a00114.html) and further general +information is available at +[FreeRTOS](https://www.freertos.org/RTOS.html). + +You can also contact Phillip for detailed information on the Z180 +implementation of FreeRTOS for RomWBW. +[feilipu](https://github.com/feilipu) + +# Transferring Files + +Transferring files between your modern computer and your RomWBW +system can be achieved in a variety of ways. The most common of these +are described below. All of these have a certain degree of complexity +and I encourage new users to use the available community forums to +seek assistance as needed. + +## Serial Port Transfers + +RomWBW provides an serial file transfer program called XModem that +has been adapted to run under RomWBW hardware. The program is called +`XM` and is on your ROM disk as well as all of the pre-built disk +images. + +You can type `XM` by itself to get usage information. In general, you +will run `XM` with parameters to indicate you want to send or receive +a file on your RomWBW system. Then, you will use your modern +computers terminal program to complete the process. + +The `XM` application generally tries to detect the hardware you are +using and adapt to it. However, you must ensure that you have a +reliable serial connection. You must also ensure that the speed of +the connection is not too fast for XModem to service. Alternatively, +you can ensure that hardware flow control is working properly. + +There is an odd interaction between XModem and partner terminal +programs that can occur. Essentially, after launching `XM`, you must +start the protocol on your modern computer fairly quickly (usually in +about 20 seconds or so). So, if you do not pick a file on your modern +computer quickly enough, you will find that the transfer completes +about 16K, then hangs. The interaction that causes this is beyond the +scope of this document. + +## Disk Image Transfers + +It is possible to pass disk images between your RomWBW system and +your modern computer. This assumes you have an appropriate media slot +on your modern computer for the media you want to use (CF Card, SD +Card, or floppy drive). + +The general process to get files from your modern computer to a RomWBW +computer is: + +1. Use `cpmtools` on your modern computer to create a RomWBW CP/M +filesystem image. + +2. Insert your RomWBW media (CF Card, SD Card, or floppy disk) in your +modern computer. + +3. Use a disk imaging tool to copy the RomWBW filesystem image onto the +media. + +4. Move the media back to the RomWBW computer. + +This process is a little complicated, but it has the benefit of +allowing you to get a lot of files over to your RomWBW system quickly +and with little chance of corruption. + +The process can be run in reverse to get files from your RomWBW +computer to a modern computer. + +The exact use of these tools is a bit too much for this document, but +the tools are all included in the RomWBW distribution along with +usage documents. + +Note that the build scripts for RomWBW create the default disk images +supplied with RomWBW. It is relatively easy to customize the contents +of the disk images that are part of RomWBW. This is described in more +detail in the Source\\Images directory of the distribution. + +## FAT Filesystem Transfers + +RomWBW provides a mechanism that allows it to read and write files on +a FAT formatted disk. This means that you can generally use your +modern computer to make an SD Card or CF Card with a standard FAT32 +filesystem on it, then place that media in your RomWBW computer and +access the files. + +When formatting the media on your modern computer, be sure to pick +the FAT filesystem. NTFS and other filesystems will not work. + +On your RomWBW computer you can use the `FAT` application to access +the FAT media. The `FAT` application allows you to read files, write +files, list a directory, and erase files on the FAT media. It can +handle subdirectories as well. It will only see 8.3 character +filenames however. Longer filenames will show up as a truncated +version. + +The `FAT` application is not on your ROM disk because it is too large +to fit. You will find it on all of the pre-built disk images as well +as in the Binary\\Apps directory of the distribution. + +For advanced users, it is possible to create a hybrid disk that +contains CP/M slices at the beginning and a FAT filesystem after. +Such a hybrid disk can be used to boot an operating system and still +have access to FAT files on the FAT portion of the disk. David Reese +has prepared a document describing how to do this. It is called +"SC126_How-To_No_2_Preparing_an_SD_Card_for_Use_with_SC126_Rev_1-5.pdf" +and can be found in the Doc\\Contrib directory of the distribution. + +# Startup Command Processing + +Each of the operating systems supported by RomWBW provide a mechanism +to run commands at boot. This is similar to the AUTOEXEC.BAT files +from MS-DOS. + +With the exception of ZPM3, all operating systems will look for a +file called `PROFILE.SUB` on the system drive at boot. If it is +found, it will be processed as a standard CP/M submit file. You can +read about the use of the SUBMIT facility in the CP/M manuals +included in the RomWBW distribution. Note that the boot disk must +also have a copy of `SUBMIT.EXE`. + +In the case of ZPM3, the file called `STARTZPM.COM` will be run at +boot. To customize this file, you use the ZCPR ALIAS facility. You +will need to refer to ZCPR documentation for more information on the +ALIAS facility. + +Note that the automatic startup processing generally requires booting +to a disk drive. Since the ROM disk is not writable, there is no +simple way to add/edit a `PROFILE.SUB` file there. If you want to +customize your ROM and add a `PROFILE.SUB` file to the ROM Disk, it +will work, but is a lot harder than using a boot disk. + +# ROM Customization + +The pre-built ROM images are configured for the basic capabilities of +each platform. Additionally, some of the typical add-on hardware for +each platform will be automatically detected and used. If you want to +go beyond this, RomWBW provides a very flexible configuration +mechanism based on configuration files. Creating a customized ROM +requires running a build script, but it is quite easy to do. + +Essentially, the creation of a custom ROM is accomplished by updating +a small configuration file, then running a script to compile the +software and generate the custom ROM and disk images. There are build +scripts for Windows, Linux, and MacOS to accommodate virtually all +users. All required build tools (compilers, assemblers, etc.) are +included in the distribution, so it is not necessary to setup a build +environment on your computer. + +The process for building a custom ROM is documented in the ReadMe.txt +file in the Source directory of the distribution. + +For those who are interested in more than basic system customization, +note that all source code is provided (including the operating +systems). Modification of the source code is considered an expert +level task and is left to the reader to pursue. + +Note that the ROM customization process does not apply to UNA. All +UNA customization is performed within the ROM setup script. + +# UNA Hardware BIOS + +John Coffman has produced a new generation of hardware BIOS called +UNA. The standard RomWBW distribution includes it's own hardware +BIOS. However, RomWBW can alternatively be constructed with UNA as +the hardware BIOS portion of the ROM. If you wish to use the UNA +variant of RomWBW, then just program your ROM with the ROM image +called "UNA_std.rom" in the Binary directory. This one image is +suitable on **all** of the platforms and hardware UNA supports. + +UNA is customized dynamically using a ROM based setup routine and the +setup is persisted in the system NVRAM of the RTC chip. This means +that the single UNA-based ROM image can be used on most of the +RetroBrew platforms and is easily customized. UNA also supports FAT +file system access that can be used for in-situ ROM programming and +loading system images. + +While John is likely to enhance UNA over time, there are currently a +few things that UNA does not support: + +* Floppy Drives +* Terminal Emulation +* Zeta 1, N8, RC2014, Easy Z80, and Dyno Systems +* Some older support boards + +The UNA version embedded in RomWBW is the latest production release +of UNA. RomWBW will be updated with John's upcoming UNA release with +support for VGA3 as soon as it reaches production status. + +Please refer to the +[UNA BIOS Firmware Page](https://www.retrobrewcomputers.org/doku.php?id=software:firmwareos:una:start) +for more information on UNA. + +# Upgrading + +Upgrading to a newer release of RomWBW is essentially just a matter of +updating the ROM chip in your system. If you have spare ROM chips for +your system and a ROM programmer, it is always safest to retain your +existing, working ROM chip and program a new one with the new +firmware. If the new one fails to boot, you can easily return to the +known working ROM. + +Prior to attempting to reprogram your actual ROM chip, you may wish to +"try" the upgrade. With RomWBW, you can upload a new system image +executable and load it from the command line. For each ROM image file +(.rom) in the Binary directory, you will also find a corresponding +application file (.com). For example, for SBC_std.rom, there is also +an SBC_std.com file. You can upload the .com file to your system using +XModem, then simply run the .com file. You will see your system go +through the normal startup process just like it was started from ROM. +However, your ROM has not been updated and the next time you boot your +system, it will revert to the system image contained in ROM. + +If you do not have easy access to a ROM programmer, it is usually +possible to reprogram your system ROM using the FLASH utility from +Will Sowerbutts. This application, called FLASH.COM, can be found on the +ROM drive of any running system. In this case, you would need to +transfer the new ROM image (.rom) over to your system using XModem (or +one of the other mechanisms described in the Transferring Files +section). The ROM image is too large to fit on your RAM drive, +so you will need to transfer it to a larger storage drive. Once the +ROM image is on your system, you can use the FLASH application to +update your ROM. The following is a typical example of transferring +ROM image using XModem and flashing the chip in-situ. + +``` +E>xm r rom.img + +XMODEM v12.5 - 07/13/86 +RBC, 28-Aug-2019 [WBW], ASCI + +Receiving: E0:ROM.IMG +7312k available for uploads +File open - ready to receive +To cancel: Ctrl-X, pause, Ctrl-X + +Thanks for the upload + +E>flash write rom.img +FLASH4 by Will Sowerbutts version 1.2.3 + +Using RomWBW (v2.6+) bank switching. +Flash memory chip ID is 0xBFB7: 39F040 +Flash memory has 128 sectors of 4096 bytes, total 512KB +Write complete: Reprogrammed 2/128 sectors. +Verify (128 sectors) complete: OK! +``` + +Obviously, there is some risk to this approach since any issues with the +programming or ROM image could result in a non-functional system. + +To confirm your ROM chip has been successfully updated, restart your +system and boot an operating system from ROM. Do not boot from a disk +device yet. Review the boot messages to see if any issues have +occurred. + +Once you are satisfied that the ROM is working well, you will need to +update the system images and RomWBW custom applications on your disk +drives. The system images and custom applications are matched to the +RomWBW ROM firmware in use. If you attempt to boot a disk or run +applications that have not been updated to match the current ROM +firmware, you are likely to have odd problems. + +The simplest way to update your disk media is to just use your modern +computer to overwrite the entire media with the latest disk image of +your choice. This process is described below in the Disk Images +section. If you wish to update existing disk media in your system, you +need to perform the following steps. + +If the disk is bootable, you need to update the system tracks of the +disk. This is done using a SYSCOPY command such as `SYSCOPY +C:=B:ZSYS.SYS`. For a ZSDOS boot disk, use ZSYS.SYS. For a CP/M 2.2 +disk, use CPM.SYS. For a CP/M 3 or ZPM3 disk, use CPMLDR.SYS. +CPMLDR.SYS is not provided on the ROM disk, so you will need to +upload it from the distribution. + +Finally, if you have copies of any of the RomWBW custom applications +on your hard disk, you need to update them with the latest copies. The +following applications are found on your ROM disk. Use COPY to copy +them over any older versions of the app on your disk: + +* ASSIGN.COM +* SYSCOPY.COM +* MODE.COM +* FDU.COM (was FDTST.COM) +* FORMAT.COM +* XM.COM +* FLASH.COM +* FDISK80.COM +* TALK.COM +* RTC.COM +* TIMER.COM +* INTTEST.COM + +For example: `B>COPY ASSIGN.COM C:` + +Some RomWBW custom applications are too large to fit on the ROM disk. +If you are using any of these you will need to transfer them to your +system and then update all copies. These applications are found in +the Binary\\Apps directory of the distribution and in all of the disk +images. + +* FAT.COM +* TUNE.COM + +# RomWBW Distribution + +All source code and distributions are maintained on GitHub. Code +contributions are very welcome. + +[RomWBW GitHub Repository](https://github.com/wwarthen/RomWBW|https://github.com/wwarthen/RomWBW) + +## Distribution Directory Layout + +The RomWBW distribution is a compressed zip archive file organized in +a set of directories. Each of these directories has it's own +ReadMe.txt file describing the contents in detail. In summary, these +directories are: + +| Application | Description | +| ----------- | -------------------------------------------------------------- | +| Binary | The final output files of the build process are placed here. Most importantly, are the ROM images with the file names ending in ".rom". | +| Doc | Contains various detailed documentation including the operating systems, RomWBW architecture, etc. | +| Source | Contains the source code files used to build the software and ROM images. | +| Tools | Contains the MS Windows programs that are used by the build process or that may be useful in setting up your system. | + +# Acknowledgments + +While I have heavily modified much of the code, I want to acknowledge +that much of the work is derived from the work of others in the +RetroBrew Computers Community including Andrew Lynch, Dan Werner, Max +Scane, David Giles, John Coffman, and probably many others I am not +clearly aware of (let me know if I omitted someone!). + +I especially want to credit Douglas Goodall for contributing code, +time, testing, and advice. He created an entire suite of application +programs to enhance the use of RomWBW. However, he is looking for +someone to continue the maintenance of these applications and they +have become unusable due to changes within RomWBW. As of RomWBW 2.6, +these applications are no longer provided. + +* David Giles contributed support for the CSIO support in the SD Card +driver. +* Ed Brindley contributed some of the code that supports the RC2014 +platform. +* Phil Summers contributed Forth and BASIC in ROM as well as a long +list of general code enhancements. +* Phillip Stevens contributed support for FreeRTOS. +* Curt Mayer contributed the Linux / MacOS build process. +* UNA BIOS and FDISK80 is a product of John Coffman. +* FLASH4 is a product of Will Sowerbutts. + +Contributions of all kinds to RomWBW are very welcome. + +# Getting Assistance + +The best way to get assistance with RomWBW or any aspect of the +RetroBrew Computers projects is via the community forums: + +* [RetroBrew Computers Forum](https://www.retrobrewcomputers.org/forum/) +* [RC2014 Google Group](https://groups.google.com/forum/#!forum/rc2014-z80) +* [retro-comp Google Group](https://groups.google.com/forum/#!forum/retro-comp) + +Submission of issues and bugs are welcome at the [RomWBW GitHub Repository](https://github.com/wwarthen/RomWBW). + +Also feel free to email !author at [!authmail](mailto:!authmail). \ No newline at end of file diff --git a/Source/Doc/Graphics/Bank Switched Memory.pdf b/Source/Doc/Graphics/Bank Switched Memory.pdf new file mode 100644 index 00000000..42db138c Binary files /dev/null and b/Source/Doc/Graphics/Bank Switched Memory.pdf differ diff --git a/Source/Doc/Graphics/Bank Switched Memory.png b/Source/Doc/Graphics/Bank Switched Memory.png new file mode 100644 index 00000000..f617cb0d Binary files /dev/null and b/Source/Doc/Graphics/Bank Switched Memory.png differ diff --git a/Source/Doc/Bank Switched Memory.vsd b/Source/Doc/Graphics/Bank Switched Memory.vsd similarity index 100% rename from Source/Doc/Bank Switched Memory.vsd rename to Source/Doc/Graphics/Bank Switched Memory.vsd diff --git a/Source/Doc/Character Emulation Video Services.vsd b/Source/Doc/Graphics/Character Emulation Video Services.vsd similarity index 100% rename from Source/Doc/Character Emulation Video Services.vsd rename to Source/Doc/Graphics/Character Emulation Video Services.vsd diff --git a/Source/Doc/Graphics/Character Emulation Video Services.pdf b/Source/Doc/Graphics/Character Emulation Video Services.pdf new file mode 100644 index 00000000..fadac746 Binary files /dev/null and b/Source/Doc/Graphics/Character Emulation Video Services.pdf differ diff --git a/Source/Doc/Graphics/Character Emulation Video Services.png b/Source/Doc/Graphics/Character Emulation Video Services.png new file mode 100644 index 00000000..4ccb3cc8 Binary files /dev/null and b/Source/Doc/Graphics/Character Emulation Video Services.png differ diff --git a/Source/Doc/Graphics/Hard Disk Anatomy.vsd b/Source/Doc/Graphics/Hard Disk Anatomy.vsd new file mode 100644 index 00000000..680e16e3 Binary files /dev/null and b/Source/Doc/Graphics/Hard Disk Anatomy.vsd differ diff --git a/Source/Doc/Graphics/Logo.pdf b/Source/Doc/Graphics/Logo.pdf new file mode 100644 index 00000000..0921f91a Binary files /dev/null and b/Source/Doc/Graphics/Logo.pdf differ diff --git a/Source/Doc/RomWBW System Guide/Logo.png b/Source/Doc/Graphics/Logo.png similarity index 100% rename from Source/Doc/RomWBW System Guide/Logo.png rename to Source/Doc/Graphics/Logo.png diff --git a/Source/Doc/Graphics/Logo.svg b/Source/Doc/Graphics/Logo.svg new file mode 100644 index 00000000..da51b423 --- /dev/null +++ b/Source/Doc/Graphics/Logo.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + Page-1 + + + Sheet.34 + + + + Sheet.35 + + + + Sheet.36 + + + + Sheet.38 + + + + Sheet.39 + + + + Sheet.44 + + + + Sheet.48 + + + + Sheet.49 + + + + Sheet.50 + + + + Sheet.52 + + + + Sheet.53 + + + + diff --git a/Source/Doc/WBW.vsdx b/Source/Doc/Graphics/WBW.vsdx similarity index 100% rename from Source/Doc/WBW.vsdx rename to Source/Doc/Graphics/WBW.vsdx diff --git a/Source/Doc/RomWBW Architecture.docx b/Source/Doc/RomWBW Architecture.docx deleted file mode 100644 index 8aacad68..00000000 Binary files a/Source/Doc/RomWBW Architecture.docx and /dev/null differ diff --git a/Source/Doc/RomWBW System Guide/Build.cmd b/Source/Doc/RomWBW System Guide/Build.cmd deleted file mode 100644 index 3413f3fc..00000000 --- a/Source/Doc/RomWBW System Guide/Build.cmd +++ /dev/null @@ -1,19 +0,0 @@ -@echo off -setlocal - -rem set MIKTEX_HOME=D:\miktex-portable\texmfs\install - -rem if "%MIKTEX_HOME%"=="" goto :eof - -rem set TEXSYSTEM=miktex -rem set MIKTEX_BINDIR=%MIKTEX_HOME%\miktex\bin -rem set MIKTEX_COMMONSTARTUPFILE=%MIKTEX_HOME%\miktex\config\miktexstartup.ini -rem set MIKTEX_GS_LIB=%MIKTEX_HOME%\ghostscript\base;%MIKTEX_HOME%\fonts -rem set MIKTEX_USERSTARTUPFILE=%MIKTEX_HOME%\miktex\config\miktexstartup.ini -rem set PATH=%MIKTEX_HOME%\miktex\bin;%PATH% - -call texify -p --clean Main.ltx - -if errorlevel 1 goto :eof - -move /Y Main.pdf "..\..\..\Doc\RomWBW System Guide.pdf" \ No newline at end of file diff --git a/Source/Doc/RomWBW System Guide/Clean.cmd b/Source/Doc/RomWBW System Guide/Clean.cmd deleted file mode 100644 index 66c5e249..00000000 --- a/Source/Doc/RomWBW System Guide/Clean.cmd +++ /dev/null @@ -1,9 +0,0 @@ -@echo off -setlocal - -if exist *.aux del *.aux -if exist *.log del *.log -if exist *.toc del *.toc -if exist *.lot del *.lot -if exist *.lof del *.lof -if exist *.pdf del *.pdf diff --git a/Source/Doc/RomWBW System Guide/Generate.cmd b/Source/Doc/RomWBW System Guide/Generate.cmd deleted file mode 100644 index f43109b8..00000000 --- a/Source/Doc/RomWBW System Guide/Generate.cmd +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -setlocal - -texify -p -V --run-viewer Main.ltx diff --git a/Source/Doc/RomWBW System Guide/Main.ltx b/Source/Doc/RomWBW System Guide/Main.ltx deleted file mode 100644 index b6cb5a9c..00000000 --- a/Source/Doc/RomWBW System Guide/Main.ltx +++ /dev/null @@ -1,790 +0,0 @@ -\documentclass[letterpaper,12pt]{refrep} -\usepackage[T1]{fontenc} % Handle char values 128-255 -\usepackage[scaled]{helvet} % A nice sans serif font please -\usepackage{blindtext} % Enable \blindtext -\usepackage{scrextend} % ??? -\addtokomafont{labelinglabel}{\bfseries} % Make list labels bold -\usepackage[inline]{enumitem} % Prettier version of enumerate list -\usepackage{xcolor} % Enable colors? -\usepackage{framed} % Enable framing (used in examples) -\usepackage{graphicx} % ??? -\usepackage{fancyhdr} % More flexible header formatting -\usepackage{xhfill} % Enable \xrfill used in footer -%\usepackage{listings} % Handles source listings, improves on verbatim -\usepackage{fancyvrb} % Enhances \verbatim to allow internal formatting -\usepackage{hyperref} % More flexible hyperref formatting (\hypersetup) -\usepackage{bookmark} % Avoids "Package rerunfilecheck Warning" -\renewcommand*{\familydefault}{\sfdefault} -\title{RomWBW System Guide\\Version 2.8} -\author{Wayne Warthen\\wwarthen@gmail.com\\\\RetroBrew Computing Group\\http://www.retrobrewcomputers.org} -\date{\today} -\fullpage -\sloppy -\hypersetup{colorlinks=true} - -%\pdfobjcompresslevel=1 - -\begin{document} - -\pagenumbering{roman} - -\pagestyle{empty} - -%\maketitle -\begin{titlepage} - \centering - \par - \vspace*{72pt} - \includegraphics{Logo.png} \par - \vfill - \raggedleft - {\scshape \bfseries \fontsize{48pt}{56pt} \selectfont RomWBW \par} - {\bfseries \fontsize{32pt}{36pt} \selectfont System Guide \par} - \vspace{24pt} - {\huge Version 2.8 \\ \today \par} - \vspace{24pt} - {\large \itshape RetroBrew Computing Group \\ \href{http://www.retrobrewcomputers.org}{www.retrobrewcomputers.org} \par} - \vspace{12pt} - {\large \itshape Wayne Warthen \\ \href{mailto:wwarthen@gmail.com}{wwarthen@gmail.com} \par} -\end{titlepage} - -%\newpage - -\setcounter{page}{2} - -\begin{center} \Large COPYRIGHT \end{center} - -Copyright \copyright{} 2016 Wayne Warthen - -Permission is granted to copy, distribute and/or modify this document -under the terms of the GNU Free Documentation License, Version 1.3 -or any later version published by the Free Software Foundation; -with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. -A copy of the license is included in the section entitled "GNU -Free Documentation License". - -\bigskip -\begin{center} \Large DISCLAIMER \end{center} - -The author makes no representations or warranties with respect to the contents hereof and -specifically disclaims any implied warranties of merchantability or fitness for any particular -purpose. Further, the author reserves the right to revise this publication and to make -changes from time to time in the content hereof without obligation of the author to notify -any person of such revision or changes. - -\bigskip -\begin{center} \Large TRADEMARKS \end{center} - -CP/M is a registered trademark of Digital Research. ASM, DESPOOL, DDT, -LINK-80, MAC, MP/M, PL/1-80 and SID are trademarks of Digital Research. Intel is a -registered trademark of Intel Corporation. Zilog and Z80 are registered trademarks of Zilog, Inc. - -\bigskip -\bigskip -\begin{center} \rule{3cm}{2pt} \end{center} - -\bigskip -\bigskip -\begin{it} -This document was formatted using \LaTeX{} and produced using the MiKTeX implementation of pdfLaTeX, MakeIndex, and BibTeX running on Microsoft Windows. -\end{it} - -%\clearpage - -\fancyhf{} -\setlength{\headheight}{15.2pt} -\pagestyle{fancyplain} -\renewcommand{\chaptermark}[1]{ \markboth{#1}{} } -\renewcommand{\sectionmark}[1]{ \markright{#1}{} } -\lhead{ \fancyplain{}{\footnotesize \bfseries \rightmark \hfill RomWBW System Guide} } -\lfoot{ \fancyplain{}{\small RetroBrew Computing Group ~~ {\xrfill[3pt]{1pt}[cyan]} ~~ \thepage} } - -\renewcommand{\contentsname}{Table of Contents} -\tableofcontents -\listoftables -\listoffigures -\clearpage - -\lhead{ \fancyplain{}{\footnotesize \bfseries \thesection ~ \rightmark \hfill RomWBW System Guide} } -\pagenumbering{arabic} - -%\input{Overview.ltx} - -%\input{GettingStarted.ltx} - -\chapter{Introduction} - -\LaTeX{} is a document preparation system for -the \TeX{} typesetting program. It offers excellent -programmable desktop publishing features and -extensive facilities for automating most -aspects of typesetting and desktop publishing, -including numbering and cross-referencing, -tables and figures, page layout, -bibliographies, and much more. \LaTeX{} was -originally written in 1984 by Leslie Lamport -and has become the dominant method for using -\TeX; few people write in plain \TeX{} anymore. -The current version is \LaTeXe. - -\section{Acknowledgements} - -\section{System Requirements} - -\section{Getting Started} - -\newpage - -This is a second paragraph. - -This is a third paragraph. - -An itemized list: - -\begin{itemize} -\item First list item -\item Second list item -\item Third list item -\end{itemize} - - -An enumerated list: - -\begin{enumerate}[label=\textbf{\arabic*}.] -\item First list item -\item Second list item -\item Third list item -\end{enumerate} - -A description list: - -\begin{description} -\item [Ant] What is an ant? -\item [Elephant] \blindtext -\end{description} - -Labeling: - -\begin{labeling}{alligator} -\item [ant] really busy all the time -\item [chimp] likes bananas -\item [alligator] very dangerous animal, sharp teeth, long -muscular tail and a bit of text that is longer than one -line and shows the alignment of text quite nicely -\end{labeling} - -\section{Getting Started} - -\begin{framed} -\ttfamily -\footnotesize -%\small -\begin{verbatim} -C:> -12345678901234567890123456789012345678901234567890123456789012345678901234567890 -12345678901234567890123456789012345678901234567890123456789012345678901234567890 -12345678901234567890123456789012345678901234567890123456789012345678901234567890 -12345678901234567890123456789012345678901234567890123456789012345678901234567890 -12345678901234567890123456789012345678901234567890123456789012345678901234567890 -12345678901234567890123456789012345678901234567890123456789012345678901234567890 -12345678901234567890123456789012345678901234567890123456789012345678901234567890 -12345678901234567890123456789012345678901234567890123456789012345678901234567890 -12345678901234567890123456789012345678901234567890123456789012345678901234567890 -\end{verbatim} -\end{framed} - -Now something else. - -Below is a table: - -\begin{tabular}{l ||| l | l | p{5cm}} -\hline -hello & goodbyte & hex & decimal \\ -\hline -\hline -hello & goodbyte & hex & decimal \\ -hello & goodbyte & hex & decimal \\ -\hline -\end{tabular} - -\chapter{Introduction} -\section{Getting Started} -\blindtext \par -\blindtext \par -\blindtext \par -\blindtext \par -\blindtext \par -\blindtext \par - -\chapter{Introduction} -\section{Getting Started} -\blindtext - -\chapter{Introduction} -\section{Getting Started} -\blindtext - -\chapter{Introduction} -\section{Getting Started} -\blindtext - -\chapter{Introduction} -\section{Getting Started} -\blindtext - -\chapter{Introduction} -\section{Getting Started} -\blindtext - -\chapter{Introduction} -\section{Getting Started} -\blindtext - -\chapter{Introduction} -\section{Getting Started} -\blindtext - -\chapter{Introduction} -\section{Getting Started} -\blindtext - -\chapter{Introduction} -\section{Getting Started} -\blindtext - -\appendix - -\chapter{Licensing} - -\section{GNU Free Documentation License} - -%\chapter*{\rlap{GNU Free Documentation License}} -%\addcontentsline{toc}{chapter}{GNU Free Documentation License} - - \begin{center} - - Version 1.3, 3 November 2008 - -\end{center} - -Copyright \copyright{} 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. -\href{http://fsf.org/}{} - - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - -{\large\bf 0. PREAMBLE \par} - -The purpose of this License is to make a manual, textbook, or other -functional and useful document ``free'' in the sense of freedom: to -assure everyone the effective freedom to copy and redistribute it, -with or without modifying it, either commercially or noncommercially. -Secondarily, this License preserves for the author and publisher a way -to get credit for their work, while not being considered responsible -for modifications made by others. - -This License is a kind of ``copyleft'', which means that derivative -works of the document must themselves be free in the same sense. It -complements the GNU General Public License, which is a copyleft -license designed for free software. - -We have designed this License in order to use it for manuals for free -software, because free software needs free documentation: a free -program should come with manuals providing the same freedoms that the -software does. But this License is not limited to software manuals; -it can be used for any textual work, regardless of subject matter or -whether it is published as a printed book. We recommend this License -principally for works whose purpose is instruction or reference. - - -{\large\bf 1. APPLICABILITY AND DEFINITIONS\par} -%\phantomsection -%\addcontentsline{toc}{section}{1. APPLICABILITY AND DEFINITIONS} - -This License applies to any manual or other work, in any medium, that -contains a notice placed by the copyright holder saying it can be -distributed under the terms of this License. Such a notice grants a -world-wide, royalty-free license, unlimited in duration, to use that -work under the conditions stated herein. The ``\textbf{Document}'', below, -refers to any such manual or work. Any member of the public is a -licensee, and is addressed as ``\textbf{you}''. You accept the license if you -copy, modify or distribute the work in a way requiring permission -under copyright law. - -A ``\textbf{Modified Version}'' of the Document means any work containing the -Document or a portion of it, either copied verbatim, or with -modifications and/or translated into another language. - -A ``\textbf{Secondary Section}'' is a named appendix or a front-matter section of -the Document that deals exclusively with the relationship of the -publishers or authors of the Document to the Document's overall subject -(or to related matters) and contains nothing that could fall directly -within that overall subject. (Thus, if the Document is in part a -textbook of mathematics, a Secondary Section may not explain any -mathematics.) The relationship could be a matter of historical -connection with the subject or with related matters, or of legal, -commercial, philosophical, ethical or political position regarding -them. - -The ``\textbf{Invariant Sections}'' are certain Secondary Sections whose titles -are designated, as being those of Invariant Sections, in the notice -that says that the Document is released under this License. If a -section does not fit the above definition of Secondary then it is not -allowed to be designated as Invariant. The Document may contain zero -Invariant Sections. If the Document does not identify any Invariant -Sections then there are none. - -The ``\textbf{Cover Texts}'' are certain short passages of text that are listed, -as Front-Cover Texts or Back-Cover Texts, in the notice that says that -the Document is released under this License. A Front-Cover Text may -be at most 5 words, and a Back-Cover Text may be at most 25 words. - -A ``\textbf{Transparent}'' copy of the Document means a machine-readable copy, -represented in a format whose specification is available to the -general public, that is suitable for revising the document -straightforwardly with generic text editors or (for images composed of -pixels) generic paint programs or (for drawings) some widely available -drawing editor, and that is suitable for input to text formatters or -for automatic translation to a variety of formats suitable for input -to text formatters. A copy made in an otherwise Transparent file -format whose markup, or absence of markup, has been arranged to thwart -or discourage subsequent modification by readers is not Transparent. -An image format is not Transparent if used for any substantial amount -of text. A copy that is not ``Transparent'' is called ``\textbf{Opaque}''. - -Examples of suitable formats for Transparent copies include plain -ASCII without markup, Texinfo input format, LaTeX input format, SGML -or XML using a publicly available DTD, and standard-conforming simple -HTML, PostScript or PDF designed for human modification. Examples of -transparent image formats include PNG, XCF and JPG. Opaque formats -include proprietary formats that can be read and edited only by -proprietary word processors, SGML or XML for which the DTD and/or -processing tools are not generally available, and the -machine-generated HTML, PostScript or PDF produced by some word -processors for output purposes only. - -The ``\textbf{Title Page}'' means, for a printed book, the title page itself, -plus such following pages as are needed to hold, legibly, the material -this License requires to appear in the title page. For works in -formats which do not have any title page as such, ``Title Page'' means -the text near the most prominent appearance of the work's title, -preceding the beginning of the body of the text. - -The ``\textbf{publisher}'' means any person or entity that distributes -copies of the Document to the public. - -A section ``\textbf{Entitled XYZ}'' means a named subunit of the Document whose -title either is precisely XYZ or contains XYZ in parentheses following -text that translates XYZ in another language. (Here XYZ stands for a -specific section name mentioned below, such as ``\textbf{Acknowledgements}'', -``\textbf{Dedications}'', ``\textbf{Endorsements}'', or ``\textbf{History}''.) -To ``\textbf{Preserve the Title}'' -of such a section when you modify the Document means that it remains a -section ``Entitled XYZ'' according to this definition. - -The Document may include Warranty Disclaimers next to the notice which -states that this License applies to the Document. These Warranty -Disclaimers are considered to be included by reference in this -License, but only as regards disclaiming warranties: any other -implication that these Warranty Disclaimers may have is void and has -no effect on the meaning of this License. - - -{\large\bf 2. VERBATIM COPYING\par} -%\phantomsection -%\addcontentsline{toc}{section}{2. VERBATIM COPYING} - -You may copy and distribute the Document in any medium, either -commercially or noncommercially, provided that this License, the -copyright notices, and the license notice saying this License applies -to the Document are reproduced in all copies, and that you add no other -conditions whatsoever to those of this License. You may not use -technical measures to obstruct or control the reading or further -copying of the copies you make or distribute. However, you may accept -compensation in exchange for copies. If you distribute a large enough -number of copies you must also follow the conditions in section~3. - -You may also lend copies, under the same conditions stated above, and -you may publicly display copies. - - -{\large\bf 3. COPYING IN QUANTITY\par} -%\phantomsection -%\addcontentsline{toc}{section}{3. COPYING IN QUANTITY} - - -If you publish printed copies (or copies in media that commonly have -printed covers) of the Document, numbering more than 100, and the -Document's license notice requires Cover Texts, you must enclose the -copies in covers that carry, clearly and legibly, all these Cover -Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on -the back cover. Both covers must also clearly and legibly identify -you as the publisher of these copies. The front cover must present -the full title with all words of the title equally prominent and -visible. You may add other material on the covers in addition. -Copying with changes limited to the covers, as long as they preserve -the title of the Document and satisfy these conditions, can be treated -as verbatim copying in other respects. - -If the required texts for either cover are too voluminous to fit -legibly, you should put the first ones listed (as many as fit -reasonably) on the actual cover, and continue the rest onto adjacent -pages. - -If you publish or distribute Opaque copies of the Document numbering -more than 100, you must either include a machine-readable Transparent -copy along with each Opaque copy, or state in or with each Opaque copy -a computer-network location from which the general network-using -public has access to download using public-standard network protocols -a complete Transparent copy of the Document, free of added material. -If you use the latter option, you must take reasonably prudent steps, -when you begin distribution of Opaque copies in quantity, to ensure -that this Transparent copy will remain thus accessible at the stated -location until at least one year after the last time you distribute an -Opaque copy (directly or through your agents or retailers) of that -edition to the public. - -It is requested, but not required, that you contact the authors of the -Document well before redistributing any large number of copies, to give -them a chance to provide you with an updated version of the Document. - - -{\large\bf 4. MODIFICATIONS\par} -%\phantomsection -%\addcontentsline{toc}{section}{4. MODIFICATIONS} - -You may copy and distribute a Modified Version of the Document under -the conditions of sections 2 and 3 above, provided that you release -the Modified Version under precisely this License, with the Modified -Version filling the role of the Document, thus licensing distribution -and modification of the Modified Version to whoever possesses a copy -of it. In addition, you must do these things in the Modified Version: - -\begin{itemize} -\item[A.] - Use in the Title Page (and on the covers, if any) a title distinct - from that of the Document, and from those of previous versions - (which should, if there were any, be listed in the History section - of the Document). You may use the same title as a previous version - if the original publisher of that version gives permission. - -\item[B.] - List on the Title Page, as authors, one or more persons or entities - responsible for authorship of the modifications in the Modified - Version, together with at least five of the principal authors of the - Document (all of its principal authors, if it has fewer than five), - unless they release you from this requirement. - -\item[C.] - State on the Title page the name of the publisher of the - Modified Version, as the publisher. - -\item[D.] - Preserve all the copyright notices of the Document. - -\item[E.] - Add an appropriate copyright notice for your modifications - adjacent to the other copyright notices. - -\item[F.] - Include, immediately after the copyright notices, a license notice - giving the public permission to use the Modified Version under the - terms of this License, in the form shown in the Addendum below. - -\item[G.] - Preserve in that license notice the full lists of Invariant Sections - and required Cover Texts given in the Document's license notice. - -\item[H.] - Include an unaltered copy of this License. - -\item[I.] - Preserve the section Entitled ``History'', Preserve its Title, and add - to it an item stating at least the title, year, new authors, and - publisher of the Modified Version as given on the Title Page. If - there is no section Entitled ``History'' in the Document, create one - stating the title, year, authors, and publisher of the Document as - given on its Title Page, then add an item describing the Modified - Version as stated in the previous sentence. - -\item[J.] - Preserve the network location, if any, given in the Document for - public access to a Transparent copy of the Document, and likewise - the network locations given in the Document for previous versions - it was based on. These may be placed in the ``History'' section. - You may omit a network location for a work that was published at - least four years before the Document itself, or if the original - publisher of the version it refers to gives permission. - -\item[K.] - For any section Entitled ``Acknowledgements'' or ``Dedications'', - Preserve the Title of the section, and preserve in the section all - the substance and tone of each of the contributor acknowledgements - and/or dedications given therein. - -\item[L.] - Preserve all the Invariant Sections of the Document, - unaltered in their text and in their titles. Section numbers - or the equivalent are not considered part of the section titles. - -\item[M.] - Delete any section Entitled ``Endorsements''. Such a section - may not be included in the Modified Version. - -\item[N.] - Do not retitle any existing section to be Entitled ``Endorsements'' - or to conflict in title with any Invariant Section. - -\item[O.] - Preserve any Warranty Disclaimers. -\end{itemize} - -If the Modified Version includes new front-matter sections or -appendices that qualify as Secondary Sections and contain no material -copied from the Document, you may at your option designate some or all -of these sections as invariant. To do this, add their titles to the -list of Invariant Sections in the Modified Version's license notice. -These titles must be distinct from any other section titles. - -You may add a section Entitled ``Endorsements'', provided it contains -nothing but endorsements of your Modified Version by various -parties---for example, statements of peer review or that the text has -been approved by an organization as the authoritative definition of a -standard. - -You may add a passage of up to five words as a Front-Cover Text, and a -passage of up to 25 words as a Back-Cover Text, to the end of the list -of Cover Texts in the Modified Version. Only one passage of -Front-Cover Text and one of Back-Cover Text may be added by (or -through arrangements made by) any one entity. If the Document already -includes a cover text for the same cover, previously added by you or -by arrangement made by the same entity you are acting on behalf of, -you may not add another; but you may replace the old one, on explicit -permission from the previous publisher that added the old one. - -The author(s) and publisher(s) of the Document do not by this License -give permission to use their names for publicity for or to assert or -imply endorsement of any Modified Version. - - -{\large\bf 5. COMBINING DOCUMENTS\par} -%\phantomsection -%\addcontentsline{toc}{section}{5. COMBINING DOCUMENTS} - - -You may combine the Document with other documents released under this -License, under the terms defined in section~4 above for modified -versions, provided that you include in the combination all of the -Invariant Sections of all of the original documents, unmodified, and -list them all as Invariant Sections of your combined work in its -license notice, and that you preserve all their Warranty Disclaimers. - -The combined work need only contain one copy of this License, and -multiple identical Invariant Sections may be replaced with a single -copy. If there are multiple Invariant Sections with the same name but -different contents, make the title of each such section unique by -adding at the end of it, in parentheses, the name of the original -author or publisher of that section if known, or else a unique number. -Make the same adjustment to the section titles in the list of -Invariant Sections in the license notice of the combined work. - -In the combination, you must combine any sections Entitled ``History'' -in the various original documents, forming one section Entitled -``History''; likewise combine any sections Entitled ``Acknowledgements'', -and any sections Entitled ``Dedications''. You must delete all sections -Entitled ``Endorsements''. - -{\large\bf 6. COLLECTIONS OF DOCUMENTS\par} -%\phantomsection -%\addcontentsline{toc}{section}{6. COLLECTIONS OF DOCUMENTS} - -You may make a collection consisting of the Document and other documents -released under this License, and replace the individual copies of this -License in the various documents with a single copy that is included in -the collection, provided that you follow the rules of this License for -verbatim copying of each of the documents in all other respects. - -You may extract a single document from such a collection, and distribute -it individually under this License, provided you insert a copy of this -License into the extracted document, and follow this License in all -other respects regarding verbatim copying of that document. - - -{\large\bf 7. AGGREGATION WITH INDEPENDENT WORKS\par} -%\phantomsection -%\addcontentsline{toc}{section}{7. AGGREGATION WITH INDEPENDENT WORKS} - - -A compilation of the Document or its derivatives with other separate -and independent documents or works, in or on a volume of a storage or -distribution medium, is called an ``aggregate'' if the copyright -resulting from the compilation is not used to limit the legal rights -of the compilation's users beyond what the individual works permit. -When the Document is included in an aggregate, this License does not -apply to the other works in the aggregate which are not themselves -derivative works of the Document. - -If the Cover Text requirement of section~3 is applicable to these -copies of the Document, then if the Document is less than one half of -the entire aggregate, the Document's Cover Texts may be placed on -covers that bracket the Document within the aggregate, or the -electronic equivalent of covers if the Document is in electronic form. -Otherwise they must appear on printed covers that bracket the whole -aggregate. - - -{\large\bf 8. TRANSLATION\par} -%\phantomsection -%\addcontentsline{toc}{section}{8. TRANSLATION} - - -Translation is considered a kind of modification, so you may -distribute translations of the Document under the terms of section~4. -Replacing Invariant Sections with translations requires special -permission from their copyright holders, but you may include -translations of some or all Invariant Sections in addition to the -original versions of these Invariant Sections. You may include a -translation of this License, and all the license notices in the -Document, and any Warranty Disclaimers, provided that you also include -the original English version of this License and the original versions -of those notices and disclaimers. In case of a disagreement between -the translation and the original version of this License or a notice -or disclaimer, the original version will prevail. - -If a section in the Document is Entitled ``Acknowledgements'', -``Dedications'', or ``History'', the requirement (section~4) to Preserve -its Title (section~1) will typically require changing the actual -title. - - -{\large\bf 9. TERMINATION\par} -%\phantomsection -%\addcontentsline{toc}{section}{9. TERMINATION} - - -You may not copy, modify, sublicense, or distribute the Document -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense, or distribute it is void, and -will automatically terminate your rights under this License. - -However, if you cease all violation of this License, then your license -from a particular copyright holder is reinstated (a) provisionally, -unless and until the copyright holder explicitly and finally -terminates your license, and (b) permanently, if the copyright holder -fails to notify you of the violation by some reasonable means prior to -60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, receipt of a copy of some or all of the same material does -not give you any rights to use it. - - -{\large\bf 10. FUTURE REVISIONS OF THIS LICENSE\par} -%\phantomsection -%\addcontentsline{toc}{section}{10. FUTURE REVISIONS OF THIS LICENSE} - - -The Free Software Foundation may publish new, revised versions -of the GNU Free Documentation License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. See -\texttt{http://www.gnu.org/copyleft/}. - -Each version of the License is given a distinguishing version number. -If the Document specifies that a particular numbered version of this -License ``or any later version'' applies to it, you have the option of -following the terms and conditions either of that specified version or -of any later version that has been published (not as a draft) by the -Free Software Foundation. If the Document does not specify a version -number of this License, you may choose any version ever published (not -as a draft) by the Free Software Foundation. If the Document -specifies that a proxy can decide which future versions of this -License can be used, that proxy's public statement of acceptance of a -version permanently authorizes you to choose that version for the -Document. - - -{\large\bf 11. RELICENSING\par} -%\phantomsection -%\addcontentsline{toc}{section}{11. RELICENSING} - - -``Massive Multiauthor Collaboration Site'' (or ``MMC Site'') means any -World Wide Web server that publishes copyrightable works and also -provides prominent facilities for anybody to edit those works. A -public wiki that anybody can edit is an example of such a server. A -``Massive Multiauthor Collaboration'' (or ``MMC'') contained in the -site means any set of copyrightable works thus published on the MMC -site. - -``CC-BY-SA'' means the Creative Commons Attribution-Share Alike 3.0 -license published by Creative Commons Corporation, a not-for-profit -corporation with a principal place of business in San Francisco, -California, as well as future copyleft versions of that license -published by that same organization. - -``Incorporate'' means to publish or republish a Document, in whole or -in part, as part of another Document. - -An MMC is ``eligible for relicensing'' if it is licensed under this -License, and if all works that were first published under this License -somewhere other than this MMC, and subsequently incorporated in whole -or in part into the MMC, (1) had no cover texts or invariant sections, -and (2) were thus incorporated prior to November 1, 2008. - -The operator of an MMC Site may republish an MMC contained in the site -under CC-BY-SA on the same site at any time before August 1, 2009, -provided the MMC is eligible for relicensing. - - -{\large\bf ADDENDUM: How to use this License for your documents\par} -%\phantomsection -%\addcontentsline{toc}{section}{ADDENDUM: How to use this License for your documents} - -To use this License in a document you have written, include a copy of -the License in the document and put the following copyright and -license notices just after the title page: - -\bigskip -\begin{quote} - Copyright \copyright{} YEAR YOUR NAME. - - Permission is granted to copy, distribute and/or modify this document - under the terms of the GNU Free Documentation License, Version 1.3 - or any later version published by the Free Software Foundation; - with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. - A copy of the license is included in the section entitled ``GNU - Free Documentation License''. -\end{quote} -\bigskip - -If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, -replace the ``with \dots\ Texts.''\ line with this: - -\bigskip -\begin{quote} - with the Invariant Sections being LIST THEIR TITLES, with the - Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. -\end{quote} -\bigskip - -If you have Invariant Sections without Cover Texts, or some other -combination of the three, merge those two alternatives to suit the -situation. - -If your document contains nontrivial examples of program code, we -recommend releasing these examples in parallel under your choice of -free software license, such as the GNU General Public License, -to permit their use in free software. - -\end{document} \ No newline at end of file diff --git a/Source/Doc/RomWBW User Guide/BoardNotes.ltx b/Source/Doc/RomWBW User Guide/BoardNotes.ltx deleted file mode 100644 index 6c6beaa1..00000000 --- a/Source/Doc/RomWBW User Guide/BoardNotes.ltx +++ /dev/null @@ -1,20 +0,0 @@ -\chapter{Board Notes} - -\blindtext - -\section{SBC} - -\blindtext - -\section{Zeta} - -\blindtext - -\section{N8} - -\blindtext - -\section{Mark IV} - -\blindtext - diff --git a/Source/Doc/RomWBW User Guide/Build.cmd b/Source/Doc/RomWBW User Guide/Build.cmd deleted file mode 100644 index 3218fc6c..00000000 --- a/Source/Doc/RomWBW User Guide/Build.cmd +++ /dev/null @@ -1,19 +0,0 @@ -@echo off -setlocal - -rem set MIKTEX_HOME=D:\miktex-portable\texmfs\install - -rem if "%MIKTEX_HOME%"=="" goto :eof - -rem set TEXSYSTEM=miktex -rem set MIKTEX_BINDIR=%MIKTEX_HOME%\miktex\bin -rem set MIKTEX_COMMONSTARTUPFILE=%MIKTEX_HOME%\miktex\config\miktexstartup.ini -rem set MIKTEX_GS_LIB=%MIKTEX_HOME%\ghostscript\base;%MIKTEX_HOME%\fonts -rem set MIKTEX_USERSTARTUPFILE=%MIKTEX_HOME%\miktex\config\miktexstartup.ini -rem set PATH=%MIKTEX_HOME%\miktex\bin;%PATH% - -call texify -p --clean Main.ltx - -if errorlevel 1 goto :eof - -move /Y Main.pdf "..\..\..\Doc\RomWBW User Guide.pdf" \ No newline at end of file diff --git a/Source/Doc/RomWBW User Guide/Clean.cmd b/Source/Doc/RomWBW User Guide/Clean.cmd deleted file mode 100644 index 66c5e249..00000000 --- a/Source/Doc/RomWBW User Guide/Clean.cmd +++ /dev/null @@ -1,9 +0,0 @@ -@echo off -setlocal - -if exist *.aux del *.aux -if exist *.log del *.log -if exist *.toc del *.toc -if exist *.lot del *.lot -if exist *.lof del *.lof -if exist *.pdf del *.pdf diff --git a/Source/Doc/RomWBW User Guide/Customization.ltx b/Source/Doc/RomWBW User Guide/Customization.ltx deleted file mode 100644 index a544dadf..00000000 --- a/Source/Doc/RomWBW User Guide/Customization.ltx +++ /dev/null @@ -1,12 +0,0 @@ -\chapter{Customization} - -\blindtext - -\section{Build Process} - -\blindtext - -\section{Configuration File} - -\blindtext - diff --git a/Source/Doc/RomWBW User Guide/DiskUsage.ltx b/Source/Doc/RomWBW User Guide/DiskUsage.ltx deleted file mode 100644 index 38dfe466..00000000 --- a/Source/Doc/RomWBW User Guide/DiskUsage.ltx +++ /dev/null @@ -1,15 +0,0 @@ -\chapter{Disk Usage} - -\blindtext - -\section{Preparing Disk Media} - -\blindtext - -\section{Disk Images} - -\blindtext - -\section{Placing Operating System on Disk} - -\blindtext \ No newline at end of file diff --git a/Source/Doc/RomWBW User Guide/Features.ltx b/Source/Doc/RomWBW User Guide/Features.ltx deleted file mode 100644 index 8c0a0500..00000000 --- a/Source/Doc/RomWBW User Guide/Features.ltx +++ /dev/null @@ -1,12 +0,0 @@ -\chapter{Features} - -\blindtext - -\section{Real Time Clock} - -\blindtext - -\section{Directory Time Stamping} - -\blindtext - diff --git a/Source/Doc/RomWBW User Guide/FormatSamples.ltx b/Source/Doc/RomWBW User Guide/FormatSamples.ltx deleted file mode 100644 index 4920b7df..00000000 --- a/Source/Doc/RomWBW User Guide/FormatSamples.ltx +++ /dev/null @@ -1,83 +0,0 @@ -\chapter{Formatting Samples} - -\blindtext - -An itemized list: - -\begin{itemize} -\item First list item -\item Second list item -\item Third list item -\end{itemize} - -An enumerated list: - -\begin{enumerate}[label=\textbf{\arabic*}.] -\item First list item -\item Second list item -\item Third list item -\end{enumerate} - -A description list: - -\begin{description}[style=multiline, leftmargin=1.25in, labelindent=0.25in, align=right] -\item [Ant] What is an ant? -\item [Elephant] \blindtext -\end{description} - -%Below is an example of labeling: - -%\begin{labeling}{alligator} -%\item [ant] really busy all the time -%\item [chimp] likes bananas -%\item [alligator] very dangerous animal, sharp teeth, long -%muscular tail and a bit of text that is longer than one -%line and shows the alignment of text quite nicely -%\end{labeling} - -\textrm{12345} - -Here is an output sample (Verbatim): - - -\begin{figure}[ht] -\setlength\abovecaptionskip{-0.5em} -\begin{Verbatim}[commandchars=\\\{\}, fontsize=\scriptsize, frame=single, rulecolor=\color{cyan}, numbers=left] -ROM Image: 'Output\textbackslash{}SBC_simh.rom' - -RetroBrew HBIOS v2.8.0-pre.5, 2016-06-22 - -SBC Z80 @ 20.000MHz ROM=512KB RAM=512KB -UART0: IO=0x68 8250 MODE=38400,8,N,1 -SIMRTC: Wed 2016-06-22 15:10:17 -MD: UNITS=2 ROMDISK=384KB RAMDISK=384KB -HDSK: UNITS=2 - -Below line is 80 characters: - -12345678901234567890123456789012345678901234567890123456789012345678901234567890 -\end{Verbatim} -\caption{Sample Output} -\label{fig:sampoutput} -\end{figure} - -Table \ref{tab:samptab} is an example of a floating table: - -\begin{table}[ht] -\center -\setlength{\arrayrulewidth}{2pt} -\begin{tabular}{l l} -\toprule -\bf CPU Board & \bf ROM Image File \\ -\midrule -SBC v1/v2 & SBC\_std.rom \\ -Zeta v1 & ZETA\_std.rom \\ -Zeta v2 & ZETA2\_std.rom \\ -N8 (2511) & N8\_2511.rom \\ -N8 (2312) & N8\_2312.rom \\ -Mark IV & MK4\_std.rom \\ -\bottomrule -\end{tabular} -\caption{Sample Table} -\label{tab:samptab} -\end{table} diff --git a/Source/Doc/RomWBW User Guide/GFDL.ltx b/Source/Doc/RomWBW User Guide/GFDL.ltx deleted file mode 100644 index 73bfeed4..00000000 --- a/Source/Doc/RomWBW User Guide/GFDL.ltx +++ /dev/null @@ -1,481 +0,0 @@ -\section{GNU Free Documentation License} - -\begin{center} Version 1.3, 3 November 2008 \end{center} - -\begin{multicols}{2} - -\tiny -\setlength{\parskip}{1em} -\setlength\columnseprule{.4pt} - -Copyright \copyright{} 2000, 2001, 2002, 2007, 2008 -Free Software Foundation, Inc. -\href{http://fsf.org/}{} - -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - -\textbf{0. PREAMBLE} - -The purpose of this License is to make a manual, textbook, or other -functional and useful document ``free'' in the sense of freedom: to -assure everyone the effective freedom to copy and redistribute it, -with or without modifying it, either commercially or noncommercially. -Secondarily, this License preserves for the author and publisher a way -to get credit for their work, while not being considered responsible -for modifications made by others. - -This License is a kind of ``copyleft'', which means that derivative -works of the document must themselves be free in the same sense. It -complements the GNU General Public License, which is a copyleft -license designed for free software. - -We have designed this License in order to use it for manuals for free -software, because free software needs free documentation: a free -program should come with manuals providing the same freedoms that the -software does. But this License is not limited to software manuals; -it can be used for any textual work, regardless of subject matter or -whether it is published as a printed book. We recommend this License -principally for works whose purpose is instruction or reference. - -\textbf{1. APPLICABILITY AND DEFINITIONS} - -This License applies to any manual or other work, in any medium, that -contains a notice placed by the copyright holder saying it can be -distributed under the terms of this License. Such a notice grants a -world-wide, royalty-free license, unlimited in duration, to use that -work under the conditions stated herein. The ``Document'', below, -refers to any such manual or work. Any member of the public is a -licensee, and is addressed as ``you''. You accept the license if you -copy, modify or distribute the work in a way requiring permission -under copyright law. - -A ``Modified Version'' of the Document means any work containing the -Document or a portion of it, either copied verbatim, or with -modifications and/or translated into another language. - -A ``Secondary Section'' is a named appendix or a front-matter section of -the Document that deals exclusively with the relationship of the -publishers or authors of the Document to the Document's overall subject -(or to related matters) and contains nothing that could fall directly -within that overall subject. (Thus, if the Document is in part a -textbook of mathematics, a Secondary Section may not explain any -mathematics.) The relationship could be a matter of historical -connection with the subject or with related matters, or of legal, -commercial, philosophical, ethical or political position regarding -them. - -The ``Invariant Sections'' are certain Secondary Sections whose titles -are designated, as being those of Invariant Sections, in the notice -that says that the Document is released under this License. If a -section does not fit the above definition of Secondary then it is not -allowed to be designated as Invariant. The Document may contain zero -Invariant Sections. If the Document does not identify any Invariant -Sections then there are none. - -The ``Cover Texts'' are certain short passages of text that are listed, -as Front-Cover Texts or Back-Cover Texts, in the notice that says that -the Document is released under this License. A Front-Cover Text may -be at most 5 words, and a Back-Cover Text may be at most 25 words. - -A ``Transparent'' copy of the Document means a machine-readable copy, -represented in a format whose specification is available to the -general public, that is suitable for revising the document -straightforwardly with generic text editors or (for images composed of -pixels) generic paint programs or (for drawings) some widely available -drawing editor, and that is suitable for input to text formatters or -for automatic translation to a variety of formats suitable for input -to text formatters. A copy made in an otherwise Transparent file -format whose markup, or absence of markup, has been arranged to thwart -or discourage subsequent modification by readers is not Transparent. -An image format is not Transparent if used for any substantial amount -of text. A copy that is not ``Transparent'' is called ``Opaque''. - -Examples of suitable formats for Transparent copies include plain -ASCII without markup, Texinfo input format, LaTeX input format, SGML -or XML using a publicly available DTD, and standard-conforming simple -HTML, PostScript or PDF designed for human modification. Examples of -transparent image formats include PNG, XCF and JPG. Opaque formats -include proprietary formats that can be read and edited only by -proprietary word processors, SGML or XML for which the DTD and/or -processing tools are not generally available, and the -machine-generated HTML, PostScript or PDF produced by some word -processors for output purposes only. - -The ``Title Page'' means, for a printed book, the title page itself, -plus such following pages as are needed to hold, legibly, the material -this License requires to appear in the title page. For works in -formats which do not have any title page as such, ``Title Page'' means -the text near the most prominent appearance of the work's title, -preceding the beginning of the body of the text. - -The ``publisher'' means any person or entity that distributes -copies of the Document to the public. - -A section ``Entitled XYZ'' means a named subunit of the Document whose -title either is precisely XYZ or contains XYZ in parentheses following -text that translates XYZ in another language. (Here XYZ stands for a -specific section name mentioned below, such as ``Acknowledgements'', -``Dedications'', ``Endorsements'', or ``History''.) -To ``Preserve the Title'' -of such a section when you modify the Document means that it remains a -section ``Entitled XYZ'' according to this definition. - -The Document may include Warranty Disclaimers next to the notice which -states that this License applies to the Document. These Warranty -Disclaimers are considered to be included by reference in this -License, but only as regards disclaiming warranties: any other -implication that these Warranty Disclaimers may have is void and has -no effect on the meaning of this License. - -\textbf{2. VERBATIM COPYING} - -You may copy and distribute the Document in any medium, either -commercially or noncommercially, provided that this License, the -copyright notices, and the license notice saying this License applies -to the Document are reproduced in all copies, and that you add no other -conditions whatsoever to those of this License. You may not use -technical measures to obstruct or control the reading or further -copying of the copies you make or distribute. However, you may accept -compensation in exchange for copies. If you distribute a large enough -number of copies you must also follow the conditions in section~3. - -You may also lend copies, under the same conditions stated above, and -you may publicly display copies. - -\textbf{3. COPYING IN QUANTITY} - -If you publish printed copies (or copies in media that commonly have -printed covers) of the Document, numbering more than 100, and the -Document's license notice requires Cover Texts, you must enclose the -copies in covers that carry, clearly and legibly, all these Cover -Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on -the back cover. Both covers must also clearly and legibly identify -you as the publisher of these copies. The front cover must present -the full title with all words of the title equally prominent and -visible. You may add other material on the covers in addition. -Copying with changes limited to the covers, as long as they preserve -the title of the Document and satisfy these conditions, can be treated -as verbatim copying in other respects. - -If the required texts for either cover are too voluminous to fit -legibly, you should put the first ones listed (as many as fit -reasonably) on the actual cover, and continue the rest onto adjacent -pages. - -If you publish or distribute Opaque copies of the Document numbering -more than 100, you must either include a machine-readable Transparent -copy along with each Opaque copy, or state in or with each Opaque copy -a computer-network location from which the general network-using -public has access to download using public-standard network protocols -a complete Transparent copy of the Document, free of added material. -If you use the latter option, you must take reasonably prudent steps, -when you begin distribution of Opaque copies in quantity, to ensure -that this Transparent copy will remain thus accessible at the stated -location until at least one year after the last time you distribute an -Opaque copy (directly or through your agents or retailers) of that -edition to the public. - -It is requested, but not required, that you contact the authors of the -Document well before redistributing any large number of copies, to give -them a chance to provide you with an updated version of the Document. - -\textbf{4. MODIFICATIONS} - -You may copy and distribute a Modified Version of the Document under -the conditions of sections 2 and 3 above, provided that you release -the Modified Version under precisely this License, with the Modified -Version filling the role of the Document, thus licensing distribution -and modification of the Modified Version to whoever possesses a copy -of it. In addition, you must do these things in the Modified Version: - -\begin{itemize}[leftmargin=*] - -\item[A.] Use in the Title Page (and on the covers, if any) a title distinct -from that of the Document, and from those of previous versions -(which should, if there were any, be listed in the History section -of the Document). You may use the same title as a previous version -if the original publisher of that version gives permission. - -\item[B.] List on the Title Page, as authors, one or more persons or entities -responsible for authorship of the modifications in the Modified -Version, together with at least five of the principal authors of the -Document (all of its principal authors, if it has fewer than five), -unless they release you from this requirement. - -\item[C.] State on the Title page the name of the publisher of the -Modified Version, as the publisher. - -\item[D.] Preserve all the copyright notices of the Document. - -\item[E.] Add an appropriate copyright notice for your modifications -adjacent to the other copyright notices. - -\item[F.] Include, immediately after the copyright notices, a license notice -giving the public permission to use the Modified Version under the -terms of this License, in the form shown in the Addendum below. - -\item[G.] Preserve in that license notice the full lists of Invariant Sections -and required Cover Texts given in the Document's license notice. - -\item[H.] Include an unaltered copy of this License. - -\item[I.] Preserve the section Entitled ``History'', Preserve its Title, and add -to it an item stating at least the title, year, new authors, and -publisher of the Modified Version as given on the Title Page. If -there is no section Entitled ``History'' in the Document, create one -stating the title, year, authors, and publisher of the Document as -given on its Title Page, then add an item describing the Modified -Version as stated in the previous sentence. - -\item[J.] Preserve the network location, if any, given in the Document for -public access to a Transparent copy of the Document, and likewise -the network locations given in the Document for previous versions -it was based on. These may be placed in the ``History'' section. -You may omit a network location for a work that was published at -least four years before the Document itself, or if the original -publisher of the version it refers to gives permission. - -\item[K.] For any section Entitled ``Acknowledgements'' or ``Dedications'', -Preserve the Title of the section, and preserve in the section all -the substance and tone of each of the contributor acknowledgements -and/or dedications given therein. - -\item[L.] Preserve all the Invariant Sections of the Document, -unaltered in their text and in their titles. Section numbers -or the equivalent are not considered part of the section titles. - -\item[M.] Delete any section Entitled ``Endorsements''. Such a section -may not be included in the Modified Version. - -\item[N.] Do not retitle any existing section to be Entitled ``Endorsements'' -or to conflict in title with any Invariant Section. - -\item[O.] Preserve any Warranty Disclaimers. - -\end{itemize} - -If the Modified Version includes new front-matter sections or -appendices that qualify as Secondary Sections and contain no material -copied from the Document, you may at your option designate some or all -of these sections as invariant. To do this, add their titles to the -list of Invariant Sections in the Modified Version's license notice. -These titles must be distinct from any other section titles. - -You may add a section Entitled ``Endorsements'', provided it contains -nothing but endorsements of your Modified Version by various -parties---for example, statements of peer review or that the text has -been approved by an organization as the authoritative definition of a -standard. - -You may add a passage of up to five words as a Front-Cover Text, and a -passage of up to 25 words as a Back-Cover Text, to the end of the list -of Cover Texts in the Modified Version. Only one passage of -Front-Cover Text and one of Back-Cover Text may be added by (or -through arrangements made by) any one entity. If the Document already -includes a cover text for the same cover, previously added by you or -by arrangement made by the same entity you are acting on behalf of, -you may not add another; but you may replace the old one, on explicit -permission from the previous publisher that added the old one. - -The author(s) and publisher(s) of the Document do not by this License -give permission to use their names for publicity for or to assert or -imply endorsement of any Modified Version. - -\textbf{5. COMBINING DOCUMENTS} - -You may combine the Document with other documents released under this -License, under the terms defined in section~4 above for modified -versions, provided that you include in the combination all of the -Invariant Sections of all of the original documents, unmodified, and -list them all as Invariant Sections of your combined work in its -license notice, and that you preserve all their Warranty Disclaimers. - -The combined work need only contain one copy of this License, and -multiple identical Invariant Sections may be replaced with a single -copy. If there are multiple Invariant Sections with the same name but -different contents, make the title of each such section unique by -adding at the end of it, in parentheses, the name of the original -author or publisher of that section if known, or else a unique number. -Make the same adjustment to the section titles in the list of -Invariant Sections in the license notice of the combined work. - -In the combination, you must combine any sections Entitled ``History'' -in the various original documents, forming one section Entitled -``History''; likewise combine any sections Entitled ``Acknowledgements'', -and any sections Entitled ``Dedications''. You must delete all sections -Entitled ``Endorsements''. - -\textbf{6. COLLECTIONS OF DOCUMENTS} - -You may make a collection consisting of the Document and other documents -released under this License, and replace the individual copies of this -License in the various documents with a single copy that is included in -the collection, provided that you follow the rules of this License for -verbatim copying of each of the documents in all other respects. - -You may extract a single document from such a collection, and distribute -it individually under this License, provided you insert a copy of this -License into the extracted document, and follow this License in all -other respects regarding verbatim copying of that document. - - -\textbf{7. AGGREGATION WITH INDEPENDENT WORKS} - -A compilation of the Document or its derivatives with other separate -and independent documents or works, in or on a volume of a storage or -distribution medium, is called an ``aggregate'' if the copyright -resulting from the compilation is not used to limit the legal rights -of the compilation's users beyond what the individual works permit. -When the Document is included in an aggregate, this License does not -apply to the other works in the aggregate which are not themselves -derivative works of the Document. - -If the Cover Text requirement of section~3 is applicable to these -copies of the Document, then if the Document is less than one half of -the entire aggregate, the Document's Cover Texts may be placed on -covers that bracket the Document within the aggregate, or the -electronic equivalent of covers if the Document is in electronic form. -Otherwise they must appear on printed covers that bracket the whole -aggregate. - -\textbf{8. TRANSLATION} - -Translation is considered a kind of modification, so you may -distribute translations of the Document under the terms of section~4. -Replacing Invariant Sections with translations requires special -permission from their copyright holders, but you may include -translations of some or all Invariant Sections in addition to the -original versions of these Invariant Sections. You may include a -translation of this License, and all the license notices in the -Document, and any Warranty Disclaimers, provided that you also include -the original English version of this License and the original versions -of those notices and disclaimers. In case of a disagreement between -the translation and the original version of this License or a notice -or disclaimer, the original version will prevail. - -If a section in the Document is Entitled ``Acknowledgements'', -``Dedications'', or ``History'', the requirement (section~4) to Preserve -its Title (section~1) will typically require changing the actual -title. - -\textbf{9. TERMINATION} - -You may not copy, modify, sublicense, or distribute the Document -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense, or distribute it is void, and -will automatically terminate your rights under this License. - -However, if you cease all violation of this License, then your license -from a particular copyright holder is reinstated (a) provisionally, -unless and until the copyright holder explicitly and finally -terminates your license, and (b) permanently, if the copyright holder -fails to notify you of the violation by some reasonable means prior to -60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, receipt of a copy of some or all of the same material does -not give you any rights to use it. - -\textbf{10. FUTURE REVISIONS OF THIS LICENSE} - -The Free Software Foundation may publish new, revised versions -of the GNU Free Documentation License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. See -\href{http://www.gnu.org/copyleft/}{http://www.gnu.org/copyleft/}. - -Each version of the License is given a distinguishing version number. -If the Document specifies that a particular numbered version of this -License ``or any later version'' applies to it, you have the option of -following the terms and conditions either of that specified version or -of any later version that has been published (not as a draft) by the -Free Software Foundation. If the Document does not specify a version -number of this License, you may choose any version ever published (not -as a draft) by the Free Software Foundation. If the Document -specifies that a proxy can decide which future versions of this -License can be used, that proxy's public statement of acceptance of a -version permanently authorizes you to choose that version for the -Document. - -\textbf{11. RELICENSING} - -``Massive Multiauthor Collaboration Site'' (or ``MMC Site'') means any -World Wide Web server that publishes copyrightable works and also -provides prominent facilities for anybody to edit those works. A -public wiki that anybody can edit is an example of such a server. A -``Massive Multiauthor Collaboration'' (or ``MMC'') contained in the -site means any set of copyrightable works thus published on the MMC -site. - -``CC-BY-SA'' means the Creative Commons Attribution-Share Alike 3.0 -license published by Creative Commons Corporation, a not-for-profit -corporation with a principal place of business in San Francisco, -California, as well as future copyleft versions of that license -published by that same organization. - -``Incorporate'' means to publish or republish a Document, in whole or -in part, as part of another Document. - -An MMC is ``eligible for relicensing'' if it is licensed under this -License, and if all works that were first published under this License -somewhere other than this MMC, and subsequently incorporated in whole -or in part into the MMC, (1) had no cover texts or invariant sections, -and (2) were thus incorporated prior to November 1, 2008. - -The operator of an MMC Site may republish an MMC contained in the site -under CC-BY-SA on the same site at any time before August 1, 2009, -provided the MMC is eligible for relicensing. - -\textbf{ADDENDUM: How to use this License for your documents} - -To use this License in a document you have written, include a copy of -the License in the document and put the following copyright and -license notices just after the title page: - -\begingroup -\leftskip=2em -\slshape - -Copyright \copyright{} YEAR YOUR NAME. - -Permission is granted to copy, distribute and/or modify this document -under the terms of the GNU Free Documentation License, Version 1.3 -or any later version published by the Free Software Foundation; -with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. -A copy of the license is included in the section entitled ``GNU -Free Documentation License''. - -\endgroup - -If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, -replace the ``with \dots\ Texts.''\ line with this: - -\begingroup -\leftskip=2em -\slshape - -with the Invariant Sections being LIST THEIR TITLES, with the -Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. - -\endgroup - -If you have Invariant Sections without Cover Texts, or some other -combination of the three, merge those two alternatives to suit the -situation. - -If your document contains nontrivial examples of program code, we -recommend releasing these examples in parallel under your choice of -free software license, such as the GNU General Public License, -to permit their use in free software. - -\end{multicols} \ No newline at end of file diff --git a/Source/Doc/RomWBW User Guide/GPL.ltx b/Source/Doc/RomWBW User Guide/GPL.ltx deleted file mode 100644 index b95a8596..00000000 --- a/Source/Doc/RomWBW User Guide/GPL.ltx +++ /dev/null @@ -1,733 +0,0 @@ -\section{GNU General Public License} - -\begin{center} Version 3, 29 June 2007 \end{center} - -\begin{multicols}{2} - -\tiny -\setlength{\parskip}{1em} -\setlength\columnseprule{.4pt} - -Copyright \copyright{} 2007 Free Software Foundation, Inc. -\href{http://fsf.org/}{http://fsf.org/} - -Everyone is permitted to copy and distribute verbatim copies of this -license document, but changing it is not allowed. - -\bigskip -{\scriptsize \textbf{Preamble}} - -The GNU General Public License is a free, copyleft license for -software and other kinds of works. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom -to share and change all versions of a program--to make sure it -remains free software for all its users. We, the Free Software -Foundation, use the GNU General Public License for most of our -software; it applies also to any other work released this way by its -authors. You can apply it to your programs, too. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge -for them if you wish), that you receive source code or can get it if -you want it, that you can change the software or use pieces of it in -new free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you -have certain responsibilities if you distribute copies of the -software, or if you modify it: responsibilities to respect the -freedom of others. - -For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, -receive or can get the source code. And you must show them these -terms so they know their rights. - -Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - -Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the -manufacturer can do so. This is fundamentally incompatible with the -aim of protecting users' freedom to change the software. The -systematic pattern of such abuse occurs in the area of products for -individuals to use, which is precisely where it is most unacceptable. -Therefore, we have designed this version of the GPL to prohibit the -practice for those products. If such problems arise substantially in -other domains, we stand ready to extend this provision to those -domains in future versions of the GPL, as needed to protect the -freedom of users. - -Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish -to avoid the special danger that patents applied to a free program -could make it effectively proprietary. To prevent this, the GPL -assures that patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and -modification follow. - -\bigskip -{\scriptsize \textbf{TERMS AND CONDITIONS}} - -\textbf{0. Definitions.} - -``This License'' refers to version 3 of the GNU General Public License. - -``Copyright'' also means copyright-like laws that apply to other -kinds of works, such as semiconductor masks. - -``The Program'' refers to any copyrightable work licensed under this -License. Each licensee is addressed as ``you''. ``Licensees'' and -``recipients'' may be individuals or organizations. - -To ``modify'' a work means to copy from or adapt all or part of the -work in a fashion requiring copyright permission, other than the -making of an exact copy. The resulting work is called a ``modified -version'' of the earlier work or a work ``based on'' the earlier work. - -A ``covered work'' means either the unmodified Program or a work -based on the Program. - -To ``propagate'' a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To ``convey'' a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user -through a computer network, with no transfer of a copy, is not -conveying. - -An interactive user interface displays ``Appropriate Legal Notices'' -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -\textbf{1. Source Code.} - -The ``source code'' for a work means the preferred form of the work -for making modifications to it. ``Object code'' means any non-source -form of a work. - -A ``Standard Interface'' means an interface that either is an -official standard defined by a recognized standards body, or, in the -case of interfaces specified for a particular programming language, -one that is widely used among developers working in that language. - -The ``System Libraries'' of an executable work include anything, -other than the work as a whole, that (a) is included in the normal -form of packaging a Major Component, but which is not part of that -Major Component, and (b) serves only to enable use of the work with -that Major Component, or to implement a Standard Interface for which -an implementation is available to the public in source code form. A -``Major Component'', in this context, means a major essential -component (kernel, window system, and so on) of the specific -operating system (if any) on which the executable work runs, or a -compiler used to produce the work, or an object code interpreter used -to run it. - -The ``Corresponding Source'' for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts -to control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available -free programs which are used unmodified in performing those -activities but which are not part of the work. For example, -Corresponding Source includes interface definition files associated -with source files for the work, and the source code for shared -libraries and dynamically linked subprograms that the work is -specifically designed to require, such as by intimate data -communication or control flow between those subprograms and other -parts of the work. - -The Corresponding Source need not include anything that users can -regenerate automatically from other parts of the Corresponding Source. - -The Corresponding Source for a work in source code form is that same -work. - -\textbf{2. Basic Permissions.} - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not convey, -without conditions so long as your license otherwise remains in -force. You may convey covered works to others for the sole purpose of -having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply -with the terms of this License in conveying all material for which -you do not control copyright. Those thus making or running the -covered works for you must do so exclusively on your behalf, under -your direction and control, on terms that prohibit them from making -any copies of your copyrighted material outside their relationship -with you. - -Conveying under any other circumstances is permitted solely under the -conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - -\textbf{3. Protecting Users' Legal Rights From Anti-Circumvention Law.} - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such -circumvention is effected by exercising rights under this License -with respect to the covered work, and you disclaim any intention to -limit operation or modification of the work as a means of enforcing, -against the work's users, your or third parties' legal rights to -forbid circumvention of technological measures. - -\textbf{4. Conveying Verbatim Copies.} - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the -code; keep intact all notices of the absence of any warranty; and -give all recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -\textbf{5. Conveying Modified Source Versions.} - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these -conditions: - -\begin{itemize}[leftmargin=*] - -\item[a)] The work must carry prominent notices stating that you -modified it, and giving a relevant date. - -\item[b)] The work must carry prominent notices stating that it is -released under this License and any conditions added under section 7. -This requirement modifies the requirement in section 4 to ``keep -intact all notices''. - -\item[c)] You must license the entire work, as a whole, under this -License to anyone who comes into possession of a copy. This License -will therefore apply, along with any applicable section 7 additional -terms, to the whole of the work, and all its parts, regardless of how -they are packaged. This License gives no permission to license the -work in any other way, but it does not invalidate such permission if -you have separately received it. - -\item[d)] If the work has interactive user interfaces, each must -display Appropriate Legal Notices; however, if the Program has -interactive interfaces that do not display Appropriate Legal Notices, -your work need not make them do so. - -\end{itemize} - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -``aggregate'' if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -\textbf{6. Conveying Non-Source Forms.} - -You may convey a covered work in object code form under the terms of -sections 4 and 5, provided that you also convey the machine-readable -Corresponding Source under the terms of this License, in one of these -ways: - -\begin{itemize}[leftmargin=*] - -\item[a)] Convey the object code in, or embodied in, a physical -product (including a physical distribution medium), accompanied by -the Corresponding Source fixed on a durable physical medium -customarily used for software interchange. - -\item[b)] Convey the object code in, or embodied in, a physical -product (including a physical distribution medium), accompanied by a -written offer, valid for at least three years and valid for as long -as you offer spare parts or customer support for that product model, -to give anyone who possesses the object code either (1) a copy of the -Corresponding Source for all the software in the product that is -covered by this License, on a durable physical medium customarily -used for software interchange, for a price no more than your -reasonable cost of physically performing this conveying of source, or -(2) access to copy the Corresponding Source from a network server at -no charge. - -\item[c)] Convey individual copies of the object code with a copy of -the written offer to provide the Corresponding Source. This -alternative is allowed only occasionally and noncommercially, and -only if you received the object code with such an offer, in accord -with subsection 6b. - -\item[d)] Convey the object code by offering access from a designated -place (gratis or for a charge), and offer equivalent access to the -Corresponding Source in the same way through the same place at no -further charge. You need not require recipients to copy the -Corresponding Source along with the object code. If the place to copy -the object code is a network server, the Corresponding Source may be -on a different server (operated by you or a third party) that -supports equivalent copying facilities, provided you maintain clear -directions next to the object code saying where to find the -Corresponding Source. Regardless of what server hosts the -Corresponding Source, you remain obligated to ensure that it is -available for as long as needed to satisfy these requirements. - -\item[e)] Convey the object code using peer-to-peer transmission, -provided you inform other peers where the object code and -Corresponding Source of the work are being offered to the general -public at no charge under subsection 6d. - -\end{itemize} - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A ``User Product'' is either (1) a ``consumer product'', which means -any tangible personal property which is normally used for personal, -family, or household purposes, or (2) anything designed or sold for -incorporation into a dwelling. In determining whether a product is a -consumer product, doubtful cases shall be resolved in favor of -coverage. For a particular product received by a particular user, -``normally used'' refers to a typical or common use of that class of -product, regardless of the status of the particular user or of the -way in which the particular user actually uses, or expects or is -expected to use, the product. A product is a consumer product -regardless of whether the product has substantial commercial, -industrial or non-consumer uses, unless such uses represent the only -significant mode of use of the product. - -``Installation Information'' for a User Product means any methods, -procedures, authorization keys, or other information required to -install and execute modified versions of a covered work in that User -Product from a modified version of its Corresponding Source. The -information must suffice to ensure that the continued functioning of -the modified object code is in no case prevented or interfered with -solely because modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include -a requirement to continue to provide support service, warranty, or -updates for a work that has been modified or installed by the -recipient, or for the User Product in which it has been modified or -installed. Access to a network may be denied when the modification -itself materially and adversely affects the operation of the network -or violates the rules and protocols for communication across the -network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -\textbf{7. Additional Terms.} - -``Additional permissions'' are terms that supplement the terms of -this License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program -shall be treated as though they were included in this License, to the -extent that they are valid under applicable law. If additional -permissions apply only to part of the Program, that part may be used -separately under those permissions, but the entire Program remains -governed by this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright -holders of that material) supplement the terms of this License with -terms: - -\begin{itemize}[leftmargin=*] - -\item[a)] Disclaiming warranty or limiting liability differently from -the terms of sections 15 and 16 of this License; or - -\item[b)] Requiring preservation of specified reasonable legal -notices or author attributions in that material or in the Appropriate -Legal Notices displayed by works containing it; or - -\item[c)] Prohibiting misrepresentation of the origin of that -material, or requiring that modified versions of such material be -marked in reasonable ways as different from the original version; or - -\item[d)] Limiting the use for publicity purposes of names of -licensors or authors of the material; or - -\item[e)] Declining to grant rights under trademark law for use of -some trade names, trademarks, or service marks; or - -\item[f)] Requiring indemnification of licensors and authors of that -material by anyone who conveys the material (or modified versions of -it) with contractual assumptions of liability to the recipient, for -any liability that these contractual assumptions directly impose on -those licensors and authors. - -\end{itemize} - -All other non-permissive additional terms are considered ``further -restrictions'' within the meaning of section 10. If the Program as -you received it, or any part of it, contains a notice stating that it -is governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; the -above requirements apply either way. - -\textbf{8. Termination.} - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the -copyright holder fails to notify you of the violation by some -reasonable means prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -\textbf{9. Acceptance Not Required for Having Copies.} - -You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -\textbf{10. Automatic Licensing of Downstream Recipients.} - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An ``entity transaction'' is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate -litigation (including a cross-claim or counterclaim in a lawsuit) -alleging that any patent claim is infringed by making, using, -selling, offering for sale, or importing the Program or any portion -of it. - -\textbf{11. Patents.} - -A ``contributor'' is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's ``contributor version''. - -A contributor's ``essential patent claims'' are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor -version, but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, ``control'' includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a ``patent license'' is any -express agreement or commitment, however denominated, not to enforce -a patent (such as an express permission to practice a patent or -covenant not to sue for patent infringement). To ``grant'' such a -patent license to a party means to make such an agreement or -commitment not to enforce a patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through -a publicly available network server or other readily accessible -means, then you must either (1) cause the Corresponding Source to be -so available, or (2) arrange to deprive yourself of the benefit of -the patent license for this particular work, or (3) arrange, in a -manner consistent with the requirements of this License, to extend -the patent license to downstream recipients. ``Knowingly relying'' -means you have actual knowledge that, but for the patent license, -your conveying the covered work in a country, or your recipient's use -of the covered work in a country, would infringe one or more -identifiable patents in that country that you have reason to believe -are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent -license you grant is automatically extended to all recipients of the -covered work and works based on it. - -A patent license is ``discriminatory'' if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make -payment to the third party based on the extent of your activity of -conveying the work, and under which the third party grants, to any of -the parties who would receive the covered work from you, a -discriminatory patent license (a) in connection with copies of the -covered work conveyed by you (or copies made from those copies), or -(b) primarily for and in connection with specific products or -compilations that contain the covered work, unless you entered into -that arrangement, or that patent license was granted, prior to 28 -March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -\textbf{12. No Surrender of Others' Freedom.} - -If conditions are imposed on you (whether by court order, agreement -or otherwise) that contradict the conditions of this License, they do -not excuse you from the conditions of this License. If you cannot -convey a covered work so as to satisfy simultaneously your -obligations under this License and any other pertinent obligations, -then as a consequence you may not convey it at all. For example, if -you agree to terms that obligate you to collect a royalty for further -conveying from those to whom you convey the Program, the only way you -could satisfy both those terms and this License would be to refrain -entirely from conveying the Program. - -\textbf{13. Use with the GNU Affero General Public License.} - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a -single combined work, and to convey the resulting work. The terms of -this License will continue to apply to the part which is the covered -work, but the special requirements of the GNU Affero General Public -License, section 13, concerning interaction through a network will -apply to the combination as such. - -\textbf{14. Revised Versions of this License.} - -The Free Software Foundation may publish revised and/or new versions -of the GNU General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies that a certain numbered version of the GNU General Public -License ``or any later version'' applies to it, you have the option -of following the terms and conditions either of that numbered version -or of any later version published by the Free Software Foundation. If -the Program does not specify a version number of the GNU General -Public License, you may choose any version ever published by the Free -Software Foundation. - -If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes -you to choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -\textbf{15. Disclaimer of Warranty.} - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM ``AS IS'' WITHOUT -WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND -PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE -DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR -CORRECTION. - -\textbf{16. Limitation of Liability.} - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR -CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES -ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT -NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR -LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM -TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER -PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -\textbf{17. Interpretation of Sections 15 and 16.} - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - -END OF TERMS AND CONDITIONS - -\bigskip -{\scriptsize \textbf{How to Apply These Terms to Your New Programs}} - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make -it free software which everyone can redistribute and change under -these terms. - -To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the ``copyright'' line and a pointer to where the full notice is found. - -\begingroup -\leftskip=2em -\slshape - -\\ -Copyright (C) - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see -\href{http://www.gnu.org/licenses/}{http://www.gnu.org/licenses/}. - -\endgroup - -Also add information on how to contact you by electronic and paper -mail. - -If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - -\begingroup -\leftskip=2em -\slshape - - Copyright (C) \\ -This program comes with ABSOLUTELY NO WARRANTY; for details type -`show w'. -This is free software, and you are welcome to redistribute it -under certain conditions; type `show c' for details. - -\endgroup - -The hypothetical commands `show w' and `show c' should show the -appropriate parts of the General Public License. Of course, your -program's commands might be different; for a GUI interface, you would -use an ``about box''. - -You should also get your employer (if you work as a programmer) or -school, if any, to sign a ``copyright disclaimer'' for the program, -if necessary. For more information on this, and how to apply and -follow the GNU GPL, see -\href{http://www.gnu.org/licenses/}{http://www.gnu.org/licenses/}. - -The GNU General Public License does not permit incorporating your -program into proprietary programs. If your program is a subroutine -library, you may consider it more useful to permit linking -proprietary applications with the library. If this is what you want -to do, use the GNU Lesser General Public License instead of this -License. But first, please read -\href{http://www.gnu.org/philosophy/why-not-lgpl.html}{http://www.gnu.o -rg/philosophy/why-not-lgpl.html}. - -\end{multicols} \ No newline at end of file diff --git a/Source/Doc/RomWBW User Guide/Generate.cmd b/Source/Doc/RomWBW User Guide/Generate.cmd deleted file mode 100644 index f43109b8..00000000 --- a/Source/Doc/RomWBW User Guide/Generate.cmd +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -setlocal - -texify -p -V --run-viewer Main.ltx diff --git a/Source/Doc/RomWBW User Guide/GettingStarted.ltx b/Source/Doc/RomWBW User Guide/GettingStarted.ltx deleted file mode 100644 index ccfba708..00000000 --- a/Source/Doc/RomWBW User Guide/GettingStarted.ltx +++ /dev/null @@ -1,251 +0,0 @@ -\chapter{Getting Started} - -Because of the wide variety of hardware combinations, there is no "one -case fits all" approach to getting started. The good news is that RomWBW -operates very consistently regardless of the specific hardware. The -operating systems, applications, and storage formats are all common. -However, building and testing your hardware is entirely outside the scope -of this document. The RetroBrew Computing Forum -(https://www.retrobrewcomputers.org/forum) is probably the best place to -get advice if you get stuck on hardware issues. - -\section{SIMH Simulator} - -It is not necessary, but I highly recommend running RomWBW under the SIMH -Simulator as a first step. This requires no hardware and will allow you -to see how it should look when you use it on real hardware. Since the -SIMH software is included in the distribution package, you can start it -with a single command. Using a command prompt window, navigate to the -high level directory of the distribution. Enter the command "sim" and -the simulator should start up. The first few lines of output should -look similar to Figure \ref{fig:simhboot}. You may see some benign -warning messages that can be ignored. - -\begin{figure}[ht] -\setlength\abovecaptionskip{-0.5em} -\begin{Verbatim}[commandchars=\\\{\}, fontsize=\scriptsize, frame=single, rulecolor=\color{cyan}, numbers=left] -ROM Image: 'Output\textbackslash{}SBC_simh.rom' - -RetroBrew HBIOS v2.8.0-pre.5, 2016-06-22 - -SBC Z80 @ 20.000MHz ROM=512KB RAM=512KB -UART0: IO=0x68 8250 MODE=38400,8,N,1 -SIMRTC: Wed 2016-06-22 15:10:17 -MD: UNITS=2 ROMDISK=384KB RAMDISK=384KB -HDSK: UNITS=2 - -\textsl{} -\end{Verbatim} -\caption{SIMH Boot Example} -\label{fig:simhboot} -\end{figure} - -\section{Board Setup} - -In all cases, you will want to start with a Z80/Z180 host board. Any of -the boards listed in System Requirements will work fine. I strongly -recommend that you initially work on getting just the single host board -running by itself -- don't even plug it into an ECB backplane. - -Given a host board that is assembled and passes any hardware checks -recommended by the boards designer, you need to make sure the board is -configured for RomWBW. Refer to the entry in Appendix A for your board -and confirm that all switches and jumpers on the board are set as -required by RomWBW. - -Your initial goal is to locate and program a ROM image for your host -board. The ROM images are located in the Output directory. You are -looking for the files that end in ".rom". Don't worry about all of the -other variations at this point. Refer to Table \ref{tab:basicromfiles} to -determine the ROM image that you want. - -\begin{table}[ht] -\center -%\renewcommand{\arraystretch}{1.5} -\setlength{\arrayrulewidth}{2pt} -\begin{tabular}{l l} -\toprule -\bf CPU Board & \bf ROM Image File \\ -\midrule -SBC v1/v2 & SBC\_std.rom \\ -Zeta v1 & ZETA\_std.rom \\ -Zeta v2 & ZETA2\_std.rom \\ -N8 (2511) & N8\_2511.rom \\ -N8 (2312) & N8\_2312.rom \\ -Mark IV & MK4\_std.rom \\ -\bottomrule -\end{tabular} -\caption{Basic ROM Files} -\label{tab:basicromfiles} -\end{table} - -Locate the appropriate ROM image file in the Output directory based on -Table \ref{tab:basicromfiles}. You should see that the file is exactly 512KB in size. -As indicated above in System Requirements, your system should have a ROM -capacity of 512KB or greater. You need to program the file to your ROM -using whatever tool you have. Programming a ROM chip is beyond the scope -of this document, but any feel free to ask for help at the RetroBrew -Computing Forum. The ROM image files are pure binary and should be -programmed into the ROM chip starting at address 0H thru address 7FFFH (8000H bytes). -Insert the programmed ROM chip in your system. - -Initially, you will need two external connections to your board. Power -and serial port. All of the CPU boards provide an onboard power -connection. Refer to the board designer's notes on the RBC Wiki for more -information on the power connection your board requires. - -Finally, you must connect the primary serial port of your host board to a -terminal using 38,400 baud, 8 data bits, 1 stop bit, and no parity. You can -use either a dedicated terminal or use terminal emulation software on -your PC\footnote{Under Windows, Tera Term is a good choice for terminal -emulation.}. -When connecting to a standard PC serial port, a null modem cable is required. -There is a good document on the Wiki that explains cabling of serial ports at -http://???????. \todo{Need to restore serial port cabling document on Wiki!} - -\section{Startup} - -System startup (booting) is accomplished simply by applying power. In some cases, -it may be necessary to press the reset button after applying power to get a -successful startup. - -If everything is working properly, you should see something like Figure \ref{fig:boot} -on your terminal screen. Your output will vary somewhat depending on your -specific hardware. The example shown comes from a Mark IV. - -\begin{figure}[ht] -\setlength\abovecaptionskip{-0.5em} -\begin{Verbatim}[commandchars=\\\{\}, fontsize=\scriptsize, frame=single, rulecolor=\color{cyan}, numbers=left] -RetroBrew HBIOS v2.8.0-pre.5, 2016-06-03 - -MARK IV Z180 @ 18.432MHz ROM=512KB RAM=512KB -ASCI0: IO=0x46,48 MODE=38400,8,N,1 -ASCI1: IO=0x47,49 MODE=38400,8,N,1 -DSRTC: MODE=STD Wed 2016-06-22 15:03:06 -MD: UNITS=2 ROMDISK=384KB RAMDISK=384KB -IDE: MODE=MK4 IO=0x80 UNITS=2 -IDE0: NO MEDIA -IDE1: NO MEDIA -SD: MODE=MK4 FAST OPR=0x89 CNTR=0x4A TRDR=0x4B UNITS=1 -SD0: NO MEDIA - -Unit Device Type Capacity/Mode ----------- ---------- ---------------- -------------------- -Disk 0 MD1: RAM Disk 384KB,LBA -Disk 1 MD0: ROM Disk 384KB,LBA -Disk 2 IDE0: Hard Disk -- -Disk 3 IDE1: Hard Disk -- -Disk 4 SD0: SD Card -- -Serial 0 ASCI0: RS-232 38400,8,N,1 -Serial 1 ASCI1: RS-232 38400,8,N,1 - -MARK IV Z180 Boot Loader - -Boot: (C)PM, (Z)System, (M)onitor, - (L)ist disks, or Disk Unit # ===> -\end{Verbatim} -\caption{Typical Boot Display} -\label{fig:boot} -\end{figure} - -If you see output on your terminal screen, but it is garbled/unreadable, then -check the serial port configuration settings on your terminal or terminal -emulation software. - -If you do not see any output of any kind on your terminal screen, the following -general areas should be checked: - -\begin{itemize} -\item Confirm power is being applied to the board and the the voltage is -in an acceptable range. -\item Confirm the ROM is programmed accurately by placing it back in the -programmer and using the verify function. -\item Verify the serial connection. When connecting to a PC, make sure -you have a null modem cable or adapter. -\item Review your board's construction carefully for chip orientation, bent -pins, missing or bridged solder joints, etc. -\end{itemize} - -You will find that the RetroBrew Computing Group is very helpful if you get -stuck. The best way to request assistance is to post a message on the -Forum. - -\section{Boot Display} - -As illustrated in Figure \ref{fig:boot}, RomWBW displays a lot of information -about the system and it's configuration. There are 4 basic sections to -the boot display. - -Line 1 is a banner that identifies the BIOS portion of the ROM including -version and build date. - -Lines 3-12 display the hardware inventory of the system as understood by -the ROM. Note that some of this information is \emph{not} discovered dynamically -- -it is built into the ROM. So, do not be alarmed if some parts of this -display do not match your hardware. For example, the RAM and ROM size -are configured into the ROM itself. You can refer to Appendix A for -more information on how to read the specific lines. - -Lines 14-22 contain a table that summarizes the devices in the system. This -information is used when the operating system is loaded/configured to -assign OS devices to system devices. - -Lines 24-27 is the display of the boot loader menu and prompt. The boot -loader allows you to choose the operating mode you want to initiate. These -options will be described the next section. - -\section{Loader} - -At the conclusion of a successful system startup, the loader menu/prompt will -be displayed on the console. The function of the loader is to load an -operating system or system monitor. - -\subsection{Monitor} - -Pressing 'M' at the boot loader prompt will launch a basic system monitor. -The system monitor provides very basic functions that are primarily useful -for testing components of your system. These functions include displaying -and modifying memory, reading and writing to I/O ports, etc. - -Refer to ??? for monitor operation. - -\subsection{CP/M} - -Pressing 'C' at the boot loader prompt will launch Digital Research CP/M-80 -Version 2.2. A complete copy of the CP/M operating system is imbedded in -the ROM and will be loaded directly from there, so no disk access is required. - -Initially, drive A will be a RAM drive (initialized with no files). Drive B -will be a ROM drive. The standard CP/M distribtion files are included on the -ROM drive (e.g., ASM, PIP, STAT). Drive B will initially be the logged drive. -At this point, you can execute the programs on drive B. Remember that drive B -is a ROM drive, so any attempt to write to that drive will result in an error. - -Refer to Chapter ?? for more information on using CP/M 2.2. - -\subsection{Z-System} - -Pressing 'Z' at the boot loader prompt will launch Z-System, a CP/M 2.2 -compatible operating system with many enhancements. As with CP/M, this -operating system will be loaded directly from ROM. - -The drive configuration for Z-System is identical to CP/M. - -Refer to Chapter ?? for more information on using Z-System. - -\subsection{Disk Boot} - -The boot loader also supports loading an operating system from a disk -device. In this case, you must press the number key corresponding to -the disk device containing the operating system to be loaded. The -disk device numbers are the ones listed in the device summary table. - -In order to boot from a disk device, it must be properly initialized -using the SYSCOPY application or equivalent. Attempting to boot a -disk that has no operating system will result in an error and the -boot loader prompt will be redisplayed. - -You can press 'L' at the boot loader prompt to display a list of -the disk devices available. The existence of a disk in this list -does \emph{not} mean that it has been initialized with an -operating system. diff --git a/Source/Doc/RomWBW User Guide/Licensing.ltx b/Source/Doc/RomWBW User Guide/Licensing.ltx deleted file mode 100644 index 434b4bb0..00000000 --- a/Source/Doc/RomWBW User Guide/Licensing.ltx +++ /dev/null @@ -1,5 +0,0 @@ -\chapter{Licensing} - -\input{GPL.ltx} - -\input{GFDL.ltx} diff --git a/Source/Doc/RomWBW User Guide/Logo.png b/Source/Doc/RomWBW User Guide/Logo.png deleted file mode 100644 index 3d43ff31..00000000 Binary files a/Source/Doc/RomWBW User Guide/Logo.png and /dev/null differ diff --git a/Source/Doc/RomWBW User Guide/Main.ltx b/Source/Doc/RomWBW User Guide/Main.ltx deleted file mode 100644 index 91652a84..00000000 --- a/Source/Doc/RomWBW User Guide/Main.ltx +++ /dev/null @@ -1,192 +0,0 @@ -\documentclass[letterpaper,12pt,oneside]{book} -%\documentclass[letterpaper,12pt,oneside,draft]{book} -%\usepackage[utf8]{inputenc} % Handle UTF8 input files -\usepackage[T1]{fontenc} % Allows use of fonts with char codes 128-255 -\usepackage{bookman} % Nice rm font -\usepackage{ascii} % Nice tt font -\usepackage[scaled]{helvet} % Nice sf font -\usepackage{geometry} % Page layout commands -\usepackage{multicol} % Easy use of multiple columns -\usepackage{hyperref} % More flexible hyperref formatting (\hypersetup) -\usepackage{blindtext} % Enable \blindtext -%\usepackage{scrextend} % A bundle of useful stuff... -\usepackage[inline]{enumitem} % Prettier version of enumerate list -%\usepackage{color} % Enable colors? -\usepackage{framed} % Enable framing (used in examples) -\usepackage{graphicx} % Add support for imbedding graphics -\usepackage{fancyhdr} % More flexible header formatting -\usepackage{xhfill} % Enable \xrfill used in footer -\usepackage{booktabs} % Improved table formattting -\usepackage{fancyvrb} % Enhances \verbatim to allow internal formatting -\usepackage{tocloft} % More versatile toc/lof/lot formatting -\usepackage{bookmark} % Avoids "Package rerunfilecheck Warning" -%\usepackage{showframe} % Diagnostic - -\title{RomWBW User Guide\\Version 2.8} -\author{Wayne Warthen\\wwarthen@gmail.com\\\\RetroBrew Computing Group\\http://www.retrobrewcomputers.org} -\date{\today} - -% -% PDF construction options -% - -%\pdfminorversion=5 -%%\pdfobjcompresslevel=1 -%\pdfobjcompresslevel=3 -%\pdfcompresslevel=9 - -% -% Global page layout and formatting -% - -%\renewcommand*\ttdefault{ascii} -%\renewcommand*\rmdefault{bookman} -%\renewcommand*\sfdefault{ascii} -\renewcommand*{\familydefault}{\sfdefault} -\geometry{letterpaper, margin=1.5in} -\setlength{\headheight}{14pt} -%\setlength\papermarginwidth{1.5in} -%\settextfraction{1.0} -%\setlength\textheight{5in} -%\renewcommand{\baselinestretch}{1.1} -\setlength\parskip{1.2em} -\setlength\parindent{0pt} -\setlist{topsep=0pt} -\sloppy - -\newcommand\todo[1]{\textcolor{red}{[TODO: #1]}} -\hypersetup{colorlinks=true} -%\addtokomafont{labelinglabel}{\bfseries} % Make list labels bold - -\renewcommand{\chaptername}{Section} - -\begin{document} - -% -% Start of frontmatter -% - -\frontmatter -\pagestyle{plain} - -%\maketitle -\begin{titlepage} - \centering - \par - \vspace*{72pt} - \includegraphics{Logo.png} \par - \vfill - \raggedleft - {\scshape \bfseries \fontsize{48pt}{56pt} \selectfont RomWBW \par} - {\bfseries \fontsize{32pt}{36pt} \selectfont User Guide \par} - \vspace{24pt} - {\huge Version 2.8 \\ \today \par} - \vspace{24pt} - {\large \itshape RetroBrew Computing Group \\ \href{http://www.retrobrewcomputers.org}{www.retrobrewcomputers.org} \par} - \vspace{12pt} - {\large \itshape Wayne Warthen \\ \href{mailto:wwarthen@gmail.com}{wwarthen@gmail.com} \par} -\end{titlepage} - -\setcounter{page}{2} - -\begin{center} \Large \uppercase{Copyright} \end{center} - -Copyright \copyright{} 2016 Wayne Warthen - -Permission is granted to copy, distribute and/or modify this document -under the terms of the GNU Free Documentation License, Version 1.3 -or any later version published by the Free Software Foundation; -with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. -A copy of the license is included in the section entitled "GNU -Free Documentation License". - -\bigskip -\begin{center} \Large \uppercase{Disclaimer} \end{center} - -The author makes no representations or warranties with respect to the contents hereof and -specifically disclaims any implied warranties of merchantability or fitness for any particular -purpose. Further, the author reserves the right to revise this publication and to make -changes from time to time in the content hereof without obligation of the author to notify -any person of such revision or changes. - -\bigskip -\begin{center} \Large \uppercase{Trademarks} \end{center} - -CP/M is a registered trademark of Digital Research. ASM, DESPOOL, DDT, -LINK-80, MAC, MP/M, PL/1-80 and SID are trademarks of Digital Research. Intel is a -registered trademark of Intel Corporation. Zilog and Z80 are registered trademarks of Zilog, Inc. - -\bigskip -\bigskip -\begin{center} \rule{3cm}{2pt} \end{center} - -\bigskip -\bigskip -\begin{it} -This document was formatted using \LaTeX{} and produced using the MiKTeX implementation of pdfLaTeX and BibTeX. -\end{it} - -\newpage -\renewcommand{\contentsname}{Table of Contents} -\cftpagenumbersoff{chapter} -\tableofcontents -\clearpage -\listoftables -\clearpage -\listoffigures -\clearpage - -\renewcommand*\ttdefault{pcr} - -% -% Start of main document content -% - -\mainmatter -\pagestyle{fancy} - -\fancyhf{} -\renewcommand{\chaptermark}[1]{ \markboth{#1}{} } -\renewcommand{\sectionmark}[1]{ \markright{#1}{} } -\fancyfoot{\small RetroBrew Computing Group ~~ {\xrfill[3pt]{1pt}[cyan]} ~~ \thepage} -\renewcommand{\headrulewidth}{0pt} -\renewcommand{\footrulewidth}{0pt} - -\fancypagestyle{plain}{ - \fancyhf{} - \fancyfoot{\small RetroBrew Computing Group ~~ {\xrfill[3pt]{1pt}[cyan]} ~~ \thepage} - \renewcommand{\headrulewidth}{0pt} - \renewcommand{\footrulewidth}{0pt} -} - -%\fancyhead{\footnotesize \bfseries \thesection ~ \rightmark \hfill RomWBW User Guide} -\fancyhead{\small \thesection ~ \rightmark \hfill RomWBW User Guide} -\renewcommand{\headrulewidth}{1pt} - -\input{Overview.ltx} - -\input{GettingStarted.ltx} - -\input{DiskUsage.ltx} - -\input{SystemMonitor.ltx} - -\input{OperatingSystems.ltx} - -\input{Features.ltx} - -\input{Customization.ltx} - -\input{FormatSamples.ltx} - -\appendix - -\addtocontents{toc}{ {\bigskip \bigskip \Large Appendixes \bigskip \par} } - -\input{BoardNotes.ltx} - -\input{Licensing.ltx} - -\backmatter - -\end{document} \ No newline at end of file diff --git a/Source/Doc/RomWBW User Guide/OperatingSystems.ltx b/Source/Doc/RomWBW User Guide/OperatingSystems.ltx deleted file mode 100644 index b0fa7ab0..00000000 --- a/Source/Doc/RomWBW User Guide/OperatingSystems.ltx +++ /dev/null @@ -1,15 +0,0 @@ -\chapter{Operating Systems} - -\blindtext - -\section{CP/M-80} - -\blindtext - -\section{Z-System} - -\blindtext - -\section{NZ-COM} - -\blindtext diff --git a/Source/Doc/RomWBW User Guide/Overview.ltx b/Source/Doc/RomWBW User Guide/Overview.ltx deleted file mode 100644 index 15012aea..00000000 --- a/Source/Doc/RomWBW User Guide/Overview.ltx +++ /dev/null @@ -1,272 +0,0 @@ -\chapter{Overview} - -RomWBW is system software that supports the Z80/Z180 based computing -platforms produced by the RetroBrew -Computing Group which is found at http://www.retrobrewcomputers.org. The goal of the -project is to provide all firmware -and software needed to make a fully functional computing platform. This -includes both firmware (ROM) and -software (disk images). The source code is provided and is licensed under -GPL v3. A GitHub repository is -used to maintain all source code and can be found at -http://www.github.com/RomWBW. - -Essentially all Z80/Z180 based hardware produced by RetroBrew Computing is -fully supported by RomWBW. Much of the software was adapted from software -produced by others in the community (see Acknowledgements) and is packaged -within RomWBW to provide an integrated solution. A companion document -(RomWBW System Guide) provides substantial detail on the architecture and -internal operation of this software. - -It is worth noting that this software is a perpetual work-in-progress. -While it has become fairly stable and robust over time, it is undergoing -constant updates to support new and revised hardware produced by the -community. Backward compatibility between releases should not be assumed. -In order to provide a complete solution, the RomWBW package incorporates a -hardware BIOS (hardware drivers) and selected operating systems and -application software. All software is derived from the CP/M era of 8-bit -computing. The operating systems included have been adapted to run under -the RomWBW architecture. In general, application software has simply been -included as originally distributed by the vendors and required no adaptation. - -The RomWBW distribution package includes all the tools required to easily -build the software from the source that is included in the package. The -package includes a range of pre-built ROM and disk images. These are -usually sufficient to get your hardware up and running simply by -programming a ROM and optionally copying disk image(s) to a floppy disk, CF -Card, or SD Card. If you wish to highly customize your system software, it -is straightforward to modify the source code and build your own. At -present, this requires Microsoft Windows XP or greater. All of the tools -have counterparts for Linux, so building the software under Linux should be -possible with a little effort. - -\section{System Requirements} - -RomWBW is purely a software project. It assumes you have a fully -functional hardware platform on which to host the software. A great deal -of information on procuring and building the appropriate hardware is found -on the RetroBrew Computing Wiki at http://www.retrobrewcomputing.org. -Additionally, the RetroBrew Computing Group has a very active forum found at -http://www.retrobrewcomputing.org/forum. This forum is the ideal place to -ask questions and get guidance for hardware and software. It is the -primary forum for supporting RomWBW. - -The starting point for a hardware platform that will appropriately host -RomWBW software is one of the following CPU boards: - -\begin{description}[style=multiline, leftmargin=1.25in, labelindent=0.25in, align=right] - -\item [SBC] -This is the original Z80 CPU board produced by the community. It remains a -very functional platform and is relatively easy to build. Note that v1 has -a design deficiency that may or may not prevent the proper operation of -RomWBW (bank switching does not always function reliably). The SBC CPU -board features an ECB bus connector which allows it to be expanded with a -backplane and peripheral boards. - -\item [Zeta] -The Zeta is very similar to the SBC board and is generally compatible with -it. However, the Zeta platform is optimized to be a compact, standalone -system. In addition to the features of the SBC, it includes an onboard -floppy disk controller and the form factor of the board allows it to be -mounted directly to a 3.5" floppy disk for a complete computing solution. -It optionally supports a single daughter board that provides SD Card -storage, VGA Monitor interface, and PS/2 keyboard interface. Although it -does not have a bus interface, Zeta is powerful, compact, and fully -featured. The Zeta v2 primarily adds enhanced bank switching and an -interrupt controller which is not required by RomWBW. - -\item [N8] -The N8 is a very robust SBC. It is significantly larger than the SBC and -incorporates a wide range of peripherals right on the one board (although -it also supports expansion via ECB bus). The N8 is based on the Z180 CPU -and incorporates interfaces for 2 serial ports, 2 parallel ports, IDE Hard -Disk / CF Card, SD Card, sound synthesizer, video display, PS/2 keyboard -and mouse interface, and floppy disk controller. This board is very -powerful, but more challenging to build. It is not compatible with the -SBC/Zeta -- it implements a different bank switching mechanism. - -\item [Mark IV] -The Mark IV by John Coffman is similar to the SBC in that it shares the -same form factor and ECB for expansion. However, it is substantially more -powerful featuring a Z180 CPU and onboard CF Card and SD Card interfaces. - -\end{description} - -RomWBW fully supports all of the above boards as a starting point. For -Zeta, the ParPortProp is supported as an option. The other platforms all -support the ECB bus for adding optional peripheral support boards. - -In addition to the hardware listed above, RomWBW also runs well on the -Microsoft Windows based SIMH Altair Z80 simulator which allows you to try -all of the RomWBW features without any actual hardware. The distribution -package contains a copy of the simulator software for MS Windows, so it is -very easy to use it (see Getting Started). - -Note that RomWBW assumes specific board configuration settings. You must -ensure that you set the jumpers/switches of each board as required by -RomWBW (unless you modify RomWBW and produce a custom version that supports -your specific board configurations). The standard board configuration -settings are documented in Appendix A. - -Note that RomWBW assumes there is 512KB of ROM and 512KB of RAM for all -systems. It is fine if your system has more RAM or ROM than this, but it -is problematic if you have less. It would be very rare for a system to -have less that these amounts, but be aware of this constraint. These -assumptions can be modified via customization later, but the pre-built -software must have these minimums. - -All of the host boards include a serial port. RomWBW will use this serial -port for output when you start your system. By default, RomWBW uses 38,400 -baud, 8 data bits, 1 stop bit, and no parity. You will need to connect the -primary serial port of the host board to a terminal (or PC running terminal -emulation software) to see the system output when you start RomWBW. - -The use of the ECB bus signals is standardized such that any ECB add-on -board can generally be combined with any of the ECB host boards to provide -enhanced functionality. Appendix A provides an inventory of the boards -supported by RomWBW along with relevant notes and required board -configuration settings. Appendix A also includes a compatibility/support -matrix between the host boards and the peripheral support boards. - -\section{Acknowledgements} - -First, I want to be clear that RomWBW is not the only option available for -system software on RetroBrew Computing Z80/Z180 hardware. While many -similar projects are no longer active, they are very useful and may contain -functionality that has not been incorporated in RomWBW. All of the -software projects (including RomWBW) are listed in the RetroBrew Computing -Wiki. - -The UNA Project from John Coffman is the other currently active software -project for the Z80/Z180 projects. It is far more advanced than RomWBW in -that it can support all 4 host boards with a single ROM image and allows -dynamic system configuration via onboard setup. It does not yet support -the full range of hardware or video capabilities of RomWBW. Note that -RomWBW supports an UNA "hybrid" configuration in which the UNA BIOS is -combined with the RomWBW OS and application layers. - -The RetroBrew Computing Group has existed in various forms since about 2010 -(?). Many individuals have contributed to the community. The original -founder of the community has moved on and requested anonymity going -forward. However, his initiative is greatly appreciated. While there is -no formal structure to the community, Andrew Bingham has taken the mantle -of responsibility for the wiki and discussion group. This is a critical -function and he deserves substantial credit for this effort. - -Earlier in the community's history, there were multiple branches of -software development. Frequently, when a new board was produced, someone -would create an independent code branch to support it. This started to -lead to a very fragmented set of software that made it very difficult to -create an integrated system with selected boards. RomWBW came about as an -effort to create a framework that would allow arbitrary hardware to be -easily added without creating entirely separate branches of code. - -RomWBW essentially became a semi-structured place to incorporate all of the -many software efforts of the community. Initially, most of the RomWBW -codebase was simply a "cut and paste" of the software produced by others. -Over time, much of this software has been repeatedly revised such that it -is no longer similar to the original, but RomWBW owes its existence to the -contributions of many other individuals. A few of those people are listed -below and I apologize for anyone that I may have inadvertently omitted. I -have intentionally omitted the original founder of the community based on -my understanding of his desire to be anonymous going forward. - -Douglas Goodall worked in very close collaboration with me during the first -year of the RomWBW Project. He produced an excellent set of supporting -utility programs and provided a great deal of design input. Regrettably, -his utilities no longer have a caretaker and have become unusable as RomWBW -has evolved, but their legacy continues within the current codebase. The -source for all of these utilities is still available if anyone wants to -take responsibility for bringing them back to current status. - -John Coffman has personally produced a great deal of the hardware designs -within the community. RomWBW contains many portions of code that John -contributed over time. Additionally, he has been instrumental in providing -advice and guidance to me for many years now. - -Dan Werner has been one of the most prolific coders within the community. -A great deal of his code was incorporated in the early RomWBW releases. -David Giles produced some code that also provided a more integrated set of -software for each host board. Over time, much of his code was incorporated -in RomWBW. Likewise, Max Scane has produced code that ultimately wound up -in RomWBW -- specifically, he contributed the CLRDIR application. - -It is my belief that all code incorporated into RomWBW has been done so -with the express or implied permission of the original authors. I realize -there have been many other individuals that have contributed to RomWBW and -apologize for not naming all of them. - -\section{RomWBW Distribution Package} - -RomWBW is distributed as a complete package (a .zip file) that contains -everything appropriate for the different hardware variations. In other -words, don't look for a specific distribution for your hardware, you just -want the current package. Within the package, you will find documentation, -source code, build tools, and pre-built ROM and disk images. - -The distribution package is usually hosted at the following locations: - - -\begin{itemize} -\item {\bf RetroBrew Computing Wiki} - -Navigate to https://www.retrobrewcomputers.org. Then, using the navigation -menu on the left, choose software $\rightarrow$ firmwareos -$\rightarrow$ romwbw to reach the RomWBW -Project Page. At the bottom of the page you will find the distribution -files listed for download. - -\item {\bf GitHub} - -Navigate to https://github.com/wwarthen/RomWBW for the RomWBW Project -on GitHub. Select "releases" to reach the list of distribution files. -Note that you will see both Prereleases and Releases listed. Unless you -specifically want to test work-in-progress, please download only a Release -version. -\end{itemize} - -The package should be named something like RomWBW-2.8-Package.zip. Using -any standard personal computer (Windows, Linux, Mac, etc.), download and -extract the contents of the zip file using any of the standard zip tools. -You will see that there are several directories that are used to organize -the contents. Don't get overwhelmed. Initially, all you really care about -is the Output directory (and possibly the Doc directory): - -\begin{description}[style=multiline, leftmargin=1.25in, labelindent=0.25in, align=right] - -\item [Doc] -Contains documentation files for many components of the RomWBW distribution -including operating systems, applications, and other aspects of RomWBW -itself. In most cases, the name of the file should identify the component -being documented. - -\item[Hardware] -Files that are specific to certain hardware components. For example, it -has the font ROM images for the video display boards. You do not need any -of these files for the host boards used initially. Appendix A describes -the contents of this directory for relevant boards. - -\item[Images] -Files that are used to create disk images. Since the disk images are all -pre-built, you do not need to worry about this directory until you want to -create custom disk images (documented later). - -\item[Output] -The ROM and Disk Images that you need to get started as documented below in -Getting Started. - -\item[Source] -The source code files that are compiled or assembled to create RomWBW. -Again, the output is pre-built, so you don't need to worry about this -directory until you want to customize your system. - -\item[Tools] -Windows-based applications that are used to build RomWBW. It also contains -applications that you can use to copy disk images to floppy disks, CF -Cards, SD Cards, etc. It also has the SIMH simulation software. - -\end{description} - -In most cases, you will find a ReadMe.txt file in the directory which -describes the contents of the directory in more detail. diff --git a/Source/Doc/RomWBW User Guide/SystemMonitor.ltx b/Source/Doc/RomWBW User Guide/SystemMonitor.ltx deleted file mode 100644 index eff0bf16..00000000 --- a/Source/Doc/RomWBW User Guide/SystemMonitor.ltx +++ /dev/null @@ -1,7 +0,0 @@ -\chapter{System Monitor} - -\blindtext - -\section{Monitor Commands} - -\blindtext \ No newline at end of file diff --git a/Source/Doc/RomWBW User Guide/x - Copy (8).ltx b/Source/Doc/RomWBW User Guide/x - Copy (8).ltx deleted file mode 100644 index 9b8e1807..00000000 --- a/Source/Doc/RomWBW User Guide/x - Copy (8).ltx +++ /dev/null @@ -1,7 +0,0 @@ -\chapter{x} - -\blindtext - -\section{x} - -\blindtext \ No newline at end of file diff --git a/Source/Doc/RomWBW User Guide/x - Copy.ltx b/Source/Doc/RomWBW User Guide/x - Copy.ltx deleted file mode 100644 index 9b8e1807..00000000 --- a/Source/Doc/RomWBW User Guide/x - Copy.ltx +++ /dev/null @@ -1,7 +0,0 @@ -\chapter{x} - -\blindtext - -\section{x} - -\blindtext \ No newline at end of file diff --git a/Source/Doc/RomWBW User Guide/x.ltx b/Source/Doc/RomWBW User Guide/x.ltx deleted file mode 100644 index 9b8e1807..00000000 --- a/Source/Doc/RomWBW User Guide/x.ltx +++ /dev/null @@ -1,7 +0,0 @@ -\chapter{x} - -\blindtext - -\section{x} - -\blindtext \ No newline at end of file diff --git a/Source/Doc/WBW.png b/Source/Doc/WBW.png deleted file mode 100644 index 3d43ff31..00000000 Binary files a/Source/Doc/WBW.png and /dev/null differ diff --git a/Source/Doc/Z180 ASCI Baud Rate Options.xlsx b/Source/Doc/Z180 ASCI Baud Rate Options.xlsx new file mode 100644 index 00000000..b808a5f9 Binary files /dev/null and b/Source/Doc/Z180 ASCI Baud Rate Options.xlsx differ diff --git a/Source/Doc/ZCPR Manual/Build.cmd b/Source/Doc/ZCPR Manual/Build.cmd deleted file mode 100644 index 84086e9b..00000000 --- a/Source/Doc/ZCPR Manual/Build.cmd +++ /dev/null @@ -1,19 +0,0 @@ -@echo off -setlocal - -rem set MIKTEX_HOME=D:\miktex-portable\texmfs\install - -rem if "%MIKTEX_HOME%"=="" goto :eof - -rem set TEXSYSTEM=miktex -rem set MIKTEX_BINDIR=%MIKTEX_HOME%\miktex\bin -rem set MIKTEX_COMMONSTARTUPFILE=%MIKTEX_HOME%\miktex\config\miktexstartup.ini -rem set MIKTEX_GS_LIB=%MIKTEX_HOME%\ghostscript\base;%MIKTEX_HOME%\fonts -rem set MIKTEX_USERSTARTUPFILE=%MIKTEX_HOME%\miktex\config\miktexstartup.ini -rem set PATH=%MIKTEX_HOME%\miktex\bin;%PATH% - -call texify -p --clean "Main.ltx" - -if errorlevel 1 goto :eof - -move /Y Main.pdf "..\..\..\Doc\ZCPR Manual.pdf" \ No newline at end of file diff --git a/Source/Doc/ZCPR Manual/Clean.cmd b/Source/Doc/ZCPR Manual/Clean.cmd deleted file mode 100644 index 42d81d19..00000000 --- a/Source/Doc/ZCPR Manual/Clean.cmd +++ /dev/null @@ -1,9 +0,0 @@ -@echo off -setlocal - -if exist *.pdf del *.pdf -if exist *.prn del *.prn -if exist *.ix del *.ix -if exist *.log del *.log -if exist part?.txt del part?.txt -if exist *.synctex.gz del *.synctex.gz diff --git a/Source/Doc/ZCPR Manual/Main.ltx b/Source/Doc/ZCPR Manual/Main.ltx deleted file mode 100644 index 2cc18498..00000000 --- a/Source/Doc/ZCPR Manual/Main.ltx +++ /dev/null @@ -1,25 +0,0 @@ -\documentclass[letterpaper,10pt,oneside]{book} -\usepackage[T1]{fontenc} -%\usepackage[defaultmono]{droidmono} -\usepackage[scaled]{beramono} -\usepackage{fancyvrb} -\usepackage{geometry} -\usepackage{pdflscape} -%\usepackage{showframe} % Diagnostic - -% Suppress headers and footers completely -\pagestyle{empty} - -% 66 lines per page, portrait -\geometry{top=0.0in, bottom=0.0in, left=1.5in, right=1.5in} - -\RecustomVerbatimCommand{\VerbatimInput}{VerbatimInput} -{ - commandchars=\\\{\} -} - -\begin{document} - -\VerbatimInput{zcpr.ltx} - -\end{document} \ No newline at end of file diff --git a/Source/Doc/ZCPR Manual/zcpr.ltx b/Source/Doc/ZCPR Manual/zcpr.ltx deleted file mode 100644 index 732f43a7..00000000 --- a/Source/Doc/ZCPR Manual/zcpr.ltx +++ /dev/null @@ -1,1384 +0,0 @@ - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - - - - \textbf{Documentation on ZCPR - A Z80 Replacement for the CP/M CCP} - - - - - ZCPR is a Group Project By the CCP-GROUP: - RLC - Richard Conn FJW - Frank Wancho - KBP - Keith Peterson RGF - Ron Fowler - - - ZCPR Documentation By RLC - - - - - - - - Table of Contents - ----- -- -------- - - Introduction 2 - - Part A: Installation Instructions 4 - ZCPR Integration Example 5 - Setting the ZCPR Inline Options 8 - REL, BASE, CPRLOC, RAS, SUBA, CLEVEL3 8 - Customization Symbols 8 - NLINES, WIDE, PGDFLT 8 - PGDFLG, MAXUSR, SYSFLG, SOFLG, SUPRES, - DEFUSR, SPRMPT, CPRMPT, NUMBASE, 9 - SECTFLG, FENCE 10 - Patching SUBMIT.COM 10 - - Part B: Usage Instructions and Explanation of - Commands 11 - The ZCPR Command Hierarchy Search 11 - The ZCPR-Resident Commands 14 - DIR, ERA 14 - LIST, TYPE, SAVE 15 - REN, USER, DFU 16 - JUMP, GO, GET 17 - ZCPR Error Messages 18 - - Part C: ZCPR Command Levels and How to Use Them 19 - - - - - - - - - Page 1 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - \textbf{Documentation on ZCPR - A Z80 Replacement for the CP/M CCP} - - - - - ZCPR is a replacement for the CP/M Console Command Processor - (CCP) which is designed to run as part of CP/M on Z80-based - microcomputers. In most cases it is upward-compatible with the - original CP/M Version 2.2 CCP. - - ZCPR, however, provides many extensions to the CP/M CCP. - Included in these extensions are the following features: - - . The TYPE function can be made to page or not page its - output at the user's discretion - - . A LIST function is available which sends its output - to the CP/M LST: Device and does NOT page - - . The DIR command has been extended to allow the dis- - play of the system files or all files - - . The ERA command now prints out the names of the files - it is erasing - - . The current user number may be included as part of - the command prompt; if the user is under a number other than 0, - the prompt is of the form 'du>' (like 'A2>' or 'B10>'), and, if - the user is under 0, the prompt may be 'd>' or 'd0>' as per his - choice - - . The SUBMIT facility has been changed in two basic - ways: - - the prompt changes to 'du$' or 'd$' when the - SUBMIT command is printed - - the $$$.SUB is executed from drive A: (note that - the original SUBMIT problem now exists, but the new SUB.COM - facility corrects it); the CCP-GROUP definition of an Indirect - Command File now applies, and this definition is that any - sequence of commands which may be issued from the console is also - a valid sequence of commands for execution from an Indirect - Command File; hence, the sequence: - - DIR - B: - DIR - A: - - may be issued from either the console or an Indirect Command - File, and the results of the execution of this sequence are the - same. Basically, this says that Indirect Command Files are - upward-compatible to the console input (but not necessarily that - the contents of an Indirect Command File may be issued at the - console without modification). - - - Page 2 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - . A command-search hierarchy is now implemented which - is executed roughly as follows: - - the user's command is checked against the CPR- - resident commands and executed immediately if a match is found - - failing that, the current user number on the - current disk is scanned for the COM file; the COM file is loaded - and executed if found - - failing that, a default user number (initially 0 - but can be reset with the DFU CPR-resident command) on the cur- - rent disk is scanned for the COM file; the COM file is loaded and - executed if found - - finally, failing that, the default user number - on disk A: is scanned for the COM file; the COM file is loaded - and executed if found or an error message (COMMAND?, when COMMAND - was the user's command name) is printed - - . The numeric argument for the SAVE command can be - specified in hexadecimal so that the user may employ the values - presented by tools such as DDT exactly as they are given - - . A GET command which loads a file at a specified - memory address and a JUMP command which "calls" the subroutine at - a specified memory address have been added; a GO command which - "calls" the subroutine at 100H (subset of the JUMP capability) - has also been added - - - This document provides the user of ZCPR with the following - information: - - Part A: Installation Instructions - Part B: Usage Instructions and Explanation of Commands - Part C: ZCPR Command Levels and How to Use Them - - - - - - - - - - - - - - - - - - - - - - - - Page 3 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - Part A - Installation Instructions - - In order to install ZCPR on a target microcomputer (must be - currently running CP/M 2.2), the user must know two basic things: - - 1) Where his CCP is currently running in memory - 2) Where his CCP is located in the SYSGEN image, or, - for systems which don't support SYSGEN (such as P&T CP/M 2.2 for - the TRS-80 Model II), where his CCP is located on disk and how to - place the new ZCPR on top of it - - The first question is answered relatively easily. A pro- - gram, known as either BDOSLOC or BDLOC (for BDOS Locator), is - provided with ZCPR. You should assemble this program for your - particular computer (change the base ORG if you are running non- - ORG-0 CP/M) and execute it. Upon execution, it will provide you - with the base address of (1) the BDOS and (2) the CCP for your - particular system. BDOSLOC has worked correctly for all systems - tested so far, but there is always a chance that it may NOT work - for some non-tested system. For the time being, assume that it - works correctly and record the starting base page address of your - CCP. - - The second question is not answered nearly so easily. If - you have the ability to SYSGEN your system, it is much easier - (commonly) than if you do not. You must, after assembling the - ZCPR properly, integrate it into the sysgen (or disk) image of - CP/M. This can be done by obtaining a SYSGEN image of your - system, scanning it via a debugger such as DDT to find the offset - for the CCP, reading the new CPR in on top of the old one, and - finally running SYSGEN again to place the resultant system on - disk. If you DO NOT have SYSGEN capability, a Disk Utility - program is required to locate the CCP on disk and then write the - new ZCPR on top of the old one. The net result of this - integration is the placement of the new ZCPR onto disk in the - proper place so that it will be loaded with the rest of CP/M on - cold boot and executed properly. - - To find the original CCP, you typically have to locate it by - its appearance. It is probably stored contiguously on disk, so, - once it is found, a sequential overwrite is all that is required. - Probability is extremely high that it is stored contiguously in - the SYSGEN image. The CCP starts with two (2) and ONLY TWO jump - instructions followed by a buffer area (possibly containing an - initial command and/or the Digital Research copyright notice). - The Digital Research manuals show the CCP to reside at address - 980H in the SYSGEN image, but this may vary with system. To - find this image, use DDT or some other such debugger, load the - SYSGEN image you can get via SYSGEN, and examine memory starting - at around 900H for the two (and ONLY two) jumps described above. - If you find an area with more than two jumps (a group of them), - you are probably looking at the BIOS and should go lower for the - CCP. The CCP will probably start on an even page or half-page - address (like 900H, 980H, 1100H, etc). - - Page 4 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - Now that the location of the CCP has been found, record this - address for later. You are now ready for the integration of ZCPR - into your system. To do this, perform the following steps using - the information of the page address of the CCP (obtained from - BDOSLOC and called CPRLOC within ZCPR) and the SYSGEN image - address of the CCP (called IMAGE for reference in this document). - - 1. Edit ZCPR and set the CPRLOC equate to the value - obtained from above. Also set any flags and values as you desire - (see the section on ZCPR Customization below). When satisfied, - end the edit session. - - 2. Assemble ZCPR with MAC (or equivalent). This - assembler is required because of the MACROs used. Only the - resultant HEX file is required. - - 3. Assuming that you can use SYSGEN, obtain a SYSGEN - image of your current CP/M system and save it on disk. - - 4. Load the SYSGEN image into memory with DDT (or - equivalent). Once loaded, verify that the original CCP is at the - IMAGE address found above and compute the integration offset - using the DDT H command: - H, - The second number displayed gives you the OFFSET value required - for step 5. - - 5. Integrate ZCPR into your SYSGEN image via DDT's I - and ROFFSET commands. Use IZCPR.HEX (or the name of your version - of ZCPR) to load the FCB and ROFFSET (where OFFSET was computed - in step 4) to load the ZCPR.HEX file into memory at the proper - location. Check to see that ZCPR is indeed properly loaded by - examining the SYSGEN IMAGE area. - - 6. Place the new system on disk by running SYSGEN and - NOT loading the system from disk (use the memory image). - - For further clarification of the above process, the - following is a sample terminal session which outlines the steps - taken. - - ZCPR Integration Example - - - B>; Sample terminal session for integrating ZCPR - B>sysgen - SYSGEN VER 2.2 - SOURCE DRIVE NAME (OR RETURN TO SKIP)b - SOURCE ON B, THEN TYPE RETURN <-- I hit the RETURN key here - FUNCTION COMPLETE / - DESTINATION DRIVE NAME (OR RETURN TO REBOOT) <-- and here - B>save 44 cpm56.com <-- We now have a SYSGEN image of CP/M - to work with - - - - Page 5 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - B>xdir - XDIR Version 2.6 User Number: 0, Double Density - File Attributes: Non-System - - Filename.Typ Size K Filename.Typ Size K Filename.Typ Size K - -------- --- ------ -------- --- ------ -------- --- ------ - !TEXTWRK.-12 0 CPR .DOC 8 EE687 .TXT 4 - CPR .AQM 34 TFS .HLP 6 EE687PRE.TXT 4 - CPR .ASM 50 CONTENTS.T01 6 SW1 .TXT 10 - CPR .BAK 4 CONTENTS.T02 4 SW2 .TXT 2 - CPM56 .COM 12 CONTENTS.T03 4 - B: 30 Entries & 22 Files -- 338K Bytes Remaining - File Data: 14 Files -- 154K Bytes Displayed - B>bdosloc <-- Now to locate the CCP's address - The Base Page Address of this system's BDOS is C5 - The Base Page Address of this system's CCP is BD <-- This is it - B>ddt cpm56.com <-- Now to find the CCP in the SYSGEN image - DDT VERS 2.0 - NEXT PC - 2D00 0100 - -d900,90f <-- Start looking around here - 0900 31 80 E7 3E 06 3C 3C FE 1B CA 00 C2 DA 11 E7 D6 1..>.<<......... - -da00,a0f - 0A00 31 00 01 01 01 0C C5 CD 0F E4 21 00 BE 11 00 04 1.........!..... - -db00,b0f - 0B00 31 00 01 01 01 11 C5 CD 0F E4 21 00 C0 11 00 02 1.........!..... - -db80,b8f - 0B80 31 00 01 01 09 01 CD A8 00 21 00 D2 11 00 C2 0E 1........!...... - -- Detail Left Out -- - -d1100 <-- I found it at 1100H; note the 2 JMP's - 1100 C3 FF BD C3 FB BD 50 10 20 20 20 20 20 20 20 20 ......P. - 1110 20 20 20 20 20 20 20 20 00 00 00 00 00 00 00 00 ........ - 1120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ - -- Detail Left Out -- - -^C <-- Return to CP/M; I know that CPRLOC will be - BD00H and the IMAGE offset is 1100H - - B>ed cpr.asm \{edit ZCPR here and place CPRLOC=BD00H\}# - -- Detail Left Out -- - - B>mac cpr $pz sz <-- Now to assemble the CPR - CP/M MACRO ASSEM 2.0 - C4F0 <-- Note that CPR MUST end before BDOS - begins! - 014H USE FACTOR - END OF ASSEMBLY - - - - - - - - - - - - Page 6 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - - B>ddt cpm56.com <-- Now to integrate! - DDT VERS 2.0 - NEXT PC - 2D00 0100 - -h1100,bd00 <-- Compute offset for new CPR - CE00 5400 <-- Offset is 5400H - -icpr.hex <-- Init FCB - -r5400 <-- Read in new CPR with offset - NEXT PC - 2D00 0000 - -^C <-- Done! - B>sysgen <-- Now to SYSGEN onto disk - SYSGEN VER 2.2 - SOURCE DRIVE NAME (OR RETURN TO SKIP) <-- Use memory image - DESTINATION DRIVE NAME (OR RETURN TO REBOOT)b <-- onto B: - DESTINATION ON B, THEN TYPE RETURN - FUNCTION COMPLETE - DESTINATION DRIVE NAME (OR RETURN TO REBOOT) <-- Done for now - - B> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Page 7 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - Setting the ZCPR Inline Options - - The following are the four basic options available to the - user under ZCPR for customization of his package. - - Option Name Function - - REL Configures CPRLOC (CPRLOC equ 0) for integration - via MOVCPM rather than the DDT/SYSGEN technique - outlined above; set to TRUE for MOVCPM integra- - tion or FALSE for DDT/SYSGEN integration - - BASE Base address of your CP/M system; standard CP/M - has a base of 0, but some CP/M systems (such as - for the TRS-80 Model I and Heath/Zenith H89/Z89) - start physical RAM memory at a higher address; - equate BASE to the starting RAM memory address of - your system - - CPRLOC This is the starting address of ZCPR; set the - second CPRLOC equate to the address you obtain - from BDOSLOC - - RAS This is an equate which masks out selected ZCPR - command functions for security purposes on - Remote Access Systems such as Bulletin Boards; - the masked out functions currently include - SAVE, ERA, REN, JUMP, GO, and GET; set RAS to TRUE - to mask these out or FALSE to leave them in - - SUBA This is an equate which determines the drive - onto which ZCPR will look for an executing - Indirect Command File. If the basic philosophy - of the Indirect Command File described above is - to be maintained, this symbol should be set to - TRUE (look on drive A: for the $$$.SUB file); if - not, this symbol should be set to FALSE (look on - the default drive from the $$$.SUB file). To - review, the basic philosophy of the Indirect - Command File is that any sequence of commands - which may be issued from the console (within - reason, which means NOT to erase a $$$.SUB file) - may also be issued from within an Indirect - Command File, and the resultant execution should - be identical (same functions performed). - - CLEVEL3 This equate enables or disables extended Command - Level 3 Processing. If set to TRUE, extended - Command Level 3 Processing is enabled and the user - command line is automatically capitalized, the - terminating zero is placed at the end of the - buffer, and the internal CIBPTR is set correctly - (see later for more information). - - - - Page 8 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - - Customization Symbols - - The following symbols are provided for further customization - of ZCPR to a user's particular tastes and hardware facilities. - - Option Name Function - - NLINES Number of lines on the user's CRT for paging - - WIDE This equate is used to select a narrow or wide - display under the DIR command; if WIDE is equated - to TRUE, each file name is separated by two - spaces, a FENCE, and two more spaces; if WIDE is - equated to FALSE, each file name is separated by - one space, a FENCE, and one more space - - PGDFLT This is the Paging Default flag for the TYPE - command; if PGDFLT is set to TRUE, the TYPE - command will page its output by default and - the P option on the TYPE command (see below) - will prohibit paging; if PGDFLT is set to FALSE, - the TYPE command will NOT page its output by - default and the P option will enable paging - - PGDFLG This sets the option character in the command - line for the TYPE command (the 'P' mentioned - above); if the user wishes to change this option - character, he need only change this equate - - MAXUSR This is the largest user number recognized by - the USER command; if the user wishes to protect - the higher user areas, he may set this symbol - to the highest area normally accessable; 15 is - the largest permitted value for MAXUSR - - SYSFLG This is the option character for the DIR command - line which is used to specify that DIR search - All Files (both $SYS and $DIR) for its display; - the distributed default for this is 'A' - - SOFLG This is the option character for the DIR command - line which is used to specify that DIR search - ONLY the $SYS files for its display; the distri- - buted default for this is 'S' - - SUPRES Set SUPRES to TRUE to suppress printing the user - number when the user is under User Number 0 or - set SUPRES to FALSE to ALWAYS display the User - Number with the CPR prompt; with SUPRES set to - TRUE, a user on B: in user 0 sees 'B>' as the - prompt, but with SUPRES set to FALSE, a user on - B: in user 0 sees 'B0>' as the prompt - - - - Page 9 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - DEFUSR This is the CPR-default user number which is - searched in the command hierarchy for the COM - files (distributed as 0); the DFU changes this - temporarily until a Warm Boot or Cold Boot is - done, at which time the search reverts to this - value - - SPRMPT This is the CPR prompt character which indicates - that a SUBMIT file is in execution; by default - it is set to '$', so prompts like 'A$' appear - during SUBMIT file execution - - CPRMPT This is the CPR prompt character which indicates - that the CPR is awaiting a user console command; - by default it is set to '>', so prompts like - 'A>' appear during user input to the CPR - - NUMBASE This is the escape character used by those - commands which require a DECIMAL number as - an argument; placing this character after - the number argument switches the base to - HEXADECIMAL; for example, 'SAVE 15 MYFILE' can be - expressed as 'SAVE FH MYFILE' if NUMBASE is - set to 'H' (the default) - - SECTFLG This character constant is the suffix option for - the SAVE command which specifies that sectors, - as opposed to pages, are to be saved; the default - value is 'S' - - FENCE This is the character printed to separate entries - in a directory listing; it's default value is '|' - - - Patching SUBMIT.COM - - SUBMIT.COM may be patched to run with ZCPR by the following - procedure (this is recommended if the user does not have - SUB.COM). This patch simply makes it always place the $$$.SUB - file on Drive A:. Illustrative terminal session follows: - - A>ddt b:submit.com - DDT VERS 2.0 - NEXT PC - 0600 0100 - -s5bb <-- Patch is at 5BB Hex - 05BB 00 1 <-- Change 0 (default drive) to 1 (drive A:) - 05BC 24 . <-- That's it! - -d5b0 5cf <-- See change - 05B0 00 00 00 00 00 00 30 30 31 20 24 01 24 24 24 20 ......001 $.$$$ - 05C0 20 20 20 20 53 55 42 00 00 00 1A 1A 1A 1A 1A 1A SUB......... - -^C <-- Done - A>save 5 newsubmt.com <-- Save new SUBMIT.COM file - - - - Page 10 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - Part B - Usage Instructions and Explanation of Commands - - - The following instructions are written with the assumption - that the reader is quite familiar with how to use CP/M 2.2 and - its CCP. ZCPR is written as a logical extension of the CP/M 2.2 - CCP philosophy and should be addressed as such. - - - - The ZCPR Command Hierarchy Search - - The first, and most basic thing, to learn about ZCPR is the - order in which is searches for a COM file for execution or a file - specified by the GET command. Under the CP/M 2.2 CCP, if the - specified COM file command was not found on the current drive in - the current user area, the CCP aborted with an error message. - ZCPR, however, continues searching from this point a maximum of - two more levels. This command hierarchy search was outlined - above and is described here in further detail. - - 1. If the command is of the form 'COMMAND' and NOT - 'd:COMMAND', the CPR-resident command list is searched for a - match. If the match is found, the CPR-resident command is - immediately processed. If the match is not found or the command - is of the form 'd:COMMAND', the next step is taken. Note that - the 'd:COMMAND' form is good for executing a command COM file - which has the same name as a CPR-resident command (such as SAVE - or DIR). - - 2. If the command is of the form 'd:COMMAND', disk - drive 'd:' is temporarily logged in for the purpose of the - command search. Otherwise, the currently logged-in drive is - used. - - 3. Now the file named COMMAND.COM is searched for. If - found, it is loaded into memory starting at 100H and executed. - If not, proceed to step 4. - - 4. Now that the first search for COMMAND.COM has - failed, the CPR checks to see if the user is under the current - Default User Number. The Default User Number may be that set by - the DEFUSR equate in the CPR or that set by the user via the DFU - command. DEFUSR is in effect if DFU has not been issued since - the last Warm or Cold Boot, and DFU is in effect if it was issued - since the last Warm or Cold Boot. If the user is NOT under the - current Default User Number, ZCPR temporarily logs him into it - and searches the directory. If COMMAND.COM is found, it is - loaded as described above and executed. If not, ZCPR proceeds to - the next step. - - - - - - Page 11 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - 5. The user is now in the Default User Number, and at - this point, ZCPR checks to see if the user is on disk drive A:. - If not, it temporarily logs into A: and searches the default user - number of A: for COMMAND.COM. If found, it is loaded as - described above and executed. If not, ZCPR prints the command - name as an error message and returns to command input mode, - aborting the SUBMIT file if COMMAND came from it. - - In all cases of the search above, if COMMAND.COM is found, - after it is loaded into memory, ZCPR resets the user to his - original disk drive and user number. Hence, the files referenced - by the user by default are obtained from this environment. - - To illustrate this command hierarchy search, consider the - following examples: - - Example 1: DEFUSR equ 0 \{default user number is 0\} - - B10> <-- User is on Drive B:, User Number 10 - B10>ASM TEST.BBZ <-- User wishes to assemble TEST.ASM in - Drive B:, User 10 - <-- At this point, ZCPR looks on B:/10 for ASM.COM, fails, - looks on B:/0, fails, and finally looks on A:/0; it - finds ASM.COM here and goes back to B:/10 for the file - - - Example 2: DEFUSR equ 0 and DFU issued - - B10> <-- User is on Drive B:, User Number 10 - B10>DFU 5 <-- User Selects User 5 as default - B10>ASM TEST.BBZ <-- As above - <-- At this point, ZCPR looks on B:/10 for ASM.COM, fails, - look on B:/5, fails, and finally looks on A:/5; it - fails here also and prints ASM? as an error message - - Example 3: DEFUSR equ 0 - - B> <-- User is on Drive B:, User Number 0 - B>ASM TEST.BBZ <-- As above - <-- At this point, ZCPR looks on B:/0 for ASM.COM, fails, - looks on A:/0, fails, and prints error message - - Example 4: DEFUSR equ 0 - - A10> <-- User is on Drive A:, User Number 10 - A10>ASM TEST.AAZ <-- As above, but file on A: - <-- At this point, ZCPR looks on A:/10 for ASM.COM, fails, - looks on A:/0, fails, and prints error message - - - - - - - - - Page 12 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - Another Example: - - For example, if the user is logged into Drive B: in - User Area 10, the Default User Number is 0, and the following COM - files are present as indicated -- - - WM.COM on Drive A: in User 0 - MBASIC.COM on Drive A: in User 0 and on - Drive B: in User 0 - TEST.COM on Drive B: in User 10 and Drive B: - in User 0 - - then the following happens when the following commands are - issued from the console (or Indirect Command File): - - B10>WM TEST2.TXT - ^ ^ ^-------- File to be edited - | +----------- Invoke the WM.COM file (Word Master editor) - +--------------- User is on Drive B: in User Area 10 - - Results: - ZCPR searches B: User 10, B: User 0, and A: User 0 for - WM.COM; it finds WM.COM in A: User 0, loads it, logs the user - back into B: User 10, and executes it. - - B10>MBASIC - ^ ^----- Invoke the MBASIC.COM file (MBASIC Interpreter) - +--------- User is on Drive B: in User Area 10 - - Results: - ZCPR searches B: User 10 and B: User 0 for MBASIC.COM; - it finds MBASIC.COM in B: User 0, so it doesn't bother to look on - A: User 0. MBASIC.COM is then loaded and executed as described - in the previous example. - - B10>TEST - ^ ^--- Invoke the TEST.COM file (TEST program) - +------- User is on Drive B: in User Area 10 - - Results: - ZCPR searches B: User 10 for TEST.COM; it finds - TEST.COM in B: User 0, so it doesn't bother to look further (if - it had, it would have found TEST.COM in B: User 0). TEST.COM is - then loaded and executed as described above. - - B10>TEST2 - | +--- Invoke the TEST2.COM file (TEST2 program) - +------- User is on Drive B: in User Area 10 - - Results: - ZCPR searches B: User 10, B: User 0, and A: User 0 for - TEST2.COM; it doesn't find it, so it issues the error message - 'TEST2?', which says it couldn't find TEST2.COM. - - - - Page 13 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - - - The ZCPR-Resident Commands - - The following pages describe the ZCPR-Resident Commands. - These are commands located within ZCPR itself which are executed - from within ZCPR. The phrases and refer to ambiguous - file name and unambigous file name as per the CP/M convention. - - Command: DIR - Function: To Display a listing of the names of the files on disk - Forms: - DIR <-- Displays $DIR files - DIR S <-- Displays $SYS files - DIR A <-- Displays both $DIR and $SYS files - Customization Variables: - WIDE SYSFLG SOFLG FENCE - Examples: - DIR *.ASM <-- All $DIR .ASM files - DIR *.COM S <-- All $SYS .COM files - DIR *.COM A <-- All .COM files - Notes: - If a file is scanned for and no such name exists on disk, - the 'No Files' message will appear. However, if a file is - scanned for and the name exists as a $SYS file and $DIR files are - being scanned for, no file name is displayed but the 'No Files' - message does NOT appear. For example, if TEST.COM is a $SYS file - and 'DIR TEST.COM' is issued, no message appears. If 'DIR - TEXT.COM' is issued and TEXT.COM does not exist on disk, the 'No - Files' message is displayed. - - - Command: ERA - Function: To Erase the specified $R/W files from disk - Forms: - ERA <-- Erase both $DIR and $SYS files - Customization Variables: - WIDE FENCE - Examples: - ERA *.ASM <-- Erase all .ASM files - ERA *.* <-- Erase all files - Notes: - If a $R/O file is encountered, a BDOS error message will be - displayed and the procedure is stopped. The user is unsure at - this time as to which files have been erased and which have not - and should check. Sorry for this problem! The ERASE command (to - be given to SIG/M by RLC in the near future) is a solution to - this problem. - - - - - - - - - Page 14 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - - Command: LIST - Function: To Print the specified file on the CP/M LST: device - Forms: - LIST <-- Print the file (no paging) - Customization Variables: - -None- - Examples: - LIST TEST.TXT <-- Print TEST.TXT on LST: - Notes: - If the file has a $SYS attribute, it will be found as well - as those with $DIR attributes. - - - Command: TYPE - Function: To Print the specified file on the CP/M CON: device - Forms: - TYPE <-- Print the file with the paging deflt - TYPE P <-- Print the file with the paging deflt - negated - Customization Variables: - NLINES PGDFLT PGDFLG - Examples: - TYPE TEST.TXT - TYPE TEST.TXT P - Notes: - When the display pauses during paging, type any char to - continue or ^C to abort. ^S also works. - - - Command: SAVE - Function: To Copy the TPA starting at 100H to disk - Forms: - SAVE <-- in DEC - SAVE H <-- in HEX - SAVE S <-- Number of sectors - SAVE H S <-- Number of sectors - Customization Variables: - NUMBASE RAS - Examples: - SAVE 15 MYFILE.TXT <-- 15 pages saved - SAVE FH MYFILE.TXT <-- 15 pages saved - SAVE 10H MYFILE.TXT S <-- 16 sectors (8 pages) saved - Notes: - If the file name to be saved already exists, then SAVE will - exit with the message 'Delete File?'; if the user REALLY wants to - save under this name, he may then type Y or y and the current - file will be deleted and then recreated containing the specified - part of the TPA. - - - - - - - - Page 15 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - - - Command: REN - Function: To Change the name of a disk file - Forms: - REN = - Customization Variables: - RAS - Examples: - REN NEWFILE.TXT=OLDFILE.TXT - Notes: - If already exists, the message 'Delete File?' will - be printed and the user may respond with Y or y to delete the - current and then rename to . - - Command: USER - Function: To Change the current user number - Forms: - USER <-- in DEC - USER H <-- in HEX - Customization Variables: - -None- - Examples: - USER 15 USER FH USER 0 - USER <-- Same as USER 0 - Notes: - -None- - - - Command: DFU - Function: To Temporarily Change the default user number for the - command hierarchy search - Forms: - DFU <-- in DEC - DFU H <-- in HEX - Customization Variables: - -None- - Examples: - DFU 15 DFU FH DFU 0 - DFU <-- Same as DFU 0 - Notes: - See above for explanation. - - - - - - - - - - - - - - - Page 16 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - - - Command: JUMP - Function: To "call" the subroutine at the specified page address - Forms: - JUMP
<--
in HEX - Customization Variables: - NUMBASE RAS - Examples: - JUMP E000 or JUMP E000H <-- Jump to E000H - JUMP <-- Jump to 000H - JUMP 0 <-- Jump to 000H - Notes: - JUMP performs a subroutine "call", so the called routine may - return to the ZCPR by either a RET or a Warm Boot. - - - Command: GO - Function: To "call" the subroutine starting at 100H - Forms: - GO <-- Execute reentrant at 100H - Customization Variables: - RAS - Examples: - GO *.ASM <-- Assuming XDIR is loaded, - gives directory of *.ASM - Notes: - This command is identical in function to JUMP 100H; JUMP, - however, leaves the address as the first entry in CP/M BASE + 80H - (the input line buffer), while GO has no such address. - - - Command: GET - Function: To load a file from disk into memory starting at the - specified page - Forms: - GET
<--
in HEX - Customization Variables: - NUMBASE RAS - Examples: - GET 8000 TEST.80 <-- Load TEST.80 starting at 8000H - GET 100 TEST.80 or GET 100H TEST.80 <-- Load TEST.80 - starting at 100H - GET 0 TEST.80 <-- Load TEST.80 starting at 000H - Notes: - GET searches for the specified file according to the same - command hierarchy search employed by the ZCPR command scanner. - Hence, if the user is on B:/10 and the file is on A:/0 with the - current default user number at 0, GET will search from B:/10 to - B:/0 to A:/0 in looking for the file. - - - - - - - Page 17 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - ZCPR Error Messages - - The following are the error messages issued by ZCPR and - their meanings. - - Message Meaning - - ? Printed after a command or an argument means that such - was invalid - - No File From DIR, this means that DIR did not locate any files - Also from ERA with the same meaning - - All? Issued in response ERA *.*, asks the user is he really - wants to erase all the files. Unlike under the - original CP/M 2.2 CCP, single character input is - required (Y or y for yes and anything else for no) - with NO to end the line - - Full From SAVE, means that there is not enough space on - disk - From GET or command load by CPR, means that there - is not enough space in memory - - Delete File? - From REN or SAVE, means that the file specified already - exists on disk and the user may type Y or y to delete - it and proceed with the REN or SAVE function - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Page 18 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - - Part C - ZCPR Command Levels and How to Use Them - - - ZCPR Version 1.0 and beyond supports three distinct command - levels in its implementation. Each level constitutes a different - way to issue a command for ZCPR to process. - - Command Levels 1 and 2 are common to all implementations of - CP/M and CP/ZM from CP/M Version 1.4. Command Level 1 is that - command level in which the command is issued by the user from his - console terminal. The prompt 'd>' or 'du>' appears on the - terminal, and the user is allowed to enter the command with - editing from the terminal. Command Level 2 is that command level - in which the command is entered from an executing $$$.SUB file. - - In both cases, the command is stored in the internal ZCPR - buffer called CIBUFF (Command Input BUFFer). Under both Command - Levels 1 and 2, the command is placed into this buffer, the - characters of the command line are capitalized, a character count - which indicates the number of characters in the command line is - stored in CBUFF (the byte before CIBUFF), an ending binary 0 is - placed after the last character in the command line, and the - internal pointer CIBPTR (Command Input Buffer PoinTeR) is set to - point to CIBUFF (the first character of the command line). - - Command Level 3 is an extended concept to Command Levels 1 - and 2 which is specifically supported by ZCPR Version 1.0 and - beyond. This command level allows a transient program to place a - command line into CIBUFF and the character count into CBUFF and - have this command line executed by ZCPR. Once control is trans- - ferred to ZCPR to execute the command line, the transient program - which placed the command line loses control and the command is - executed exactly as though it had been typed by the user at his - console terminal. - - In order for a transient program to utilize the Command - Level 3 facility, this program MUST do the following: - - 1. Locate the ZCPR. Since the ZCPR is ALWAYS 2K bytes - in size and located directly under the BDOS, the transient can - locate the ZCPR by examining the BDOS entry page address at - location 7 and subtracting 8 from this number (8 pages = 2K - bytes). The resulting number is the base page address of ZCPR. - - 2. Store the command line in CIBUFF and the character - count in CBUFF. Knowing the base page address of ZCPR, the - following information is useful in doing this: - - - - - - - - Page 19 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - - ORG CPRLOC ;Base Address of ZCPR - JMP CPR ;Enter ZCPR and Execute Default Cmd - JMP CPR1 ;Enter ZCPR and Don't Execute - MBUFF: DB BUFLEN ;Size of CIBUFF in bytes - CBUFF: DS 1 ;Number of Bytes in Command Line - CIBUFF: DS BUFLEN ;Buffer for Command Line - DS 1 ;Buffer for Ending 0 (set by ZCPR) - CIBPTR: DS 2 ;Address of CIBUFF (set by ZCPR) - - - 3. Obtain the User/Disk Flag. Location 4 contains - this number, but the user may select a flag of his choice. This - flag is one byte long, and the high-order nybble (4 bits) - contains the user number and the low-order nybble contains the - disk number to process the command from. The User/Disk Flag is - to be passed to ZCPR in the C Register. - - 4. When ready, transfer control to ZCPR to process the - command by JMPing to the base address of ZCPR. The first JMP in - the JMP Table given above is at this address. At this time, ZCPR - will log in the user and disk in the User/Disk Flag and process - the Command Level 3 Command Line. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Page 20 - - - - - - ZCPR - A Z80 Replacement for the CP/M CCP - - - - - The following is a sample program which illustrates the - steps outlined above: - - ; - ; Demonstration of Command Level 3 Facility by RLC - ; - udflag equ 4 ;Address of User/Disk Flag - bdos equ 5 ;Address of BDOS Entry Point - - org 100h - - lxi d,prmpt ;Print User Prompt - mvi c,9 ;PRINT function - call bdos - - lhld bdos+1 ;Get address of BDOS - mov a,h ;High-Order Address in A - sui 8 ;A=High-Order Address of CPR - mov h,a ;HL=Address of CPR - mvi l,0 - shld cpr ;Save address in buffer - - lxi d,6 ;Point to command line buffer - dad d ;HL points to command line buffer - xchg ;DE points to command line buffer - mvi c,10 ;READLN into this buffer - call bdos - - lhld cpr ;Get Address of CPR - lda udflag ;Get User/Disk Flag - mov c,a ; ... in C - pchl ;Run Command Line - - cpr: ds 2 ;CPR Address buffer - prmpt: db 'User Command? $' - - - - - - - Enjoy using ZCPR! - -- RLC - - - - - - - - - - - - - Page 21 \ No newline at end of file diff --git a/Source/Fonts/Build.cmd b/Source/Fonts/Build.cmd new file mode 100644 index 00000000..cec086aa --- /dev/null +++ b/Source/Fonts/Build.cmd @@ -0,0 +1,20 @@ +@echo off +setlocal + +set TOOLS=../../Tools + +set PATH=%TOOLS%\lzsa;%TOOLS%\fonttool;%PATH% + +echo. +echo Preparing compressed font files... + +lzsa -f2 -r font8x8u.bin font8x8c.bin +lzsa -f2 -r font8x11u.bin font8x11c.bin +lzsa -f2 -r font8x16u.bin font8x16c.bin + +fonttool font8x8u.bin > font8x8u.asm +fonttool font8x11u.bin > font8x11u.asm +fonttool font8x16u.bin > font8x16u.asm +fonttool font8x8c.bin > font8x8c.asm +fonttool font8x11c.bin > font8x11c.asm +fonttool font8x16c.bin > font8x16c.asm diff --git a/Source/Fonts/Clean.cmd b/Source/Fonts/Clean.cmd new file mode 100644 index 00000000..8712ec61 --- /dev/null +++ b/Source/Fonts/Clean.cmd @@ -0,0 +1,6 @@ +@echo off +setlocal + + +if exist *.asm del *.asm +if exist *c.bin del *c.bin diff --git a/Source/Fonts/Makefile b/Source/Fonts/Makefile new file mode 100644 index 00000000..c9a06b35 --- /dev/null +++ b/Source/Fonts/Makefile @@ -0,0 +1,28 @@ +OBJECTS = \ + font8x8u.asm font8x11u.asm font8x16u.asm \ + font8x8c.asm font8x11c.asm font8x16c.asm + +OTHERS = font8x8c.bin font8x11c.bin font8x16c.bin + +DEST = ../HBIOS +TOOLS = ../../Tools + +include $(TOOLS)/Makefile.inc + +# +# these 2 rules cancel out the default rules from the Makefile.inc +# +%.rel: %.asm +%.bin: %.asm + +font8x8c.bin: font8x8u.bin + $(BINDIR)/lzsa -f2 -r $< $@ + +font8x11c.bin: font8x11u.bin + $(BINDIR)/lzsa -f2 -r $< $@ + +font8x16c.bin: font8x16u.bin + $(BINDIR)/lzsa -f2 -r $< $@ + +%.asm: %.bin + $(BINDIR)/bin2asm $< > $@ diff --git a/Source/Fonts/font8x11.png b/Source/Fonts/font8x11.png new file mode 100644 index 00000000..1e7ecbab Binary files /dev/null and b/Source/Fonts/font8x11.png differ diff --git a/Source/Fonts/font8x11u.bin b/Source/Fonts/font8x11u.bin new file mode 100644 index 00000000..330a44e0 Binary files /dev/null and b/Source/Fonts/font8x11u.bin differ diff --git a/Source/Fonts/font8x16.png b/Source/Fonts/font8x16.png new file mode 100644 index 00000000..b19ca57f Binary files /dev/null and b/Source/Fonts/font8x16.png differ diff --git a/Source/Fonts/font8x16u.bin b/Source/Fonts/font8x16u.bin new file mode 100644 index 00000000..c7ed1f51 Binary files /dev/null and b/Source/Fonts/font8x16u.bin differ diff --git a/Source/Fonts/font8x8.png b/Source/Fonts/font8x8.png new file mode 100644 index 00000000..b943becf Binary files /dev/null and b/Source/Fonts/font8x8.png differ diff --git a/Source/Fonts/font8x8u.bin b/Source/Fonts/font8x8u.bin new file mode 100644 index 00000000..17f16d25 Binary files /dev/null and b/Source/Fonts/font8x8u.bin differ diff --git a/Source/Fonts/fonts.txt b/Source/Fonts/fonts.txt new file mode 100644 index 00000000..2f720b44 --- /dev/null +++ b/Source/Fonts/fonts.txt @@ -0,0 +1,45 @@ +Font files for ROMWBW. + +There are three fonts associated with ROMWBW supported hardware - ECB-SCG, ECB-CVDU and the ECB-VGA3. + +Name Format Size Board & Display Mode +------------------------------------------------------------------------------------ +font8x8u.bin 8x8 2048 ECB-SCG, ECB-VGA3 (80x60) +font8x11u.bin 8x11 2816 ECB-VGA3 (80x43) +font8x16u.bin 8x16 4096 ECB-CVDU (80x25), ECB-VGA3 (80x24, 80x25, 80x30) + +For inclusion in HBIOS the .bin format files must be convert to assembler .asm format. +This is acheived using the fonttool utility and is completed automatically as part of the build process. +i.e. fonts files are converted to .asm format and then copied to the HBIOS directory. + +To replace a font, simply copy it to the Fonts directory using the same naming convention above, +ensuring format and size match. Then complete a new build process. + +To reduce the size of the HBIOS image, fonts can be compressed by using the USEZLSA2 equate. + +Use the following in your custom configuration to turn on font compression: + +USELZSA2 .SET TRUE + +Compressed fonts are created using lzsa utility. + +During the build process, fonts are compressed using the command line compressor by Emmanuel Marty +The compression is done as follows: + +lzsa.exe -f2 -r + +where option -f2 selects lzsa version 2 compression. +where option -r asks for the generation of raw (frame-less) data. + +Original compression source code and files available here: + +Latest github code: + https://github.com/emmanuel-marty/lzsa +Implementation example (older version): + https://cpcrulez.fr/applications_tools_cruncher_LZSA2_Z80.htm +x86 lzsa compressor application: + http://www.pouet.net/prod.php?which=81573 + + + + diff --git a/Source/Forth/Build.cmd b/Source/Forth/Build.cmd index d7ce3630..08fb735b 100644 --- a/Source/Forth/Build.cmd +++ b/Source/Forth/Build.cmd @@ -11,5 +11,7 @@ set ZXBINDIR=%TOOLS%/cpm/bin/ set ZXLIBDIR=%TOOLS%/cpm/lib/ set ZXINCDIR=%TOOLS%/cpm/include/ -zx z80mr camel80 -zx MLOAD25 -camel80.bin=camel80.hex +zx zsm =camel80.azm +zx link -CAMEL80.BIN=CAMEL80 + + diff --git a/Source/Forth/Clean.cmd b/Source/Forth/Clean.cmd index e2e6145a..ad58c78d 100644 --- a/Source/Forth/Clean.cmd +++ b/Source/Forth/Clean.cmd @@ -5,3 +5,5 @@ if exist *.bin del *.bin if exist *.lst del *.lst if exist *.prn del *.prn if exist *.hex del *.hex +if exist *.rel del *.rel +if exist *.sym del *.sym diff --git a/Source/Forth/Makefile b/Source/Forth/Makefile new file mode 100644 index 00000000..51fbdee2 --- /dev/null +++ b/Source/Forth/Makefile @@ -0,0 +1,6 @@ +OBJECTS = camel80.bin +TOOLS = ../../Tools +OTHERS = *.rel +DEST = ../HBIOS + +include $(TOOLS)/Makefile.inc diff --git a/Source/Forth/camel80.azm b/Source/Forth/camel80.azm index b97396a4..c74af4fc 100644 --- a/Source/Forth/camel80.azm +++ b/Source/Forth/camel80.azm @@ -1,5 +1,5 @@ CIODEV_CONSOLE EQU 0D0h -CIOIN EQU 00h ; CHARACTER INPPUT +CIOIN EQU 00h ; CHARACTER INPUT CIOOUT EQU 01h ; CHARACTER OUTPUT CIOIST EQU 02h ; CHARACTER INPUT STATUS BID_BOOT EQU 00h @@ -35,7 +35,7 @@ FTH_LOC EQU 0200h ; ; =============================================== ; CAMEL80.AZM: Code Primitives -; Source code is for the Z80MR macro assembler. +; Source code is for the ZSM assembler. ; Forth words are documented as follows: ;x NAME stack -- stack description ; where x=C for ANS Forth Core words, X for ANS @@ -60,6 +60,12 @@ FTH_LOC EQU 0200h ; 05 Nov 18 v1.02 Initial ROMWBW HBIOS version. ; 10-Nov 18 v1.02 New org address. ; b1ackmai1er difficultylevelhigh@gmail.com +; 19-Oct 19 v1.02 Convert to zsm assembler which +; identified and fixed incorrect +; case conversion when lowercase +; keywords are being passed in a +; macro. +; b1ackmai1er difficultylevelhigh@gmail.com ; =============================================== ; Macros to define Forth headers ; HEAD label,length,name,action @@ -75,25 +81,25 @@ FTH_LOC EQU 0200h DOCODE EQU 0 ; flag to indicate CODE words link DEFL 0 ; link to previous Forth word -head MACRO #label,#length,#name,#action +head MACRO label,length,name,action DW link DB 0 link DEFL $ - DB #length,'#name' -#label: - IF .NOT.(#action=DOCODE) - call #action + DB length,"&name" +label: + IFF (action EQ DOCODE) + call action ENDIF ENDM -immed MACRO #label,#length,#name,#action +immed MACRO label,length,name,action DW link DB 1 link DEFL $ - DB #length,'#name' -#label: - IF .NOT.(#action=DOCODE) - call #action + DB length,"&name" +label: + IFF (action EQ DOCODE) + call action ENDIF ENDM @@ -120,11 +126,13 @@ nexthl MACRO ENDM ; RESET AND INTERRUPT VECTORS =================== -; ...are not used in the CP/M implementation +; ...are not used in the ROMWBW implementation ; Instead, we have the... -; CP/M ENTRY POINT - org FTH_LOC ; Execute address +; RELOCATED ENTRY POINT + + .PHASE FTH_LOC + reset: ld hl,0FDFFh ; HBIOS address, rounded down ld l,0 ; = end of avail.mem (EM) dec h ; EM-100h @@ -384,7 +392,7 @@ poptos: pop bc DW SWOP,OVER,EXIT ;C >R x -- R: -- x push to return stack - head TOR,2,>R,docode + head TOR,2,!>R,docode dec ix ; push TOS onto rtn stk ld (ix+0),b dec ix @@ -393,7 +401,7 @@ poptos: pop bc next ;C R> -- x R: x -- pop from return stack - head RFROM,2,R>,docode + head RFROM,2,R!>,docode push bc ; push old TOS ld c,(ix+0) ; pop top rtn stk item inc ix ; to TOS @@ -418,7 +426,7 @@ poptos: pop bc next ;Z SP! a-addr -- set data stack pointer - head SPSTORE,3,SP!,docode + head SPSTORE,3,SP!!,docode ld h,b ld l,c ld sp,hl @@ -433,7 +441,7 @@ poptos: pop bc next ;Z RP! a-addr -- set return stack pointer - head RPSTORE,3,RP!,docode + head RPSTORE,3,RP!!,docode push bc pop ix pop bc @@ -442,7 +450,7 @@ poptos: pop bc ; MEMORY AND I/O OPERATIONS ===================== ;C ! x a-addr -- store cell in memory - head STORE,1,!,docode + head STORE,1,!!,docode ld h,b ; address in hl ld l,c pop bc ; data in bc @@ -453,7 +461,7 @@ poptos: pop bc next ;C C! char c-addr -- store char in memory - head CSTORE,2,C!,docode + head CSTORE,2,C!!,docode ld h,b ; address in hl ld l,c pop bc ; data in bc @@ -478,7 +486,7 @@ poptos: pop bc next ;Z PC! char c-addr -- output char to port - head PCSTORE,3,PC!,docode + head PCSTORE,3,PC!!,docode pop hl ; char in L out (c),l ; to port (BC) pop bc ; pop new TOS @@ -588,7 +596,7 @@ mplus1: pop de ; restore saved IP next ;Z >< x1 -- x2 swap bytes (not ANSI) - head swapbytes,2,><,docode + head swapbytes,2,!>!<,docode ld a,b ld b,c ld c,a @@ -632,7 +640,7 @@ rsh2: djnz rsh1 next ;C +! n/u a-addr -- add cell to memory - head PLUSSTORE,2,+!,docode + head PLUSSTORE,2,+!!,docode pop hl ld a,(bc) ; low byte add a,l @@ -657,7 +665,7 @@ rsh2: djnz rsh1 next ;C 0< n -- flag true if TOS negative - head ZEROLESS,2,0<,docode + head ZEROLESS,2,0!<,docode sla b ; sign bit -> cy flag sbc a,a ; propagate cy through A ld b,a ; put 0000 or FFFF in TOS @@ -674,11 +682,11 @@ tosfalse: ld bc,0 next ;X <> x1 x2 -- flag test not eq (not ANSI) - head NOTEQUAL,2,<>,docolon + head NOTEQUAL,2,!,docolon DW EQUAL,ZEROEQUAL,EXIT ;C < n1 n2 -- flag test n1 n1 n2 -- flag test n1>n2, signed - head GREATER,1,>,docolon + head GREATER,1,!>,docolon DW SWOP,LESS,EXIT ;C U< u1 u2 -- flag test u1 u1 u2 -- flag u1>u2 unsgd (not ANSI) - head UGREATER,2,U>,docolon + head UGREATER,2,U!>,docolon DW SWOP,ULESS,EXIT ; LOOP AND BRANCH OPERATIONS ==================== @@ -949,7 +957,7 @@ cmovedone: exx ;X CMOVE> c-addr1 c-addr2 u -- move from top ; as defined in the ANSI optional String word set - head CMOVEUP,6,CMOVE>,docode + head CMOVEUP,6,CMOVE!>,docode push bc exx pop bc ; count @@ -1048,8 +1056,8 @@ sdiff: ; mismatch! undo last 'cpi' increment ld c,a snext: next -*INCLUDE camel80d.azm ; CPU Dependencies -*INCLUDE camel80h.azm ; High Level words +INCLUDE camel80d.azm ; CPU Dependencies +INCLUDE camel80h.azm ; High Level words lastword EQU link ; nfa of last word in dict. enddict EQU $ ; user's code starts here @@ -1057,6 +1065,7 @@ enddict EQU $ ; user's code starts here ds (FTH_SIZ-(enddict-reset)-1) nop + .DEPHASE END diff --git a/Source/Forth/camel80d.azm b/Source/Forth/camel80d.azm index 0dd13e3e..b2d15fda 100644 --- a/Source/Forth/camel80d.azm +++ b/Source/Forth/camel80d.azm @@ -74,7 +74,7 @@ noop: next ;C >BODY xt -- a-addr adrs of param field ; 3 + ; Z80 (3 byte CALL) - head TOBODY,5,>BODY,docolon + head TOBODY,5,!>BODY,docolon DW LIT,3,PLUS,EXIT ;X COMPILE, xt -- append execution token @@ -82,7 +82,7 @@ noop: next ; it is defined in the ANSI standard as COMPILE,. ; On a DTC Forth this simply appends xt (like , ) ; but on an STC Forth this must append 'CALL xt'. - head COMMAXT,8,'COMPILE,',docode + head COMMAXT,8,COMPILE!,,docode jp COMMA ;Z !CF adrs cfa -- set code action of a word @@ -90,13 +90,13 @@ noop: next ; 1+ ! ; Z80 VERSION ; Depending on the implementation this could ; append CALL adrs or JUMP adrs. - head STORECF,3,!CF,docolon + head STORECF,3,!!CF,docolon DW LIT,0CDH,OVER,CSTORE DW ONEPLUS,STORE,EXIT ;Z ,CF adrs -- append a code field ; HERE !CF 3 ALLOT ; Z80 VERSION (3 bytes) - head COMMACF,3,',CF',docolon + head COMMACF,3,!,CF,docolon DW HERE,STORECF,LIT,3,ALLOT,EXIT ;Z !COLON -- change code field to docolon @@ -104,7 +104,7 @@ noop: next ; This should be used immediately after CREATE. ; This is made a distinct word, because on an STC ; Forth, colon definitions have no code field. - head STORCOLON,6,'!COLON',docolon + head STORCOLON,6,!!COLON,docolon DW LIT,-3,ALLOT DW LIT,docolon,COMMACF,EXIT @@ -112,7 +112,7 @@ noop: next ; ['] EXIT ,XT ; ; This is made a distinct word, because on an STC ; Forth, it appends a RET instruction, not an xt. - head CEXIT,5,',EXIT',docolon + head CEXIT,5,!,EXIT,docolon DW LIT,EXIT,COMMAXT,EXIT ; CONTROL STRUCTURES ============================ @@ -123,21 +123,21 @@ noop: next ; xt is the branch operator to use, e.g. qbranch ; or (loop). It does NOT append the destination ; address. On the Z80 this is equivalent to ,XT. - head COMMABRANCH,7,',BRANCH',docode + head COMMABRANCH,7,!,BRANCH,docode jp COMMA ;Z ,DEST dest -- append a branch address ; This appends the given destination address to ; the branch instruction. On the Z80 this is ',' ; ...other CPUs may use relative addressing. - head COMMADEST,5,',DEST',docode + head COMMADEST,5,!,DEST,docode jp COMMA ;Z !DEST dest adrs -- change a branch dest'n ; Changes the destination address found at 'adrs' ; to the given 'dest'. On the Z80 this is '!' ; ...other CPUs may need relative addressing. - head STOREDEST,5,'!DEST',docode + head STOREDEST,5,!!DEST,docode jp STORE ; HEADER STRUCTURE ============================== diff --git a/Source/Forth/camel80h.azm b/Source/Forth/camel80h.azm index 10646031..b3b6f079 100644 --- a/Source/Forth/camel80h.azm +++ b/Source/Forth/camel80h.azm @@ -23,7 +23,7 @@ ; ; =============================================== ; CAMEL80H.AZM: High Level Words -; Source code is for the Z80MR macro assembler. +; Source code is for the ZSM assembler. ; Forth words are documented as follows: ;* NAME stack -- stack description ; Word names in upper case are from the ANS @@ -56,7 +56,7 @@ ;C >IN -- a-addr holds offset into TIB ; 2 USER >IN - head TOIN,3,>IN,douser + head TOIN,3,!>IN,douser dw 2 ;C BASE -- a-addr holds conversion radix @@ -132,7 +132,7 @@ TICKSOURCE: call douser ; in name! ;C S>D n -- d single -> double prec. ; DUP 0< ; - head STOD,3,S>D,docolon + head STOD,3,S!>D,docolon dw DUP,ZEROLESS,EXIT ;Z ?NEGATE n1 n2 -- n3 negate n1 if n2 negative @@ -254,7 +254,7 @@ MIN1: dw DROP,EXIT ;C 2! x1 x2 a-addr -- store 2 cells ; SWAP OVER ! CELL+ ! ; ; the top of stack is stored at the lower adrs - head TWOSTORE,2,2!,docolon + head TWOSTORE,2,2!!,docolon dw SWOP,OVER,STORE,CELLPLUS,STORE,EXIT ;C 2DROP x1 x2 -- drop 2 cells @@ -348,25 +348,25 @@ TYP5: DW EXIT ;Z (S") -- c-addr u run-time code for S" ; R> COUNT 2DUP + ALIGNED >R ; - head XSQUOTE,4,(S"),docolon + head XSQUOTE,4,(S""!),docolon DW RFROM,COUNT,TWODUP,PLUS,ALIGNED,TOR DW EXIT ;C S" -- compile in-line string ; COMPILE (S") [ HEX ] ; 22 WORD C@ 1+ ALIGNED ALLOT ; IMMEDIATE - immed SQUOTE,2,S",docolon + immed SQUOTE,2,S"",docolon DW LIT,XSQUOTE,COMMAXT DW LIT,22H,WORD,CFETCH,ONEPLUS DW ALIGNED,ALLOT,EXIT ;C ." -- compile string to print ; POSTPONE S" POSTPONE TYPE ; IMMEDIATE - immed DOTQUOTE,2,.",docolon + immed DOTQUOTE,2,."",docolon DW SQUOTE DW LIT,TYPE,COMMAXT DW EXIT - + ; NUMERIC OUTPUT ================================ ; Numeric conversion is done l.s.digit first, so ; the output buffer is built backwards in memory. @@ -394,12 +394,12 @@ TYP5: DW EXIT ;C <# -- begin numeric conversion ; PAD HP ! ; (initialize Hold Pointer) - head LESSNUM,2,<#,docolon + head LESSNUM,2,!<#,docolon DW PAD,HP,STORE,EXIT ;Z >digit n -- c convert to 0..9A..Z ; [ HEX ] DUP 9 > 7 AND + 30 + ; - head TODIGIT,6,>DIGIT,docolon + head TODIGIT,6,!>DIGIT,docolon DW DUP,LIT,9,GREATER,LIT,7,AND,PLUS DW LIT,30H,PLUS,EXIT @@ -417,7 +417,7 @@ NUMS1: DW NUM,TWODUP,OR,ZEROEQUAL,qbranch,NUMS1 ;C #> ud1 -- c-addr u end conv., get string ; 2DROP HP @ PAD OVER - ; - head NUMGREATER,2,#>,docolon + head NUMGREATER,2,#!>,docolon DW TWODROP,HP,FETCH,PAD,OVER,MINUS,EXIT ;C SIGN n -- add minus sign if n<0 @@ -434,7 +434,7 @@ SIGN1: DW EXIT ;C . n -- display n signed ; <# DUP ABS 0 #S ROT SIGN #> TYPE SPACE ; - head DOT,1,'.',docolon + head DOT,1,.,docolon DW LESSNUM,DUP,ABS,LIT,0,NUMS DW ROT,SIGN,NUMGREATER,TYPE,SPACE,EXIT @@ -465,12 +465,12 @@ SIGN1: DW EXIT ;C , x -- append cell to dict ; HERE ! 1 CELLS ALLOT ; - head COMMA,1,',',docolon + head COMMA,1,!,,docolon dw HERE,STORE,lit,1,CELLS,ALLOT,EXIT ;C C, char -- append char to dict ; HERE C! 1 CHARS ALLOT ; - head CCOMMA,2,'C,',docolon + head CCOMMA,2,C!,,docolon dw HERE,CSTORE,lit,1,CHARS,ALLOT,EXIT ; INTERPRETER =================================== @@ -491,7 +491,7 @@ SIGN1: DW EXIT ;Z >counted src n dst -- copy to counted str ; 2DUP C! CHAR+ SWAP CMOVE ; - head TOCOUNTED,8,>COUNTED,docolon + head TOCOUNTED,8,!>COUNTED,docolon DW TWODUP,CSTORE,CHARPLUS,SWOP,CMOVE,EXIT ;C WORD char -- c-addr n word delim'd by char @@ -516,12 +516,12 @@ WORD1: DW RFROM,RFROM,ROT,MINUS,TOIN,PLUSSTORE ;Z NFA>LFA nfa -- lfa name adr -> link field ; 3 - ; - head NFATOLFA,7,NFA>LFA,docolon + head NFATOLFA,7,NFA!>LFA,docolon DW LIT,3,MINUS,EXIT ;Z NFA>CFA nfa -- cfa name adr -> code field ; COUNT 7F AND + ; mask off 'smudge' bit - head NFATOCFA,7,NFA>CFA,docolon + head NFATOCFA,7,NFA!>CFA,docolon DW COUNT,LIT,07FH,AND,PLUS,EXIT ;Z IMMED? nfa -- f fetch immediate flag @@ -599,7 +599,7 @@ QSIGN1: DW EXIT ; R> M+ 2SWAP ; 1 /STRING ; REPEAT ; - head TONUMBER,7,>NUMBER,docolon + head TONUMBER,7,!>NUMBER,docolon TONUM1: DW DUP,qbranch,TONUM3 DW OVER,CFETCH,DIGITQ DW ZEROEQUAL,qbranch,TONUM2,DROP,EXIT @@ -701,7 +701,7 @@ QABO1: DW TWODROP,EXIT ;C ABORT" i*x 0 -- i*x R: j*x -- j*x x1=0 ;C i*x x1 -- R: j*x -- x1<>0 ; POSTPONE S" POSTPONE ?ABORT ; IMMEDIATE - immed ABORTQUOTE,6,ABORT",docolon + immed ABORTQUOTE,6,ABORT"",docolon DW SQUOTE DW LIT,QABORT,COMMAXT DW EXIT @@ -753,14 +753,14 @@ TICK: call docolon ; R> adrs of headless DOES> def'n ; LATEST @ NFA>CFA code field to fix up ; !CF ; - head XDOES,7,(DOES>),docolon + head XDOES,7,(DOES!>),docolon DW RFROM,LATEST,FETCH,NFATOCFA,STORECF DW EXIT ;C DOES> -- change action of latest def'n ; COMPILE (DOES>) ; dodoes ,CF ; IMMEDIATE - immed DOES,5,DOES>,docolon + immed DOES,5,DOES!>,docolon DW LIT,XDOES,COMMAXT DW LIT,dodoes,COMMACF,EXIT @@ -807,7 +807,7 @@ TICK: call docolon ;C ; ; REVEAL ,EXIT ; POSTPONE [ ; IMMEDIATE - immed SEMICOLON,1,';',docolon + immed SEMICOLON,1,!;,docolon DW REVEAL,CEXIT DW LEFTBRACKET,EXIT @@ -911,12 +911,12 @@ POST2: DW EXIT ;Z >L x -- L: -- x move to leave stack ; CELL LP +! LP @ ! ; (L stack grows up) - head TOL,2,>L,docolon + head TOL,2,!>L,docolon DW CELL,LP,PLUSSTORE,LP,FETCH,STORE,EXIT ;Z L> -- x L: x -- move from leave stack ; LP @ @ CELL NEGATE LP +! ; - head LFROM,2,L>,docolon + head LFROM,2,L!>,docolon DW LP,FETCH,FETCH DW CELL,NEGATE,LP,PLUSSTORE,EXIT @@ -1020,8 +1020,9 @@ DOTS2: DW EXIT DW UINIT,U0,NINIT,CMOVE ; DW LIT,80h,COUNT,INTERPRET DW XSQUOTE - DB 55,'Z80 CamelForth v1.02 25 Jan 1995, ROMWBW 10 Nov 2018' + DB 59,0dh,0ah,0dh,0ah + DB 'Z80 CamelForth v1.02 25 Jan 1995, RomWBW 19 Oct 2019' DB 0dh,0ah DW TYPE,ABORT ; ABORT never returns -; DON'T FORGET TO UPDATE THE BYTE COUNT IF YOU CHANCGE THE SIZE OF THE BOOT MSG +; DON'T FORGET TO UPDATE THE BYTE COUNT IF YOU CHANGE THE SIZE OF THE BOOT MSG diff --git a/Source/HBIOS/API.txt b/Source/HBIOS/API.txt index 74c8794c..7b97b0cd 100644 --- a/Source/HBIOS/API.txt +++ b/Source/HBIOS/API.txt @@ -57,6 +57,11 @@ GET ($F8): BC=Function/Subfunction A=Result DE:HL=Timer Value (32 bit) + SECONDS ($D1): + BC=Function/Subfunction A=Result + DE:HL=Seconds Value (32 bit) + C=Ticks within current second + BOOTINFO ($E0): BC=Function/Subfunction A=Result DE=Boot Volume (Disk Unit/Slice) @@ -85,6 +90,10 @@ SET ($F9): BC=Function/Subfunction A=Result DE:HL=Timer Value (32 bit) + SECONDS ($D1): + BC=Function/Subfunction A=Result + DE:HL=Seconds Value (32 bit) + BOOTINFO ($E0): BC=Function/Subfunction A=Result DE=Boot Volume (Disk Unit/Slice) @@ -132,7 +141,7 @@ INT ($FC): Interrupt vector management functions INTSET ($20): Set interrupt vector BC=Function/Subfunction A=Result HL=Interrupt Vector HL=Previous Vector - E=Interrupt Vector Table Position DE=Interrupt Routing Engine Address + E=Interrupt Vector Table Position Set the Interrupt Vector for the specified Interrupt Vector Table Position to the specified Interrupt Vector. The previous value at the specified table position @@ -151,28 +160,25 @@ INT ($FC): Interrupt vector management functions must be saved and restored by the interrupt handler. Interrupt handlers are different for IM1 or IM2. - + For IM1: The new interrupt handler is responsible for chaining (JP) to the previous vector - if the interrupt is not handled. The interrupt handler must return with ZF set - if interrupt is handled and ZF cleared if not handled. + if the interrupt is not handled. If the interrupt is handled, the new handler may + simply return (RET) with ZF cleared (NZ). For IM2: - The interrupt handler requires an invocation stub separate from the actual interrupt - handling code. The stub must be: + The new interrupt handler may either replace or hook the previous interrupt + handler. To replace the previous interrupt handler, the new handler just returns + (RET) when done. To hook the previous handler, the new handler can chain (JP) + to the previous vector. Note that initially all IM2 interrupt vectors are set + to be handled as “BAD†meaning that the interrupt is unexpected. In most cases, + you do not want to chain to the previous vector because it will cause the + interrupt to display a “BAD INT†system panic message. - PUSH HL - LD HL, - JP - - When calling Set Interrupt Vector, the address of the stub must be provided for the - Interrupt Vector parameter. The address of the Interrupt Routing Engine will be - returned in DE and must be inserted into the stub code as indicated above. In the - case of IM2 mode interrutps, the actual interrupt handler should not chain to the - previous entry. The new interrupt handler must assume all responsibilities for - the specific interrupt slot being occupied. + The interrupt framework will take care of issuing an EI and RETI instruction. + Do not put these instructions in your new handler. If the caller is transient, then the caller must remove the new interrupt handler and restore the original one prior to termination. This is accomplished by calling this @@ -236,8 +242,9 @@ DEVICE ($06): Serial Device Attributes Byte: 7: 0=RS-232, 1=Terminal + 6: 1=Parallel Port - If Terminal, 3-0 is attached Video Unit # + If device is of type Terminal, bits 3-0 indicate attached Video Unit # ============== Disk Functions @@ -269,15 +276,16 @@ SEEK ($12): READ ($13) / WRITE ($14) / VERIFY ($15): BC=Function/Unit A=Result HL=Buffer Address E=Blocks Read + D=Buffer Bank ID E=Block Count - Read, write, or verify block count sectors to buffer address starting at current target + Read, write, or verify block count sectors to/from buffer bank/address starting at current target sector. Current sector must be established by prior seek function; however, multiple read/write/verify function calls can be made after a seek function. Current sector is - incremented after each sector successfully read. On error, current sector is sector is - sector where error occurred. Blocks read indicates number of sectors successfully read. + incremented after each sector successfully read/written. On error, current sector is + sector where error occurred. Block count indicates number of sectors successfully read/written. Caller must ensure buffer address is large enough to contain data for all sectors - requested. + read/written. If buffer is not in upper 32K, then sectors will be double buffered. FORMAT ($16): BC=Function/Unit A=Result diff --git a/Source/HBIOS/Build.ps1 b/Source/HBIOS/Build.ps1 index df80130b..e82b67aa 100644 --- a/Source/HBIOS/Build.ps1 +++ b/Source/HBIOS/Build.ps1 @@ -19,17 +19,23 @@ param([string]$Platform = "", [string]$Config = "", [string]$RomSize = "512", [s # setup mechanism so that multiple configuration are not needed. When building for UNA, the pre-built # UNA BIOS is simply imbedded, it is not built here. # +$PlatformListZ80 = "SBC", "ZETA", "ZETA2", "RCZ80", "EZZ80", "UNA" +$PlatformListZ180 = "N8", "MK4", "RCZ180", "SCZ180", "DYNO" # # Establish the build platform. It may have been passed in on the command line. Validate # $Platform and loop requesting a new value as long as it is not valid. The valid platform # names are just hard-coded for now. # +$PlatformList = $PlatformListZ80 + $PlatformListZ180 +$Prompt = "Platform [" +ForEach ($PlatformName in $PlatformList) {$Prompt += $PlatformName + "|"} +$Prompt = $Prompt.Substring(0, $Prompt.Length - 1) + "]" $Platform = $Platform.ToUpper() while ($true) { - if (($Platform -eq "SBC") -or ($Platform -eq "ZETA") -or ($Platform -eq "ZETA2") -or ($Platform -eq "RC") -or ($Platform -eq "RC180") -or ($Platform -eq "N8") -or ($Platform -eq "MK4") -or ($Platform -eq "UNA")) {break} - $Platform = (Read-Host -prompt "Platform [SBC|ZETA|ZETA2|RC|RC180|N8|MK4|UNA]").Trim().ToUpper() + if ($PlatformList -contains $Platform) {break} + $Platform = (Read-Host -prompt $Prompt).Trim().ToUpper() } # @@ -66,7 +72,7 @@ while ($true) # TASM should be invoked with the proper CPU type. Below, the CPU type is inferred # from the platform. # -if (($Platform -eq "N8") -or ($Platform -eq "MK4") -or ($Platform -eq "RC180")) {$CPUType = "180"} else {$CPUType = "80"} +if ($PlatformListZ180 -contains $Platform) {$CPUType = "180"} else {$CPUType = "80"} # # The $RomName variable determines the name of the image created by the script. By default, @@ -101,13 +107,13 @@ $ComFile = "${OutDir}/${RomName}.com" # Final name of COM image (command line lo $ImgFile = "${OutDir}/${RomName}.img" # Final name of IMG image (memory loadable HBIOS/CBIOS image) # Select the proper CBIOS to include in the ROM. UNA is special. -if ($Platform -eq "UNA") {$CBiosFile = '../CBIOS/cbios_una.bin'} else {$CBiosFile = '../CBIOS/cbios_wbw.bin'} +if ($Platform -eq "UNA") {$Bios = 'una'} else {$Bios = 'wbw'} # List of RomWBW proprietary apps to imbed in ROM disk. -$RomApps = "assign","fdu","format","mode","osldr","rtc","survey","syscopy","sysgen","talk","timer","xm","inttest" +$RomApps = "assign","fdu","format","mode","rtc","survey","syscopy","sysgen","talk","timer","xm","inttest" "" -"Building ${RomName}: ${ROMSize}KB ROM configuration ${Config} for Z${CPUType}..." +"Building ${RomName} ${ROMSize}KB ROM configuration ${Config} for Z${CPUType}..." "" # Current date/time is queried here to be subsequently imbedded in image @@ -116,7 +122,7 @@ $TimeStamp = '"' + (Get-Date -Format 'yyyy-MM-dd') + '"' # Function to run TASM and throw an exception if an error occurs. Function Asm($Component, $Opt, $Architecture=$CPUType, $Output="${Component}.bin", $List="${Component}.lst") { - $Cmd = "tasm -t${Architecture} -g3 ${Opt} ${Component}.asm ${Output} ${List}" + $Cmd = "tasm -t${Architecture} -g3 -e ${Opt} ${Component}.asm ${Output} ${List}" $Cmd | write-host Invoke-Expression $Cmd | write-host if ($LASTEXITCODE -gt 0) {throw "TASM returned exit code $LASTEXITCODE"} @@ -134,7 +140,7 @@ Function Concat($InputFileList, $OutputFile) # # Since TASM has no mechanism to include files dynamically based on variables, a file -# if built on-the-fly here for imbedding in the build process. This file is basically +# is built on-the-fly here for imbedding in the build process. This file is basically # just used to include the platform and config files. It also passes in some values # from the build to include in the build. @@ -143,39 +149,33 @@ Function Concat($InputFileList, $OutputFile) ; #DEFINE TIMESTAMP ${TimeStamp} ; -PLATFORM .EQU PLT_${Platform} ; HARDWARE PLATFORM -ROMSIZE .EQU ${ROMSize} ; SIZE OF ROM IN KB +ROMSIZE .EQU ${ROMSize} ; -;#INCLUDE "${PlatformConfigFile}" #INCLUDE "${ConfigFile}" ; "@ | Out-File "build.inc" -Encoding ASCII -# Bring over previously assembled binary copy of the CP/M CCP and BDOS images for later use. -Copy-Item '..\cpm22\os2ccp.bin' 'ccp.bin' -Copy-Item '..\cpm22\os3bdos.bin' 'bdos.bin' +# # Bring over previously assembled binary copy of Forth for later use. +# Copy-Item '..\Forth\camel80.bin' 'camel80.bin' -# Bring over previously assembled binary copy of the ZSystem CCP and BDOS images for later use. -Copy-Item '..\zcpr-dj\zcpr.bin' 'zcpr.bin' -Copy-Item '..\zsdos\zsdos.bin' 'zsdos.bin' - -# Bring over previously assembled binary copy of Forth for later use. -Copy-Item '..\Forth\camel80.bin' 'camel80.bin' +# Bring over previously generated font files. +Copy-Item '..\Fonts\font*.asm' '.' # Assemble individual components. Note in the case of UNA, there is less to build. -Asm 'dbgmon' -Asm 'prefix' -Asm 'romldr' -Asm 'eastaegg' -Asm 'nascom' -Asm 'tastybasic' -Asm 'imgpad' -Asm 'imgpad0' +$RomComponentList = "dbgmon", "romldr", "eastaegg", "imgpad" +ForEach ($RomComponentName in $RomComponentList) {Asm $RomComponentName} + if ($Platform -ne "UNA") { Asm 'hbios' '-dROMBOOT' -Output 'hbios_rom.bin' -List 'hbios_rom.lst' Asm 'hbios' '-dAPPBOOT' -Output 'hbios_app.bin' -List 'hbios_app.lst' Asm 'hbios' '-dIMGBOOT' -Output 'hbios_img.bin' -List 'hbios_img.lst' + + Asm 'nascom' + Asm 'tastybasic' + Asm 'game' + Asm 'usrrom' + Asm 'imgpad0' } # @@ -184,17 +184,18 @@ if ($Platform -ne "UNA") # "Building ${RomName} output files..." -# Combine the CCP and BDOS portions of CP/M and ZSystem to create OS images -Concat 'ccp.bin','bdos.bin',$CBiosFile 'cpm.bin' -Concat 'zcpr.bin','zsdos.bin',$CBiosFile 'zsys.bin' +# Build 32K OS chunk containing the loader, debug monitor, and two OS images +Concat 'romldr.bin', 'eastaegg.bin','dbgmon.bin', "..\cpm22\cpm_${Bios}.bin", "..\zsdos\zsys_${Bios}.bin" osimg.bin + +# Build 20K OS chunk containing the loader, debug monitor, and one OS image +Concat 'romldr.bin', 'eastaegg.bin','dbgmon.bin', "..\zsdos\zsys_${Bios}.bin" osimg_small.bin -# Prepend a bit of boot code required to bootstrap the OS images -Concat 'prefix.bin','cpm.bin' 'cpm.sys' -Concat 'prefix.bin','zsys.bin' 'zsys.sys' +# Build second 32K chunk containing supplemental ROM apps (not for UNA) +if ($Platform -ne "UNA") +{ + Concat '..\Forth\camel80.bin', 'nascom.bin', 'tastybasic.bin', 'game.bin', 'imgpad0.bin', 'usrrom.bin' osimg1.bin +} -# Build 32K OS chunk containing the loader, debug monitor, and OS images -Concat 'romldr.bin', 'eastaegg.bin','dbgmon.bin', 'cpm.bin', 'zsys.bin' osimg.bin -Concat 'camel80.bin', 'nascom.bin', 'tastybasic.bin', 'imgpad0.bin' osimg1.bin # # Now the ROM disk image is created. This is done by starting with a # blank ROM disk image of the correct size, then cpmtools is used to @@ -203,8 +204,8 @@ Concat 'camel80.bin', 'nascom.bin', 'tastybasic.bin', 'imgpad0.bin' osimg1.bin "Building ${RomSize}KB ${RomName} ROM disk data file..." -# Use the blank ROM disk image to create a working ROM disk image -Copy-Item $BlankROM $RomDiskFile +# Create a blank ROM disk image to create a working ROM disk image +Set-Content -Value ([byte[]](0xE5) * (([int]${RomSize} * 1KB) - 128KB)) -Encoding byte -Path $RomDiskFile # Copy all files from the appropriate directory to the working ROM disk image cpmcp -f $RomFmt $RomDiskFile ../RomDsk/ROM_${RomSize}KB/*.* 0: @@ -222,7 +223,8 @@ foreach ($App in $RomApps) } # Add the CP/M and ZSystem system images to the ROM disk (used by SYSCOPY) -cpmcp -f $RomFmt $RomDiskFile *.sys 0: +cpmcp -f $RomFmt $RomDiskFile ..\cpm22\cpm_${Bios}.sys 0:cpm.sys +cpmcp -f $RomFmt $RomDiskFile ..\zsdos\zsys_${Bios}.sys 0:zsys.sys # # Finally, the individual binary components are concatenated together to produce @@ -238,9 +240,9 @@ if ($Platform -eq "UNA") else { Concat 'hbios_rom.bin','osimg.bin','osimg1.bin','osimg.bin',$RomDiskFile $RomFile - Concat 'hbios_app.bin','osimg.bin' $ComFile - Concat 'hbios_img.bin','osimg.bin' $ImgFile + Concat 'hbios_app.bin','osimg_small.bin' $ComFile + # Concat 'hbios_img.bin','osimg_small.bin' $ImgFile } -# Remove the temprary working ROM disk file +# Remove the temporary working ROM disk file Remove-Item $RomDiskFile diff --git a/Source/HBIOS/Build.sh b/Source/HBIOS/Build.sh new file mode 100755 index 00000000..86f8c05a --- /dev/null +++ b/Source/HBIOS/Build.sh @@ -0,0 +1,154 @@ +#!/bin/bash + +# fail on any error +set -e + +CPMCP=../../Tools/`uname`/cpmcp + +timestamp=$(date +%Y-%m-%d) +#timestamp="2020-02-24" + +if [ $1 == '-d' ] ; then + shift + diffdir=$1 + shift + if [ -f $diffdir/build.inc ] ; then + timestamp=$(grep TIMESTAMP $diffdir/build.inc | awk '{print $3}' | tr -d '\015"') + echo diff build using $timestamp + fi +fi + +# positional arguments +platform=$1 +config=$2 +romsize=$3 +romname=$4 + +# prompt if no match +platforms=($(find Config -name \*.asm -print | \ + sed -e 's,Config/,,' -e 's/_.*$//' | sort -u)) + +while ! echo ${platforms[@]} | grep -q -w -s "$platform" ; do + echo -n "Enter platform [" ${platforms[@]} "] :" + read platform +done + +configs=$(find Config -name ${platform}_\* -print | \ + sed -e 's,Config/,,' -e "s/${platform}_//" -e "s/.asm//") +while ! echo ${configs[@]} | grep -s -w -q "$config" ; do + echo -n "Enter config for $platform [" ${configs[@]} "] :" + read config +done +configfile=Config/${platform}_${config}.asm + +while [ ! '(' "$romsize" = 1024 -o "$romsize" = 512 ')' ] ; do + echo -n "Romsize :" + read romsize +done + +if [ -z "$romname" ] ; then + romname=${platform}_${config} +fi +echo Building for $romname for $platform $config $romsize + +if [ $platform == UNA ] ; then + BIOS=una +else + BIOS=wbw +fi + +Apps=(assign fdu format mode rtc survey syscopy sysgen talk timer xm inttest) + +blankfile=Blank${romsize}KB.dat +romdiskfile=RomDisk.tmp +romfmt=wbw_rom${romsize} +outdir=../../Binary + +echo "creating empty rom disk of size $romsize in $blankfile" +LANG=en_US.US-ASCII tr '\000' '\345' build.inc +; RomWBW Configured for $platform $config $timestamp +; +#DEFINE TIMESTAMP "$timestamp" +; +ROMSIZE .EQU $romsize +; +#INCLUDE "$configfile" +; +EOF + +echo "checking prerequisites" +for need in ../CPM22/cpm_$BIOS.bin ../ZSDOS/zsys_$BIOS.bin \ + ../Forth/camel80.bin font8x11c.asm font8x11u.asm font8x16c.asm \ + font8x16u.asm font8x8c.asm font8x8u.asm ; do + if [ ! -f $need ] ; then + echo $need missing + exit 2 + fi +done + +cp ../Forth/camel80.bin . + +make dbgmon.bin romldr.bin eastaegg.bin imgpad.bin + +if [ $platform != UNA ] ; then + make nascom.bin tastybasic.bin game.bin usrrom.bin imgpad0.bin + make hbios_rom.bin hbios_app.bin hbios_img.bin +fi + +echo "Building $romname output files..." + +cat romldr.bin eastaegg.bin dbgmon.bin ../CPM22/cpm_$BIOS.bin ../ZSDOS/zsys_$BIOS.bin >osimg.bin +cat romldr.bin eastaegg.bin dbgmon.bin ../ZSDOS/zsys_$BIOS.bin >osimg_small.bin + +if [ $platform != UNA ] ; then + cat camel80.bin nascom.bin tastybasic.bin game.bin imgpad0.bin usrrom.bin >osimg1.bin +fi + +echo "Building ${romsize}KB $romname ROM disk data file..." + +cp $blankfile $romdiskfile + +echo placing files into $romdiskfile + +for file in $(ls -1 ../RomDsk/ROM_${romsize}KB/* | sort -V) ; do + echo " " $file + $CPMCP -f $romfmt $romdiskfile $file 0: +done + +if [ -d ../RomDsk/$platform ] ; then + for file in ../RomDsk/$platform/* ; do + echo " " $file + $CPMCP -f $romfmt $romdiskfile $file 0: + done +fi + +echo "adding apps to $romdiskfile" +for i in ${Apps[@]} ; do + set +e + f=$(../../Tools/unix/casefn.sh ../../Binary/Apps/$i.com) + set -e + if [ -z "$f" ] ; then + echo " " $i "not found" + else + echo " " $f + $CPMCP -f $romfmt $romdiskfile $f 0: + fi +done + +echo "copying systems to $romdiskfile" +$CPMCP -f $romfmt $romdiskfile ../CPM22/cpm_$BIOS.sys 0:cpm.sys +$CPMCP -f $romfmt $romdiskfile ../ZSDOS/zsys_$BIOS.sys 0:zsys.sys + +if [ $platform = UNA ] ; then + cp osimg.bin $outdir/UNA_WBW_SYS.bin + cp $romdiskfile $outdir/UNA_WBW_ROM$romsize.bin + cat ../UBIOS/UNA-BIOS.BIN osimg.bin ../UBIOS/FSFAT.BIN $romdiskfile >$romname.rom +else + cat hbios_rom.bin osimg.bin osimg1.bin osimg.bin $romdiskfile >$romname.rom + cat hbios_app.bin osimg_small.bin > $romname.com + # cat hbios_img.bin osimg_small.bin > $romname.img +fi + +#rm $romdiskfile diff --git a/Source/HBIOS/Clean.cmd b/Source/HBIOS/Clean.cmd index ef213acd..c2194335 100644 --- a/Source/HBIOS/Clean.cmd +++ b/Source/HBIOS/Clean.cmd @@ -10,4 +10,5 @@ if exist *.exp del *.exp if exist *.tmp del *.tmp if exist *.mrk del *.mrk if exist *.sys del *.sys -if exist build.inc del build.inc \ No newline at end of file +if exist build.inc del build.inc +if exist font*.asm del font*.asm \ No newline at end of file diff --git a/Source/HBIOS/Config/DYNO_std.asm b/Source/HBIOS/Config/DYNO_std.asm new file mode 100644 index 00000000..dccbf5a9 --- /dev/null +++ b/Source/HBIOS/Config/DYNO_std.asm @@ -0,0 +1,41 @@ +; +;================================================================================================== +; DYNO STANDARD CONFIGURATION +;================================================================================================== +; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; +#include "cfg_dyno.asm" +; +CPUOSC .SET 18432000 ; CPU OSC FREQ IN MHZ +; +Z180_CLKDIV .SET 1 ; Z180: CHK DIV: 0=OSC/2, 1=OSC, 2=OSC*2 +Z180_MEMWAIT .SET 0 ; Z180: MEMORY WAIT STATES (0-3) +Z180_IOWAIT .SET 1 ; Z180: I/O WAIT STATES TO ADD ABOVE 1 W/S BUILT-IN (0-3) +; +ASCIENABLE .SET TRUE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +;ASCI0CFG .SET SER_115200_8N1 ; ASCI 0: SERIAL LINE CONFIG +;ASCI1CFG .SET SER_115200_8N1 ; ASCI 1: SERIAL LINE CONFIG +; +FDENABLE .SET FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +; +PPIDEENABLE .SET TRUE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) diff --git a/Source/HBIOS/Config/EZZ80_std.asm b/Source/HBIOS/Config/EZZ80_std.asm new file mode 100644 index 00000000..54f8aaf4 --- /dev/null +++ b/Source/HBIOS/Config/EZZ80_std.asm @@ -0,0 +1,33 @@ +; +;================================================================================================== +; EASY Z80 STANDARD CONFIGURATION +;================================================================================================== +; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; +#include "cfg_ezz80.asm" +; +CPUOSC .SET 10000000 ; CPU OSC FREQ IN MHZ +; +IDEENABLE .SET TRUE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +; +PPIDEENABLE .SET TRUE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) diff --git a/Source/HBIOS/Config/MK4_std.asm b/Source/HBIOS/Config/MK4_std.asm index c2265f33..c838d873 100644 --- a/Source/HBIOS/Config/MK4_std.asm +++ b/Source/HBIOS/Config/MK4_std.asm @@ -3,28 +3,47 @@ ; MARK IV STANDARD CONFIGURATION ;================================================================================================== ; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; #include "cfg_mk4.asm" ; -Z180_CLKDIV .SET 1 ; 0=OSC/2, 1=OSC, 2=OSC*2 -Z180_MEMWAIT .SET 0 ; MEMORY WAIT STATES TO INSERT (0-3) -Z180_IOWAIT .SET 1 ; IO WAIT STATES TO INSERT (0-3) +Z180_CLKDIV .SET 1 ; Z180: CHK DIV: 0=OSC/2, 1=OSC, 2=OSC*2 +Z180_MEMWAIT .SET 0 ; Z180: MEMORY WAIT STATES (0-3) +Z180_IOWAIT .SET 1 ; Z180: I/O WAIT STATES TO ADD ABOVE 1 W/S BUILT-IN (0-3) ; -FDENABLE .SET FALSE ; TRUE FOR FLOPPY DEVICE SUPPORT -FDMODE .SET FDMODE_DIDE ; FDMODE_DIO, FDMODE_DIDE, FDMODE_DIO3 +CRTACT .SET FALSE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP ; -IDEENABLE .SET TRUE ; TRUE FOR IDE DEVICE SUPPORT -IDEMODE .SET IDEMODE_MK4 ; IDEMODE_MK4, IDEMODE_DIO, IDEMODE_DIDE +VDUENABLE .SET FALSE ; VDU: ENABLE VDU VIDEO/KBD DRIVER (VDU.ASM) +CVDUENABLE .SET TRUE ; CVDU: ENABLE CVDU VIDEO/KBD DRIVER (CVDU.ASM) +VGAENABLE .SET TRUE ; VGA: ENABLE VGA VIDEO/KBD DRIVER (VGA.ASM) ; -PPIDEENABLE .SET FALSE ; TRUE FOR PPIDE DEVICE SUPPORT -PPIDEMODE .SET PPIDEMODE_MFP ; PPIDEMODE_MFP, PPPIDEMODE_DIO3 +FDENABLE .SET FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .SET FDMODE_DIDE ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3] ; -SDENABLE .SET TRUE ; TRUE FOR SD DEVICE SUPPORT -SDMODE .SET SDMODE_MK4 ; SDMODE_MK4, SDMODE_DSD +IDEENABLE .SET TRUE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) ; -PRPENABLE .SET TRUE ; TRUE FOR PROPIO BOARD SUPPORT (VIDEO, KBD, & SD CARD) +PPIDEENABLE .SET FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) ; -VGAENABLE .SET TRUE ; TRUE FOR VGA BOARD VIDEO & KBD SUPPORT -CVDUENABLE .SET TRUE ; TRUE FOR CVDU BOARD VIDEO & KBD SUPPORT -VDUENABLE .SET FALSE ; TRUE FOR VDU BOARD VIDEO & KBD SUPPORT +SDENABLE .SET TRUE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDMODE .SET SDMODE_MK4 ; SD: DRIVER MODE: SDMODE_[JUHA|N8|CSIO|PPI|UART|DSD|MK4|SC] ; -CRTACT .SET FALSE ; TRUE TO ACTIVATE CRT AT STARTUP (BOOT ON CRT) +PRPENABLE .SET TRUE ; PRP: ENABLE ECB PROPELLER IO BOARD DRIVER (PRP.ASM) diff --git a/Source/HBIOS/Config/N8_std.asm b/Source/HBIOS/Config/N8_std.asm index a5ac243b..04b4b92e 100644 --- a/Source/HBIOS/Config/N8_std.asm +++ b/Source/HBIOS/Config/N8_std.asm @@ -3,12 +3,33 @@ ; N8 STANDARD CONFIGURATION ;================================================================================================== ; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; #include "cfg_n8.asm" ; -Z180_CLKDIV .SET 1 ; 0=OSC/2, 1=OSC, 2=OSC*2 -Z180_MEMWAIT .SET 1 ; MEMORY WAIT STATES TO INSERT (0-3) -Z180_IOWAIT .SET 3 ; IO WAIT STATES TO INSERT (0-3) +Z180_CLKDIV .SET 1 ; Z180: CHK DIV: 0=OSC/2, 1=OSC, 2=OSC*2 +Z180_MEMWAIT .SET 0 ; Z180: MEMORY WAIT STATES (0-3) +Z180_IOWAIT .SET 1 ; Z180: I/O WAIT STATES TO ADD ABOVE 1 W/S BUILT-IN (0-3) ; -SDMODE .SET SDMODE_CSIO ; FOR N8 PROTOTYPE (DATECODE 2511), USE SDMODE_N8 +CRTACT .SET FALSE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP ; -CRTACT .SET FALSE ; TRUE TO ACTIVATE CRT AT STARTUP (BOOT ON CRT) +SDMODE .SET SDMODE_CSIO ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) diff --git a/Source/HBIOS/Config/RC180_std.asm b/Source/HBIOS/Config/RC180_std.asm deleted file mode 100644 index 60095004..00000000 --- a/Source/HBIOS/Config/RC180_std.asm +++ /dev/null @@ -1,27 +0,0 @@ -; -;================================================================================================== -; RC2014 STANDARD CONFIGURATION -;================================================================================================== -; -#include "cfg_rc180.asm" -; -Z180_CLKDIV .SET 1 ; 0=OSC/2, 1=OSC, 2=OSC*2 -Z180_MEMWAIT .SET 0 ; MEMORY WAIT STATES TO INSERT (0-3) -Z180_IOWAIT .SET 1 ; IO WAIT STATES TO INSERT (0-3) -; -CPUOSC .SET 18432000 ; CPU OSC FREQ -DEFSERCFG .SET SER_38400_8N1 ; DEFAULT SERIAL LINE CONFIG -; -ASCIENABLE .SET TRUE ; TRUE FOR Z180 ASCI SUPPORT -SIOENABLE .SET FALSE ; TRUE TO AUTO-DETECT ZILOG SIO/2 -SIOMODE .SET SIOMODE_RC ; TYPE OF SIO/2 TO DETECT: SIOMODE_RC, SIOMODE_SMB -ACIAENABLE .SET FALSE ; TRUE TO AUTO-DETECT MOTOROLA 6850 ACIA -; -FDENABLE .SET FALSE ; TRUE FOR FLOPPY SUPPORT -FDMODE .SET FDMODE_RCWDC ; FDMODE_RCSMC, FDMODE_RCWDC -; -IDEENABLE .SET TRUE ; TRUE FOR IDE DEVICE SUPPORT (CF MODULE) -IDEMODE .SET IDEMODE_RC ; TYPE OF CF MODULE: IDEMODE_RC, IDEMODE_SMB -PPIDEENABLE .SET FALSE ; TRUE FOR PPIDE DEVICE SUPPORT (PPIDE MODULE) -; -DSRTCENABLE .SET FALSE ; DS-1302 CLOCK DRIVER diff --git a/Source/HBIOS/Config/RCZ180_ext.asm b/Source/HBIOS/Config/RCZ180_ext.asm new file mode 100644 index 00000000..9c587ada --- /dev/null +++ b/Source/HBIOS/Config/RCZ180_ext.asm @@ -0,0 +1,47 @@ +; +;================================================================================================== +; RC2014 Z180 STANDARD CONFIGURATION (EXTERNAL 512K RAM/ROM BANKED MEMORY MODULE) +;================================================================================================== +; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; +#include "cfg_rcz180.asm" +; +CPUOSC .SET 18432000 ; CPU OSC FREQ IN MHZ +; +MEMMGR .SET MM_Z2 ; MEMORY MANAGER: MM_[SBC|Z2|N8|Z180] +; +Z180_CLKDIV .SET 1 ; Z180: CHK DIV: 0=OSC/2, 1=OSC, 2=OSC*2 +Z180_MEMWAIT .SET 0 ; Z180: MEMORY WAIT STATES (0-3) +Z180_IOWAIT .SET 1 ; Z180: I/O WAIT STATES TO ADD ABOVE 1 W/S BUILT-IN (0-3) +; +UARTENABLE .SET TRUE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +ASCIENABLE .SET TRUE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +ACIAENABLE .SET FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +SIOENABLE .SET FALSE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +; +FDENABLE .SET FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .SET FDMODE_RCWDC ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3] +; +IDEENABLE .SET TRUE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +; +PPIDEENABLE .SET TRUE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) diff --git a/Source/HBIOS/Config/RCZ180_nat.asm b/Source/HBIOS/Config/RCZ180_nat.asm new file mode 100644 index 00000000..dc287783 --- /dev/null +++ b/Source/HBIOS/Config/RCZ180_nat.asm @@ -0,0 +1,47 @@ +; +;================================================================================================== +; RC2014 Z180 STANDARD CONFIGURATION (NATIVE Z180 LINEAR MEMORY) +;================================================================================================== +; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; +#include "cfg_rcz180.asm" +; +CPUOSC .SET 18432000 ; CPU OSC FREQ IN MHZ +; +MEMMGR .SET MM_Z180 ; MEMORY MANAGER: MM_[SBC|Z2|N8|Z180] +; +Z180_CLKDIV .SET 1 ; Z180: CHK DIV: 0=OSC/2, 1=OSC, 2=OSC*2 +Z180_MEMWAIT .SET 0 ; Z180: MEMORY WAIT STATES (0-3) +Z180_IOWAIT .SET 1 ; Z180: I/O WAIT STATES TO ADD ABOVE 1 W/S BUILT-IN (0-3) +; +UARTENABLE .SET TRUE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +ASCIENABLE .SET TRUE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +ACIAENABLE .SET FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +SIOENABLE .SET FALSE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +; +FDENABLE .SET FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .SET FDMODE_RCWDC ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3] +; +IDEENABLE .SET TRUE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +; +PPIDEENABLE .SET TRUE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) diff --git a/Source/HBIOS/Config/RCZ80_kio.asm b/Source/HBIOS/Config/RCZ80_kio.asm new file mode 100644 index 00000000..a83052f4 --- /dev/null +++ b/Source/HBIOS/Config/RCZ80_kio.asm @@ -0,0 +1,46 @@ +; +;================================================================================================== +; RC2014 Z80 STANDARD CONFIGURATION W/ KIO +;================================================================================================== +; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define PLATFORM_NAME "RC2014 (KIO)" +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; +#include "Config/RCZ80_std.asm" +; +INTMODE .SET 2 ; INTERRUPTS: 0=NONE, 1=MODE 1, 2=MODE 2 +; +KIOENABLE .SET TRUE ; ENABLE ZILOG KIO SUPPORT +; +CTCENABLE .SET TRUE ; ENABLE ZILOG CTC SUPPORT +CTCBASE .SET KIOBASE+$04 ; CTC BASE I/O ADDRESS +; +ACIAENABLE .SET FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +; +SIOENABLE .SET TRUE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +SIOCNT .SET 1 ; SIO: NUMBER OF CHIPS TO DETECT (1-2), 2 CHANNELS PER CHIP +SIO0MODE .SET SIOMODE_EZZ80 ; SIO 0: CHIP TYPE: SIOMODE_[RC|SMB|ZP|EZZ80] +SIO0BASE .SET KIOBASE+$08 ; SIO 0: REGISTERS BASE ADR +SIO0CTCC .SET 0 ; SIO 0: CTC CHANNEL CLOCK SCALER, (0-3), -1 FOR NONE +SIO0ACLK .SET 1843200 ; SIO 0A: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO0BCLK .SET 1843200 ; SIO 0B: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 diff --git a/Source/HBIOS/Config/RCZ80_mt.asm b/Source/HBIOS/Config/RCZ80_mt.asm new file mode 100644 index 00000000..1e49c5d3 --- /dev/null +++ b/Source/HBIOS/Config/RCZ80_mt.asm @@ -0,0 +1,32 @@ +; +;================================================================================================== +; RC2014 Z80 CONFIGURATION W/ MT011 +;================================================================================================== +; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; +#define PLATFORM_NAME "RC2014 (MT)" +; +#include "Config/RCZ80_std.asm" +; +SDENABLE .SET TRUE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDMODE .SET SDMODE_MT ; SD: DRIVER MODE: SDMODE_[JUHA|N8|CSIO|PPI|UART|DSD|MK4|SC|MT] diff --git a/Source/HBIOS/Config/RCZ80_std.asm b/Source/HBIOS/Config/RCZ80_std.asm new file mode 100644 index 00000000..da88345f --- /dev/null +++ b/Source/HBIOS/Config/RCZ80_std.asm @@ -0,0 +1,40 @@ +; +;================================================================================================== +; RC2014 Z80 STANDARD CONFIGURATION +;================================================================================================== +; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; +#include "cfg_rcz80.asm" +; +CPUOSC .SET 7372800 ; CPU OSC FREQ IN MHZ +; +UARTENABLE .SET TRUE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +ACIAENABLE .SET TRUE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +SIOENABLE .SET TRUE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +; +FDENABLE .SET FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .SET FDMODE_RCWDC ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3] +; +IDEENABLE .SET TRUE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +; +PPIDEENABLE .SET TRUE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) diff --git a/Source/HBIOS/Config/RC_ezz80.asm b/Source/HBIOS/Config/RC_ezz80.asm deleted file mode 100644 index 32d9ab11..00000000 --- a/Source/HBIOS/Config/RC_ezz80.asm +++ /dev/null @@ -1,23 +0,0 @@ -; -;================================================================================================== -; RC2014 - EASY Z80 CONFIGURATION -;================================================================================================== -; -#include "cfg_rc.asm" -; -CPUOSC .SET 10000000 ; CPU OSC FREQ -DEFSIOCLK .SET 1843200 ; SIO CLOCK FREQ -DEFSERCFG .SET SER_115200_8N1 ; DEFAULT SERIAL LINE CONFIG (SHOULD MATCH ABOVE) -; -SIOENABLE .SET TRUE ; TRUE TO AUTO-DETECT ZILOG SIO/2 -SIOMODE .SET SIOMODE_EZZ80 ; TYPE OF SIO/2 TO DETECT: SIOMODE_RC, SIOMODE_SMB, SIOMODE_EZZ80 -ACIAENABLE .SET FALSE ; TRUE TO AUTO-DETECT MOTOROLA 6850 ACIA -; -FDENABLE .SET FALSE ; TRUE FOR FLOPPY SUPPORT -FDMODE .SET FDMODE_RCWDC ; FDMODE_RCSMC, FDMODE_RCWDC -; -IDEENABLE .SET TRUE ; TRUE FOR IDE DEVICE SUPPORT (CF MODULE) -IDEMODE .SET IDEMODE_RC ; TYPE OF CF MODULE: IDEMODE_RC, IDEMODE_SMB -PPIDEENABLE .SET FALSE ; TRUE FOR PPIDE DEVICE SUPPORT (PPIDE MODULE) -; -DSRTCENABLE .SET FALSE ; DS-1302 CLOCK DRIVER diff --git a/Source/HBIOS/Config/RC_std.asm b/Source/HBIOS/Config/RC_std.asm deleted file mode 100644 index 9c0c2450..00000000 --- a/Source/HBIOS/Config/RC_std.asm +++ /dev/null @@ -1,22 +0,0 @@ -; -;================================================================================================== -; RC2014 STANDARD CONFIGURATION -;================================================================================================== -; -#include "cfg_rc.asm" -; -CPUOSC .SET 7372800 ; CPU OSC FREQ -DEFSERCFG .SET SER_115200_8N1 ; DEFAULT SERIAL LINE CONFIG (SHOULD MATCH ABOVE) -; -SIOENABLE .SET TRUE ; TRUE TO AUTO-DETECT ZILOG SIO/2 -SIOMODE .SET SIOMODE_RC ; TYPE OF SIO/2 TO DETECT: SIOMODE_RC, SIOMODE_SMB -ACIAENABLE .SET TRUE ; TRUE TO AUTO-DETECT MOTOROLA 6850 ACIA -; -FDENABLE .SET FALSE ; TRUE FOR FLOPPY SUPPORT -FDMODE .SET FDMODE_RCWDC ; FDMODE_RCSMC, FDMODE_RCWDC -; -IDEENABLE .SET TRUE ; TRUE FOR IDE DEVICE SUPPORT (CF MODULE) -IDEMODE .SET IDEMODE_RC ; TYPE OF CF MODULE: IDEMODE_RC, IDEMODE_SMB -PPIDEENABLE .SET FALSE ; TRUE FOR PPIDE DEVICE SUPPORT (PPIDE MODULE) -; -DSRTCENABLE .SET FALSE ; DS-1302 CLOCK DRIVER diff --git a/Source/HBIOS/Config/SBC_simh.asm b/Source/HBIOS/Config/SBC_simh.asm index 4c31742e..b2088dfe 100644 --- a/Source/HBIOS/Config/SBC_simh.asm +++ b/Source/HBIOS/Config/SBC_simh.asm @@ -3,12 +3,36 @@ ; SBC SIMH EMULATOR CONFIGURATION ;================================================================================================== ; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; +#DEFINE PLATFORM_NAME "SBC (simh)" +; #include "cfg_sbc.asm" ; -INTMODE .SET 1 ; INT MODE 1 -HTIMENABLE .SET TRUE ; SIMH TIMER +INTMODE .SET 1 ; INTERRUPTS: 0=NONE, 1=MODE 1, 2=MODE 2 +; +HTIMENABLE .SET TRUE ; ENABLE SIMH TIMER SUPPORT ; -DSRTCENABLE .SET FALSE ; DS-1302 CLOCK DRIVER -SIMRTCENABLE .SET TRUE ; SIMH CLOCK DRIVER +SIMRTCENABLE .SET TRUE ; ENABLE SIMH CLOCK DRIVER (SIMRTC.ASM) +DSRTCENABLE .SET FALSE ; DSRTC: ENABLE DS-1302 CLOCK DRIVER (DSRTC.ASM) ; -HDSKENABLE .SET TRUE ; TRUE FOR SIMH HDSK SUPPORT +HDSKENABLE .SET TRUE ; HDSK: ENABLE SIMH HDSK DISK DRIVER (HDSK.ASM) diff --git a/Source/HBIOS/Config/SBC_std.asm b/Source/HBIOS/Config/SBC_std.asm index 6010957d..16acc300 100644 --- a/Source/HBIOS/Config/SBC_std.asm +++ b/Source/HBIOS/Config/SBC_std.asm @@ -3,26 +3,43 @@ ; SBC STANDARD CONFIGURATION ;================================================================================================== ; -#include "cfg_sbc.asm" +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! ; -FDENABLE .SET FALSE ; TRUE FOR FLOPPY DEVICE SUPPORT -FDMODE .SET FDMODE_DIO3 ; FDMODE_DIO, FDMODE_DIDE, FDMODE_DIO3 +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; +#include "cfg_sbc.asm" ; -IDEENABLE .SET FALSE ; TRUE FOR IDE DEVICE SUPPORT -IDEMODE .SET IDEMODE_DIO ; IDEMODE_DIO, IDEMODE_DIDE +CRTACT .SET TRUE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP ; -PPIDEENABLE .SET TRUE ; TRUE FOR PPIDE DEVICE SUPPORT -PPIDEMODE .SET PPIDEMODE_SBC ; PPIDEMODE_SBC, PPPIDEMODE_DIO3, PPIDEMODE_MFP +VDUENABLE .SET FALSE ; VDU: ENABLE VDU VIDEO/KBD DRIVER (VDU.ASM) +CVDUENABLE .SET TRUE ; CVDU: ENABLE CVDU VIDEO/KBD DRIVER (CVDU.ASM) +VGAENABLE .SET TRUE ; VGA: ENABLE VGA VIDEO/KBD DRIVER (VGA.ASM) ; -SDENABLE .SET FALSE ; TRUE FOR SD DEVICE SUPPORT -SDMODE .SET SDMODE_PPI ; SDMODE_JUHA, SDMODE_PPI, SDMODE_DSD +FDENABLE .SET FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .SET FDMODE_DIO3 ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3] ; -PRPENABLE .SET TRUE ; TRUE FOR PROPIO BOARD SUPPORT (VIDEO, KBD, & SD CARD) +IDEENABLE .SET FALSE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) ; -VGAENABLE .SET TRUE ; TRUE FOR VGA BOARD VIDEO & KBD SUPPORT -CVDUENABLE .SET TRUE ; TRUE FOR CVDU BOARD VIDEO & KBD SUPPORT -VDUENABLE .SET FALSE ; TRUE FOR VDU BOARD VIDEO & KBD SUPPORT -VDUSIZ .SET V80X24 ; DEFAULT VDU FORMAT IF ACTIVE +PPIDEENABLE .SET TRUE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) ; +SDENABLE .SET FALSE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDMODE .SET SDMODE_PPI ; SD: DRIVER MODE: SDMODE_[JUHA|N8|CSIO|PPI|UART|DSD|MK4|SC] ; -CRTACT .SET TRUE ; TRUE TO ACTIVATE CRT AT STARTUP (BOOT ON CRT) +PRPENABLE .SET TRUE ; PRP: ENABLE ECB PROPELLER IO BOARD DRIVER (PRP.ASM) diff --git a/Source/HBIOS/Config/SCZ180_126.asm b/Source/HBIOS/Config/SCZ180_126.asm new file mode 100644 index 00000000..1da05c4b --- /dev/null +++ b/Source/HBIOS/Config/SCZ180_126.asm @@ -0,0 +1,52 @@ +; +;================================================================================================== +; SC126 STANDARD CONFIGURATION +;================================================================================================== +; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define PLATFORM_NAME "SC126" +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; +#include "cfg_scz180.asm" +; +CPUOSC .SET 18432000 ; CPU OSC FREQ IN MHZ +; +Z180_CLKDIV .SET 1 ; Z180: CHK DIV: 0=OSC/2, 1=OSC, 2=OSC*2 +Z180_MEMWAIT .SET 0 ; Z180: MEMORY WAIT STATES (0-3) +Z180_IOWAIT .SET 1 ; Z180: I/O WAIT STATES TO ADD ABOVE 1 W/S BUILT-IN (0-3) +; +HBIOS_MUTEX .SET FALSE ; ENABLE REENTRANT CALLS TO HBIOS (ADDS OVERHEAD) +; +UARTENABLE .SET TRUE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +ASCIENABLE .SET TRUE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +ACIAENABLE .SET FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +SIOENABLE .SET FALSE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +; +FDENABLE .SET FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .SET FDMODE_RCWDC ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3] +; +IDEENABLE .SET TRUE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +; +PPIDEENABLE .SET TRUE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) +; +SDENABLE .SET TRUE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDCNT .SET 1 ; SD: NUMBER OF SD CARD DEVICES (1-2), FOR DSD & SC ONLY diff --git a/Source/HBIOS/Config/SCZ180_130.asm b/Source/HBIOS/Config/SCZ180_130.asm new file mode 100644 index 00000000..cf84881b --- /dev/null +++ b/Source/HBIOS/Config/SCZ180_130.asm @@ -0,0 +1,58 @@ +; +;================================================================================================== +; SC130 STANDARD CONFIGURATION +;================================================================================================== +; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define PLATFORM_NAME "SC130" +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; +#include "cfg_scz180.asm" +; +CPUOSC .SET 18432000 ; CPU OSC FREQ IN MHZ +; +Z180_CLKDIV .SET 1 ; Z180: CHK DIV: 0=OSC/2, 1=OSC, 2=OSC*2 +Z180_MEMWAIT .SET 0 ; Z180: MEMORY WAIT STATES (0-3) +Z180_IOWAIT .SET 1 ; Z180: I/O WAIT STATES TO ADD ABOVE 1 W/S BUILT-IN (0-3) +; +HBIOS_MUTEX .SET FALSE ; ENABLE REENTRANT CALLS TO HBIOS (ADDS OVERHEAD) +; +LEDENABLE .SET TRUE ; ENABLE STATUS LED (SINGLE LED) +; +DIAGENABLE .SET FALSE ; ENABLES OUTPUT TO 8 BIT LED DIAGNOSTIC PORT +; +DSRTCENABLE .SET FALSE ; DSRTC: ENABLE DS-1302 CLOCK DRIVER (DSRTC.ASM) +INTRTCENABLE .SET TRUE ; ENABLE PERIODIC INTERRUPT CLOCK DRIVER (INTRTC.ASM) +; +UARTENABLE .SET FALSE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +ASCIENABLE .SET TRUE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +ACIAENABLE .SET FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +SIOENABLE .SET FALSE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +; +FDENABLE .SET FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .SET FDMODE_RCWDC ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3] +; +IDEENABLE .SET TRUE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +; +PPIDEENABLE .SET TRUE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) +; +SDENABLE .SET TRUE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) diff --git a/Source/HBIOS/Config/SCZ180_131.asm b/Source/HBIOS/Config/SCZ180_131.asm new file mode 100644 index 00000000..c3b75a80 --- /dev/null +++ b/Source/HBIOS/Config/SCZ180_131.asm @@ -0,0 +1,58 @@ +; +;================================================================================================== +; SC131 STANDARD CONFIGURATION +;================================================================================================== +; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define PLATFORM_NAME "SC131" +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; +#include "cfg_scz180.asm" +; +CPUOSC .SET 18432000 ; CPU OSC FREQ IN MHZ +; +Z180_CLKDIV .SET 1 ; Z180: CHK DIV: 0=OSC/2, 1=OSC, 2=OSC*2 +Z180_MEMWAIT .SET 0 ; Z180: MEMORY WAIT STATES (0-3) +Z180_IOWAIT .SET 1 ; Z180: I/O WAIT STATES TO ADD ABOVE 1 W/S BUILT-IN (0-3) +; +HBIOS_MUTEX .SET FALSE ; ENABLE REENTRANT CALLS TO HBIOS (ADDS OVERHEAD) +; +LEDENABLE .SET TRUE ; ENABLE STATUS LED (SINGLE LED) +; +DIAGENABLE .SET FALSE ; ENABLES OUTPUT TO 8 BIT LED DIAGNOSTIC PORT +; +DSRTCENABLE .SET FALSE ; DSRTC: ENABLE DS-1302 CLOCK DRIVER (DSRTC.ASM) +INTRTCENABLE .SET TRUE ; ENABLE PERIODIC INTERRUPT CLOCK DRIVER (INTRTC.ASM) +; +UARTENABLE .SET FALSE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +ASCIENABLE .SET TRUE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +ACIAENABLE .SET FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +SIOENABLE .SET FALSE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +; +FDENABLE .SET FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .SET FDMODE_RCWDC ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3] +; +IDEENABLE .SET FALSE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +; +PPIDEENABLE .SET FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) +; +SDENABLE .SET TRUE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) diff --git a/Source/HBIOS/Config/UNA_std.asm b/Source/HBIOS/Config/UNA_std.asm index 093e0af2..c722c7af 100644 --- a/Source/HBIOS/Config/UNA_std.asm +++ b/Source/HBIOS/Config/UNA_std.asm @@ -3,5 +3,25 @@ ; UNA STANDARD CONFIGURATION ;================================================================================================== ; -#include "cfg_una.asm" +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! ; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; +#include "cfg_una.asm" diff --git a/Source/HBIOS/Config/ZETA2_std.asm b/Source/HBIOS/Config/ZETA2_std.asm index a31af926..0c434c57 100644 --- a/Source/HBIOS/Config/ZETA2_std.asm +++ b/Source/HBIOS/Config/ZETA2_std.asm @@ -3,12 +3,35 @@ ; ZETA2 STANDARD CONFIGURATION ;================================================================================================== ; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; #include "cfg_zeta2.asm" ; -PPIDEENABLE .SET FALSE ; TRUE FOR PPIDE DEVICE SUPPORT +UARTCFG .SET UARTCFG | SER_RTS +; +CRTACT .SET TRUE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP ; -SDENABLE .SET FALSE ; TRUE FOR SD DEVICE SUPPORT +PPIDEENABLE .SET FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) ; -PPPENABLE .SET TRUE ; TRUE FOR PROPIO BOARD SUPPORT (VIDEO, KBD, & SD CARD) +SDENABLE .SET FALSE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) ; -CRTACT .SET TRUE ; TRUE TO ACTIVATE CRT AT STARTUP (BOOT ON CRT) +PPPENABLE .SET TRUE ; PPP: ENABLE ZETA PARALLEL PORT PROPELLER BOARD DRIVER (PPP.ASM) diff --git a/Source/HBIOS/Config/ZETA_std.asm b/Source/HBIOS/Config/ZETA_std.asm index 9a31ad5b..cfc82665 100644 --- a/Source/HBIOS/Config/ZETA_std.asm +++ b/Source/HBIOS/Config/ZETA_std.asm @@ -3,12 +3,33 @@ ; ZETA STANDARD CONFIGURATION ;================================================================================================== ; +; THE COMPLETE SET OF DEFAULT CONFIGURATION SETTINGS FOR THIS PLATFORM ARE FOUND IN THE +; CFG_.ASM INCLUDED FILE WHICH IS FOUND IN THE PARENT DIRECTORY. THIS FILE CONTAINS +; COMMON CONFIGURATION SETTINGS THAT OVERRIDE THE DEFAULTS. IT IS INTENDED THAT YOU MAKE +; YOUR CUSTOMIZATIONS IN THIS FILE AND JUST INHERIT ALL OTHER SETTINGS FROM THE DEFAULTS. +; EVEN BETTER, YOU CAN MAKE A COPY OF THIS FILE WITH A NAME LIKE _XXX.ASM AND SPECIFY +; YOUR FILE IN THE BUILD PROCESS. +; +; THE SETTINGS BELOW ARE THE SETTINGS THAT ARE MOST COMMONLY MODIFIED FOR THIS PLATFORM. +; MANY OF THEM ARE EQUAL TO THE SETTINGS IN THE INCLUDED FILE, SO THEY DON'T REALLY DO +; ANYTHING AS IS. THEY ARE LISTED HERE TO MAKE IT EASY FOR YOU TO ADJUST THE MOST COMMON +; SETTINGS. +; +; N.B., SINCE THE SETTINGS BELOW ARE REDEFINING VALUES ALREADY SET IN THE INCLUDED FILE, +; TASM INSISTS THAT YOU USE THE .SET OPERATOR AND NOT THE .EQU OPERATOR BELOW. ATTEMPTING +; TO REDEFINE A VALUE WITH .EQU BELOW WILL CAUSE TASM ERRORS! +; +; PLEASE REFER TO THE CUSTOM BUILD INSTRUCTIONS (README.TXT) IN THE SOURCE DIRECTORY (TWO +; DIRECTORIES ABOVE THIS ONE). +; +#define BOOT_DEFAULT "H" ; DEFAULT BOOT LOADER CMD ON OR AUTO BOOT +; #include "cfg_zeta.asm" ; -PPIDEENABLE .SET FALSE ; TRUE FOR PPIDE DEVICE SUPPORT +CRTACT .SET TRUE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP ; -SDENABLE .SET FALSE ; TRUE FOR SD DEVICE SUPPORT +PPIDEENABLE .SET FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) ; -PPPENABLE .SET TRUE ; TRUE FOR PROPIO BOARD SUPPORT (VIDEO, KBD, & SD CARD) +SDENABLE .SET FALSE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) ; -CRTACT .SET TRUE ; TRUE TO ACTIVATE CRT AT STARTUP (BOOT ON CRT) +PPPENABLE .SET TRUE ; PPP: ENABLE ZETA PARALLEL PORT PROPELLER BOARD DRIVER (PPP.ASM) diff --git a/Source/HBIOS/MakeBlankROM.ps1 b/Source/HBIOS/MakeBlankROM.ps1 deleted file mode 100644 index 8f9e9d27..00000000 --- a/Source/HBIOS/MakeBlankROM.ps1 +++ /dev/null @@ -1,4 +0,0 @@ -# Create a "blank" rom disk image, filled with hex E5 -# -Set-Content -Value ([byte[]](0xE5) * (512KB - 128KB)) -Encoding byte -Path 'Blank512KB.dat' -Set-Content -Value ([byte[]](0xE5) * (1MB - 128KB)) -Encoding byte -Path 'Blank1024KB.dat' diff --git a/Source/HBIOS/Makefile b/Source/HBIOS/Makefile new file mode 100644 index 00000000..6ee90216 --- /dev/null +++ b/Source/HBIOS/Makefile @@ -0,0 +1,67 @@ +OBJECTS = + +ifeq (1,1) +OBJECTS += DYNO_std.rom DYNO_std.com +OBJECTS += EZZ80_std.rom EZZ80_std.com +OBJECTS += MK4_std.rom MK4_std.com +OBJECTS += N8_std.rom N8_std.com +OBJECTS += RCZ180_ext.rom RCZ180_ext.com +OBJECTS += RCZ180_nat.rom RCZ180_nat.com +OBJECTS += RCZ80_kio.rom RCZ80_kio.com +OBJECTS += RCZ80_mt.rom RCZ80_mt.com +OBJECTS += RCZ80_std.rom RCZ80_std.com +OBJECTS += SBC_simh.rom SBC_simh.com +OBJECTS += SBC_std.rom SBC_std.com +OBJECTS += SCZ180_126.rom SCZ180_126.com +OBJECTS += SCZ180_130.rom SCZ180_130.com +OBJECTS += SCZ180_131.rom SCZ180_131.com +OBJECTS += UNA_std.rom +OBJECTS += ZETA_std.rom ZETA_std.com +OBJECTS += ZETA2_std.rom ZETA2_std.com +else +OBJECTS += ZETA2_std.rom ZETA2_std.com +endif + +MOREDIFF = camel80.bin game.bin hbios_rom.bin nascom.bin prefix.bin usrrom.bin \ + dbgmon.bin hbios_app.bin imgpad0.bin osimg1.bin romldr.bin \ + eastaegg.bin hbios_img.bin imgpad.bin osimg.bin tastybasic.bin \ + game.bin usrrom.bin + +SUBDIRS = +DEST = ../../Binary +TOOLS =../../Tools +OTHERS = *.img *.rom *.com *.bin *.z80 cpm.sys zsys.sys Build.inc RomDisk.tmp font*.asm *.dat +include $(TOOLS)/Makefile.inc + +ifeq ($(DIFFMAKE),1) + DIFFBUILD := -d $(DIFFTO)/Source/HBIOS +endif + +DIFFPATH = $(DIFFTO)/Binary + + +ROMSIZE=512 + +N8_std.rom: ROMSIZE=512 + +%.rom: + bash Build.sh $(DIFFBUILD) $(shell echo $* | sed 's/_/ /') $(ROMSIZE) + +hbios_rom.bin: hbios.asm build.inc + $(TASM) -dROMBOOT hbios.asm hbios_rom.bin hbios_rom.lst + +hbios_app.bin: hbios.asm build.inc + $(TASM) -dAPPBOOT hbios.asm hbios_app.bin hbios_app.lst + +hbios_img.bin: hbios.asm build.inc + $(TASM) -dIMGBOOT hbios.asm hbios_img.bin hbios_img.lst + +romldr.bin: build.inc +dbgmon.bin: build.inc +nascom.bin: build.inc +eastaegg.bin: build.inc + +dumps: + for i in $(MOREDIFF) ; do \ + hexdump -C $$i >$$i.dump ; \ + done diff --git a/Source/HBIOS/acia.asm b/Source/HBIOS/acia.asm index b0b0fa8b..2e030a76 100644 --- a/Source/HBIOS/acia.asm +++ b/Source/HBIOS/acia.asm @@ -10,40 +10,56 @@ ; F E D C B A 9 8 7 6 5 4 3 2 1 0 ; -- MSB (D REGISTER) -- -- LSB (E REGISTER) -- ; -; CURRENTLY ONLY SUPPORTS A SINGLE CHIP IN SYSTEM ; -ACIA_DEBUG .EQU FALSE -; -ACIA_NONE .EQU 0 -ACIA_ACIA .EQU 1 +; ACIA STATUS REGISTER: ; -; POSSIBLE BASE I/O ADDRESSES -; NOTE THAT THE ACIA ONLY QUALIFIES ADDRESS BITS 7 & 6, SO -; THE ACIA'S TWO PORTS APPEAR REPEATEDLY OVER AN ADDRESS RANGE -; OF $40 STARTING FROM THE REAL BASE PORT. -; WE TAKE ADVANTAGE OF THIS TO AVOID CONFLICTING WITH SIO -; AND COMPACT FLASH MODULES DURING DETECTION PROBES. -; -ACIAA_BASE .EQU $80 + $20 ; MODULE A -ACIAB_BASE .EQU $40 + $20 ; MODULE B +; D7 D6 D5 D4 D3 D2 D1 D0 +; +-------+-------+-------+-------+-------+-------+-------+-------+ +; | /IRQ | PE | OVRN | FE | /CTS | /DCD | TDRE | RDRF | +; +-------+-------+-------+-------+-------+-------+-------+-------+ ; -#IF (INTMODE == 0) +; ACIA CONTROL REGISTER: ; -ACIA_RTSON .EQU %00010110 ; NO RCV INT, RTS ASSERTED, 8N1, CLK/64 BAUD -ACIA_RTSOFF .EQU %01010110 ; NO RCV INT, RTS DEASSERTED, 8N1, CLK/64 BAUD +; D7 D6 D5 D4 D3 D2 D1 D0 +; +-------+-------+-------+-------+-------+-------+-------+-------+ +; | RIE | TC2 | TC1 | WS3 | WS2 | WS1 | CDS2 | CDS1 | +; +-------+-------+-------+-------+-------+-------+-------+-------+ ; -#ENDIF +; RIE: RECEIVE INTERRUPT ENABLE (RECEIVE DATA REGISTER FULL) ; -#IF (INTMODE == 1) -; -ACIA_RTSON .EQU %10010110 ; RCV INT, RTS ASSERTED, 8N1, CLK/64 BAUD -ACIA_RTSOFF .EQU %11010110 ; RCV INT, RTS DEASSERTED, 8N1, CLK/64 BAUD +; TC: TRANSMIT CONTROL (TRANSMIT DATA REGISTER EMPTY) +; 0 0 - /RTS=LOW, TDRE INT DISABLED +; 0 1 - /RTS=LOW, TDRE INT ENABLED +; 1 0 - /RTS=HIGH, TDRE INT DISABLED +; 1 1 - /RTS=LOW, TRANSMIT BREAK, TDRE INT DISABLED +; +; WS: WORD SELECT (DATA BITS, PARITY, STOP BITS) +; 0 0 0 - 7,E,2 +; 0 0 1 - 7,O,2 +; 0 1 0 - 7,E,1 +; 0 1 1 - 7,O,1 +; 1 0 0 - 8,N,2 +; 1 0 1 - 8,N,1 +; 1 1 0 - 8,E,1 +; 1 1 1 - 8,O,1 +; +; CDS: COUNTER DIVIDE SELECT +; 0 0 - DIVIDE BY 1 +; 0 1 - DIVIDE BY 16 +; 1 0 - DIVIDE BY 64 +; 1 1 - MASTER RESET +; +ACIA_BUFSZ .EQU 32 ; RECEIVE RING BUFFER SIZE +; +ACIA_NONE .EQU 0 +ACIA_ACIA .EQU 1 +; +ACIA_RTSON .EQU %00000000 ; BIT MASK TO ASSERT RTS +ACIA_RTSOFF .EQU %01000000 ; BIT MASK TO DEASSERT RTS ; -#ENDIF - #IF (INTMODE > 1) - .ECHO "*** ERROR: UNSUPPORTED INTMODE FOR ACIA DRIVER!!!\n" - !!! ; FORCE AN ASSEMBLY ERROR + .ECHO "*** ERROR: UNSUPPORTED INTMODE FOR ACIA DRIVER!!!\n" + !!! ; FORCE AN ASSEMBLY ERROR #ENDIF ; ; @@ -54,23 +70,13 @@ ACIA_PREINIT: ; NOTE: INTS WILL BE DISABLED WHEN PREINIT IS CALLED AND THEY MUST REMIAIN ; DISABLED. ; - LD B,ACIA_CNT ; LOOP CONTROL - LD C,0 ; PHYSICAL UNIT INDEX - XOR A ; ZERO TO ACCUM - LD (ACIA_DEV),A ; CURRENT DEVICE NUMBER -ACIA_PREINIT0: + LD B,ACIA_CFGCNT ; LOOP CONTROL + XOR A ; ZERO TO ACCUM + LD (ACIA_DEV),A ; CURRENT DEVICE NUMBER + LD IY,ACIA_CFG ; POINT TO START OF CFG TABLE +ACIA_PREINIT0: PUSH BC ; SAVE LOOP CONTROL - LD A,C ; PHYSICAL UNIT TO A - RLCA ; MULTIPLY BY CFG TABLE ENTRY SIZE (8 BYTES) - RLCA ; ... - RLCA ; ... TO GET OFFSET INTO CFG TABLE - LD HL,ACIA_CFG ; POINT TO START OF CFG TABLE - CALL ADDHLA ; HL := ENTRY ADDRESS - PUSH HL ; SAVE IT - PUSH HL ; COPY CFG DATA PTR - POP IY ; ... TO IY CALL ACIA_INITUNIT ; HAND OFF TO GENERIC INIT CODE - POP DE ; GET ENTRY ADDRESS BACK, BUT PUT IN DE POP BC ; RESTORE LOOP CONTROL ; LD A,(IY+1) ; GET THE ACIA TYPE DETECTED @@ -78,73 +84,73 @@ ACIA_PREINIT0: JR Z,ACIA_PREINIT2 ; SKIP IT IF NOTHING FOUND ; PUSH BC ; SAVE LOOP CONTROL + PUSH IY ; CFG ENTRY ADDRESS + POP DE ; ... TO DE LD BC,ACIA_FNTBL ; BC := FUNCTION TABLE ADDRESS CALL NZ,CIO_ADDENT ; ADD ENTRY IF ACIA FOUND, BC:DE POP BC ; RESTORE LOOP CONTROL ; ACIA_PREINIT2: - INC C ; NEXT PHYSICAL UNIT + LD DE,ACIA_CFGSIZ ; SIZE OF CFG ENTRY + ADD IY,DE ; BUMP IY TO NEXT ENTRY DJNZ ACIA_PREINIT0 ; LOOP UNTIL DONE -; +; +ACIA_PREINIT3: XOR A ; SIGNAL SUCCESS RET ; AND RETURN ; ; ACIA INITIALIZATION ROUTINE ; ACIA_INITUNIT: - CALL ACIA_DETECT ; DETERMINE ACIA TYPE - LD (IY+1),A ; SAVE IN CONFIG TABLE - OR A ; SET FLAGS - RET Z ; ABORT IF NOTHING THERE + CALL ACIA_DETECT ; DETERMINE ACIA TYPE + LD (IY+1),A ; SAVE IN CONFIG TABLE + OR A ; SET FLAGS + RET Z ; ABORT IF NOTHING THERE - ; UPDATE WORKING ACIA DEVICE NUM - LD HL,ACIA_DEV ; POINT TO CURRENT UART DEVICE NUM - LD A,(HL) ; PUT IN ACCUM - INC (HL) ; INCREMENT IT (FOR NEXT LOOP) - LD (IY),A ; UDPATE UNIT NUM + ; UPDATE WORKING ACIA DEVICE NUM + LD HL,ACIA_DEV ; POINT TO CURRENT UART DEVICE NUM + LD A,(HL) ; PUT IN ACCUM + INC (HL) ; INCREMENT IT (FOR NEXT LOOP) + LD (IY),A ; UPDATE UNIT NUM ; #IF (INTMODE == 1) - ; ADD IM1 INT CALL LIST ENTRY - LD L,(IY+6) ; GET RCVBUF PTR - LD H,(IY+7) ; ... INTO HL - LD A,5 ; OFFSET OF INT HANDLER PTR - CALL ADDHLA ; ADD TO HL - LD A,(HL) ; DEREFERENCE - INC HL ; ... - LD H,(HL) ; ... - LD L,A ; ... - CALL HB_ADDIM1 ; ADD TO IM1 CALL LIST + ; ADD IM1 INT CALL LIST ENTRY + LD L,(IY+8) ; GET INT HANDLER PTR + LD H,(IY+9) ; ... INTO HL + CALL HB_ADDIM1 ; ADD TO IM1 CALL LIST #ENDIF - - ; SET DEFAULT CONFIG - LD DE,-1 ; LEAVE CONFIG ALONE - ; CALL INITDEV TO IMPLEMENT CONFIG, BUT NOTE THAT WE CALL - ; THE INITDEV ENTRY POINT THAT DOES NOT ENABLE/DISABLE INTS! - JP ACIA_INITDEVX ; IMPLEMENT IT AND RETURN +; +#IF (INTMODE > 1) + .ECHO "*** ERROR: ACIA DEVICE DOES NOT SUPPORT INTMODE 2!!!\n" + !!! ; FORCE AN ASSEMBLY ERROR +#ENDIF +; + ; IT IS EASY TO SPECIFY A SERIAL CONFIG THAT CANNOT BE IMPLEMENTED + ; DUE TO THE CONSTRAINTS OF THE ACIA. HERE WE FORCE A GENERIC + ; FAILSAFE CONFIG ONTO THE CHANNEL. IF THE SUBSEQUENT "REAL" + ; CONFIG FAILS, AT LEAST THE CHIP WILL BE ABLE TO SPIT DATA OUT + ; AT A RATIONAL BAUD/DATA/PARITY/STOP CONFIG. + CALL ACIA_INITSAFE +; + ; SET DEFAULT CONFIG + LD DE,-1 ; LEAVE CONFIG ALONE + ; CALL INITDEV TO IMPLEMENT CONFIG, BUT NOTE THAT WE CALL + ; THE INITDEV ENTRY POINT THAT DOES NOT ENABLE/DISABLE INTS! + JP ACIA_INITDEVX ; IMPLEMENT IT AND RETURN ; ; ; ACIA_INIT: - LD B,ACIA_CNT ; COUNT OF POSSIBLE ACIA UNITS - LD C,0 ; INDEX INTO ACIA CONFIG TABLE + LD B,ACIA_CFGCNT ; COUNT OF POSSIBLE ACIA UNITS + LD IY,ACIA_CFG ; POINT TO START OF CFG TABLE ACIA_INIT1: PUSH BC ; SAVE LOOP CONTROL - - LD A,C ; PHYSICAL UNIT TO A - RLCA ; MULTIPLY BY CFG TABLE ENTRY SIZE (8 BYTES) - RLCA ; ... - RLCA ; ... TO GET OFFSET INTO CFG TABLE - LD HL,ACIA_CFG ; POINT TO START OF CFG TABLE - CALL ADDHLA ; HL := ENTRY ADDRESS - PUSH HL ; COPY CFG DATA PTR - POP IY ; ... TO IY - LD A,(IY+1) ; GET ACIA TYPE OR A ; SET FLAGS CALL NZ,ACIA_PRTCFG ; PRINT IF NOT ZERO - POP BC ; RESTORE LOOP CONTROL - INC C ; NEXT UNIT + LD DE,ACIA_CFGSIZ ; SIZE OF CFG ENTRY + ADD IY,DE ; BUMP IY TO NEXT ENTRY DJNZ ACIA_INIT1 ; LOOP TILL DONE ; XOR A ; SIGNAL SUCCESS @@ -152,95 +158,89 @@ ACIA_INIT1: ; ; INTERRUPT HANDLERS ; +#IF (INTMODE == 0) +; +; NO INTERRUPT HANDLERS UNDER INTMODE 0. GENERATE A PANIC +; IF SOMETHING TRIES TO CALL THEM. +; +ACIA0_INT: +ACIA1_INT: + CALL PANIC +; +#ENDIF +; #IF (INTMODE > 0) ; -ACIAA_INT: - LD IY,ACIAA_CFG ; POINT TO CONFIG +ACIA0_INT: + LD IY,ACIA0_CFG ; POINT TO ACIA0 CFG + JR ACIA_INTRCV ; TRY TO RECEIVE FROM IT AND RETURN ; - ; CHECK FOR RECEIVE PENDING - LD C,(IY+3) ; STATUS PORT - IN A,(C) ; GET STATUS - AND $01 ; ISOLATE RECEIVE READY BIT - RET Z ; IF NOT, RETURN WITH Z SET +#IF (ACIACNT >= 2) ; -ACIAA_INT00: - ; HANDLE PENDING RECEIVE - INC C ; DATA PORT - IN A,(C) ; READ PORT - DEC C ; BACK TO CONTROL PORT - LD E,A ; SAVE BYTE READ - LD A,(ACIAA_BUFCNT) ; GET CURRENT BUFFER USED COUNT - CP ACIAA_BUFSZ ; COMPARE TO BUFFER SIZE - ;RET Z ; BAIL OUT IF BUFFER FULL, RCV BYTE DISCARDED - JR Z,ACIAA_INT2 ; BAIL OUT IF BUFFER FULL, RCV BYTE DISCARDED - INC A ; INCREMENT THE COUNT - LD (ACIAA_BUFCNT),A ; AND SAVE IT - CP ACIAA_BUFSZ - 5 ; BUFFER GETTING FULL? - JR NZ,ACIAA_INT0 ; IF NOT, BYPASS DEASSERTING RTS - LD A,ACIA_RTSOFF ; VALUE TO DEASSERT RTS - OUT (C),A ; DO IT -ACIAA_INT0: - LD HL,(ACIAA_HD) ; GET HEAD POINTER - LD A,L ; GET LOW BYTE - CP ACIAA_BUFEND & $FF ; PAST END? - JR NZ,ACIAA_INT1 ; IF NOT, BYPASS POINTER RESET - LD HL,ACIAA_BUF ; ... OTHERWISE, RESET TO START OF BUFFER -ACIAA_INT1: - LD A,E ; RECOVER BYTE READ - LD (HL),A ; SAVE RECEIVED BYTE TO HEAD POSITION - INC HL ; INCREMENT HEAD POINTER - LD (ACIAA_HD),HL ; SAVE IT -; -ACIAA_INT2: - ; CHECK FOR MORE PENDING... - IN A,(C) ; GET STATUS - RRA ; READY BIT TO CF - JR C,ACIAA_INT00 ; IF SET, DO SOME MORE - OR $FF ; NZ SET TO INDICATE INT HANDLED - RET ; AND RETURN +ACIA1_INT: + LD IY,ACIA1_CFG ; POINT TO ACIA1 CFG + JR ACIA_INTRCV ; TRY TO RECEIVE FROM IT AND RETURN ; -ACIAB_INT: - LD IY,ACIAB_CFG ; POINT TO CONFIG +#ENDIF +; +; HANDLE INT FOR A SPECIFIC CHANNEL +; BASED ON UNIT CFG POINTED TO BY IY ; - ; CHECK FOR RECEIVE PENDING - LD C,(IY+3) ; STATUS PORT +ACIA_INTRCV: + ; CHECK TO SEE IF SOMETHING IS ACTUALLY THERE + LD C,(IY+3) ; CMD/STAT PORT TO C IN A,(C) ; GET STATUS AND $01 ; ISOLATE RECEIVE READY BIT - RET Z ; IF NOT, RETURN WITH Z SET + RET Z ; NOTHING AVAILABLE ON CURRENT CHANNEL ; -ACIAB_INT00: - ; HANDLE PENDING RECEIVE - INC C ; DATA PORT +ACIA_INTRCV1: + ; RECEIVE CHARACTER INTO BUFFER + INC C ; DATA PORT IN A,(C) ; READ PORT - DEC C ; BACK TO CONTROL PORT - LD E,A ; SAVE BYTE READ - LD A,(ACIAB_BUFCNT) ; GET CURRENT BUFFER USED COUNT - CP ACIAB_BUFSZ ; COMPARE TO BUFFER SIZE - ;RET Z ; BAIL OUT IF BUFFER FULL, RCV BYTE DISCARDED - JR Z,ACIAB_INT2 ; BAIL OUT IF BUFFER FULL, RCV BYTE DISCARDED + DEC C ; BACK TO CONTROL PORT + LD B,A ; SAVE BYTE READ + LD L,(IY+6) ; SET HL TO + LD H,(IY+7) ; ... START OF BUFFER STRUCT + LD A,(HL) ; GET COUNT + CP ACIA_BUFSZ ; COMPARE TO BUFFER SIZE + JR Z,ACIA_INTRCV4 ; BAIL OUT IF BUFFER FULL, RCV BYTE DISCARDED INC A ; INCREMENT THE COUNT - LD (ACIAB_BUFCNT),A ; AND SAVE IT - CP ACIAB_BUFSZ - 5 ; BUFFER GETTING FULL? - JR NZ,ACIAB_INT0 ; IF NOT, BYPASS DEASSERTING RTS - LD A,ACIA_RTSOFF ; VALUE TO DEASSERT RTS + LD (HL),A ; AND SAVE IT + CP ACIA_BUFSZ / 2 ; BUFFER GETTING FULL? + JR NZ,ACIA_INTRCV2 ; IF NOT, BYPASS CLEARING RTS + LD A,(ACIA_CMD) ; CONFIG BYTE W/O RTS BIT + OR ACIA_RTSOFF ; CLEAR RTS OUT (C),A ; DO IT -ACIAB_INT0: - LD HL,(ACIAB_HD) ; GET HEAD POINTER - LD A,L ; GET LOW BYTE - CP ACIAB_BUFEND & $FF ; PAST END? - JR NZ,ACIAB_INT1 ; IF NOT, BYPASS POINTER RESET - LD HL,ACIAB_BUF ; ... OTHERWISE, RESET TO START OF BUFFER -ACIAB_INT1: - LD A,E ; RECOVER BYTE READ - LD (HL),A ; SAVE RECEIVED BYTE TO HEAD POSITION - INC HL ; INCREMENT HEAD POINTER - LD (ACIAB_HD),HL ; SAVE IT -; -ACIAB_INT2: +ACIA_INTRCV2: + INC HL ; HL NOW HAS ADR OF HEAD PTR + PUSH HL ; SAVE ADR OF HEAD PTR + LD A,(HL) ; DEREFERENCE HL + INC HL + LD H,(HL) + LD L,A ; HL IS NOW ACTUAL HEAD PTR + LD (HL),B ; SAVE CHARACTER RECEIVED IN BUFFER AT HEAD + INC HL ; BUMP HEAD POINTER + POP DE ; RECOVER ADR OF HEAD PTR + LD A,L ; GET LOW BYTE OF HEAD PTR + SUB ACIA_BUFSZ+4 ; SUBTRACT SIZE OF BUFFER AND POINTER + CP E ; IF EQUAL TO START, HEAD PTR IS PAST BUF END + JR NZ,ACIA_INTRCV3 ; IF NOT, BYPASS + LD H,D ; SET HL TO + LD L,E ; ... HEAD PTR ADR + INC HL ; BUMP PAST HEAD PTR + INC HL + INC HL + INC HL ; ... SO HL NOW HAS ADR OF ACTUAL BUFFER START +ACIA_INTRCV3: + EX DE,HL ; DE := HEAD PTR VAL, HL := ADR OF HEAD PTR + LD (HL),E ; SAVE UPDATED HEAD PTR + INC HL + LD (HL),D ; CHECK FOR MORE PENDING... IN A,(C) ; GET STATUS RRA ; READY BIT TO CF - JR C,ACIAB_INT00 ; IF SET, DO SOME MORE + JR C,ACIA_INTRCV1 ; IF SET, DO SOME MORE +ACIA_INTRCV4: OR $FF ; NZ SET TO INDICATE INT HANDLED RET ; AND RETURN ; @@ -249,15 +249,16 @@ ACIAB_INT2: ; DRIVER FUNCTION TABLE ; ACIA_FNTBL: - .DW ACIA_IN - .DW ACIA_OUT - .DW ACIA_IST - .DW ACIA_OST - .DW ACIA_INITDEV - .DW ACIA_QUERY - .DW ACIA_DEVICE + .DW ACIA_IN + .DW ACIA_OUT + .DW ACIA_IST + .DW ACIA_OST + .DW ACIA_INITDEV + .DW ACIA_QUERY + .DW ACIA_DEVICE #IF (($ - ACIA_FNTBL) != (CIO_FNCNT * 2)) - .ECHO "*** INVALID ACIA FUNCTION TABLE ***\n" + .ECHO "*** INVALID ACIA FUNCTION TABLE ***\n" + !!! ; FORCE AN ASSEMBLY ERROR #ENDIF ; ; @@ -265,75 +266,57 @@ ACIA_FNTBL: #IF (INTMODE == 0) ; ACIA_IN: - CALL ACIA_IST ; CHAR WAITING? - JR Z,ACIA_IN ; LOOP IF NOT - LD C,(IY+3) ; C := ACIA BASE PORT - INC C ; BUMP TO DATA PORT - IN E,(C) ; GET BYTE - XOR A ; SIGNAL SUCCESS - RET + CALL ACIA_IST ; CHAR WAITING? + JR Z,ACIA_IN ; LOOP IF NOT + LD C,(IY+3) ; C := ACIA BASE PORT + INC C ; BUMP TO DATA PORT + IN E,(C) ; GET BYTE + XOR A ; SIGNAL SUCCESS + RET ; #ELSE ; ACIA_IN: - LD A,(IY+2) ; GET MODULE ID - OR A ; SET FLAGS - JR Z,ACIAA_IN ; HANDLE MODULE 0 - DEC A ; TEST FOR NEXT - JR Z,ACIAB_IN ; HANDLE MODULE 1 - CALL PANIC ; ELSE FATAL ERROR - RET -; -ACIAA_IN: - CALL ACIAA_IST ; RECEIVED CHAR READY? - JR Z,ACIAA_IN ; LOOP TILL WE HAVE SOMETHING IN BUFFER - HB_DI ; AVOID COLLISION WITH INT HANDLER - LD A,(ACIAA_BUFCNT) ; GET COUNT - DEC A ; DECREMENT COUNT - LD (ACIAA_BUFCNT),A ; SAVE SAVE IT - CP 5 ; BUFFER LOW THRESHOLD - JR NZ,ACIAA_IN0 ; IF NOT, BYPASS SETTING RTS - - LD C,(IY+3) ; C := ACIA CMD PORT - LD A,ACIA_RTSON ; ASSERT RTS - OUT (C),A ; DO IT -ACIAA_IN0: - LD HL,(ACIAA_TL) ; GET BUFFER TAIL POINTER - LD E,(HL) ; GET BYTE - INC HL ; BUMP TAIL POINTER - LD A,L ; GET LOW BYTE - CP ACIAA_BUFEND & $FF ; PAST END? - JR NZ,ACIAA_IN1 ; IF NOT, BYPASS POINTER RESET - LD HL,ACIAA_BUF ; ... OTHERWISE, RESET TO START OF BUFFER -ACIAA_IN1: - LD (ACIAA_TL),HL ; SAVE UPDATED TAIL POINTER - HB_EI ; INTERRUPTS OK AGAIN - XOR A ; SIGNAL SUCCESS - RET ; AND DONE -; -ACIAB_IN: - CALL ACIAB_IST ; RECEIVED CHAR READY? - JR Z,ACIAB_IN ; LOOP TILL WE HAVE SOMETHING IN BUFFER + CALL ACIA_IST ; SEE IF CHAR AVAILABLE + JR Z,ACIA_IN ; LOOP UNTIL SO HB_DI ; AVOID COLLISION WITH INT HANDLER - LD A,(ACIAB_BUFCNT) ; GET COUNT + LD L,(IY+6) ; SET HL TO + LD H,(IY+7) ; ... START OF BUFFER STRUCT + LD A,(HL) ; GET COUNT DEC A ; DECREMENT COUNT - LD (ACIAB_BUFCNT),A ; SAVE SAVE IT - CP 5 ; BUFFER LOW THRESHOLD - JR NZ,ACIAB_IN0 ; IF NOT, BYPASS SETTING RTS - - LD C,(IY+3) ; C := ACIA CMD PORT - LD A,ACIA_RTSON ; ASSERT RTS + LD (HL),A ; SAVE UPDATED COUNT + CP ACIA_BUFSZ / 4 ; BUFFER LOW THRESHOLD + JR NZ,ACIA_IN1 ; IF NOT, BYPASS SETTING RTS + LD C,(IY+3) ; C IS CMD/STATUS PORT ADR + LD A,(ACIA_CMD) ; CONFIG BYTE W/O RTS BIT + OR ACIA_RTSON ; SET RTS OUT (C),A ; DO IT -ACIAB_IN0: - LD HL,(ACIAB_TL) ; GET BUFFER TAIL POINTER - LD E,(HL) ; GET BYTE - INC HL ; BUMP TAIL POINTER - LD A,L ; GET LOW BYTE - CP ACIAB_BUFEND & $FF ; PAST END? - JR NZ,ACIAB_IN1 ; IF NOT, BYPASS POINTER RESET - LD HL,ACIAB_BUF ; ... OTHERWISE, RESET TO START OF BUFFER -ACIAB_IN1: - LD (ACIAB_TL),HL ; SAVE UPDATED TAIL POINTER +ACIA_IN1: + INC HL + INC HL + INC HL ; HL NOW HAS ADR OF TAIL PTR + PUSH HL ; SAVE ADR OF TAIL PTR + LD A,(HL) ; DEREFERENCE HL + INC HL + LD H,(HL) + LD L,A ; HL IS NOW ACTUAL TAIL PTR + LD C,(HL) ; C := CHAR TO BE RETURNED + INC HL ; BUMP TAIL PTR + POP DE ; RECOVER ADR OF TAIL PTR + LD A,L ; GET LOW BYTE OF TAIL PTR + SUB ACIA_BUFSZ+2 ; SUBTRACT SIZE OF BUFFER AND POINTER + CP E ; IF EQUAL TO START, TAIL PTR IS PAST BUF END + JR NZ,ACIA_IN2 ; IF NOT, BYPASS + LD H,D ; SET HL TO + LD L,E ; ... TAIL PTR ADR + INC HL ; BUMP PAST TAIL PTR + INC HL ; ... SO HL NOW HAS ADR OF ACTUAL BUFFER START +ACIA_IN2: + EX DE,HL ; DE := TAIL PTR VAL, HL := ADR OF TAIL PTR + LD (HL),E ; SAVE UPDATED TAIL PTR + INC HL + LD (HL),D + LD E,C ; MOVE CHAR TO RETURN TO E HB_EI ; INTERRUPTS OK AGAIN XOR A ; SIGNAL SUCCESS RET ; AND DONE @@ -343,257 +326,404 @@ ACIAB_IN1: ; ; ACIA_OUT: - CALL ACIA_OST ; READY FOR CHAR? - JR Z,ACIA_OUT ; LOOP IF NOT - LD C,(IY+3) ; C := ACIA CMD PORT - INC C ; BUMP TO DATA PORT - OUT (C),E ; SEND CHAR FROM E - XOR A ; SIGNAL SUCCESS - RET + CALL ACIA_OST ; READY FOR CHAR? + JR Z,ACIA_OUT ; LOOP IF NOT + LD C,(IY+3) ; C := ACIA CMD PORT + INC C ; BUMP TO DATA PORT + OUT (C),E ; SEND CHAR FROM E + XOR A ; SIGNAL SUCCESS + RET ; ; ; #IF (INTMODE == 0) ; ACIA_IST: - LD C,(IY+3) ; STATUS PORT - IN A,(C) ; GET STATUS - AND $01 ; ISOLATE BIT 0 (RX READY) - JP Z,CIO_IDLE ; NOT READY, RETURN VIA IDLE PROCESSING - XOR A ; ZERO ACCUM - INC A ; ASCCUM := 1 TO SIGNAL 1 CHAR WAITING - RET ; DONE + LD C,(IY+3) ; STATUS PORT + IN A,(C) ; GET STATUS + AND $01 ; ISOLATE BIT 0 (RX READY) + JP Z,CIO_IDLE ; NOT READY, RETURN VIA IDLE PROCESSING + XOR A ; ZERO ACCUM + INC A ; ASCCUM := 1 TO SIGNAL 1 CHAR WAITING + RET ; DONE ; #ELSE ; ACIA_IST: - LD A,(IY+2) ; GET MODULE ID - OR A ; SET FLAGS - JR Z,ACIAA_IST ; HANDLE MODULE 0 - DEC A ; TEST FOR NEXT - JR Z,ACIAB_IST ; HANDLE MODULE 1 - CALL PANIC ; ELSE FATAL ERROR - RET -; -ACIAA_IST: - LD A,(ACIAA_BUFCNT) ; GET BUFFER UTILIZATION COUNT - OR A ; SET FLAGS - JP Z,CIO_IDLE ; NOT READY, RETURN VIA IDLE PROCESSING - RET ; AND DONE -; -ACIAB_IST: - LD A,(ACIAB_BUFCNT) ; GET BUFFER UTILIZATION COUNT + LD L,(IY+6) ; GET ADDRESS + LD H,(IY+7) ; ... OF RECEIVE BUFFER + LD A,(HL) ; BUFFER UTILIZATION COUNT OR A ; SET FLAGS JP Z,CIO_IDLE ; NOT READY, RETURN VIA IDLE PROCESSING - RET ; AND DONE + RET ; #ENDIF ; ; ; ACIA_OST: - LD C,(IY+3) ; CMD PORT - IN A,(C) ; GET STATUS - AND $02 ; ISOLATE BIT 2 (TX EMPTY) - JP Z,CIO_IDLE ; NOT READY, RETURN VIA IDLE PROCESSING - XOR A ; ZERO ACCUM - INC A ; ACCUM := 1 TO SIGNAL 1 BUFFER POSITION - RET ; DONE + LD C,(IY+3) ; CMD PORT + IN A,(C) ; GET STATUS + AND $02 ; ISOLATE BIT 2 (TX EMPTY) + JP Z,CIO_IDLE ; NOT READY, RETURN VIA IDLE PROCESSING + XOR A ; ZERO ACCUM + INC A ; ACCUM := 1 TO SIGNAL 1 BUFFER POSITION + RET ; DONE ; ; ; ACIA_INITDEV: - HB_DI ; AVOID CONFLICTS - CALL ACIA_INITDEVX ; DO THE REAL WORK - HB_EI ; INTS BACK ON - RET ; DONE -; -ACIA_INITDEVX: + HB_DI ; AVOID CONFLICTS + CALL ACIA_INITDEVX ; DO THE REAL WORK + HB_EI ; INTS BACK ON + RET ; DONE ; ; THIS ENTRY POINT BYPASSES DISABLING/ENABLING INTS WHICH IS REQUIRED BY ; PREINIT ABOVE. PREINIT IS NOT ALLOWED TO ENABLE INTS! ; - ; PROGRAM THE ACIA CHIP - LD C,(IY+3) ; COMMAND PORT - LD A,$FF ; MASTER RESET - OUT (C),A ; DO IT - LD A,ACIA_RTSON ; NORMAL OPERATION - OUT (C),A ; DO IT +ACIA_INITDEVX: +; +#IF (ACIADEBUG) + CALL NEWLINE + PRTS("ACIA$") + LD A,(IY+2) + CALL PRTDECB + CALL COUT + CALL PC_COLON +#ENDIF +; + ; TEST FOR -1 WHICH MEANS USE CURRENT CONFIG (JUST REINIT) + LD A,D ; TEST DE FOR + AND E ; ... VALUE OF -1 + INC A ; ... SO Z SET IF -1 + JR NZ,ACIA_INITDEV1 ; IF DE == -1, REINIT CURRENT CONFIG +; + ; LOAD EXISTING CONFIG TO REINIT + LD E,(IY+4) ; LOW BYTE + LD D,(IY+5) ; HIGH BYTE +; +ACIA_INITDEV1: +; +#IF (ACIADEBUG) + PUSH DE + POP BC + PRTS(" CFG=$") + CALL PRTHEXWORD +#ENDIF +; + LD A,E ; GET CONFIG LSB + AND $E0 ; CHECK FOR DTR, XON, PARITY=MARK/SPACE + JR NZ,ACIA_INITFAIL ; IF ANY BIT SET, FAIL, NOT SUPPORTED +; + LD A,D ; GET CONFIG MSB + AND $1F ; ISOLATE ENCODED BAUD RATE +; +#IF (ACIADEBUG) + PRTS(" ENC=$") + CALL PRTHEXBYTE +#ENDIF +; + ; BAUD RATE + PUSH DE ; SAVE REQUESTED CONFIG + LD L,(IY+10) ; LOAD CLK FREQ + LD H,(IY+11) ; ... INTO DE:HL + LD E,(IY+12) ; ... " + LD D,(IY+13) ; ... " + LD C,75 ; BAUD RATE ENCODING CONSTANT + CALL ENCODE ; C = TEST BAUD RATE (ENCODED) = BAUDTST + POP DE ; GET REQ CONFIG BACK, D = BAUDREQ +; + ; BIT 4 (DIV 3) OF BAUDREQ AND BAUDTST MUST MATCH! + LD A,C ; A = BAUDTST + XOR D ; XOR WITH BAUDREQ + BIT 4,A ; DO BIT 4 VALS MATCH? + JR NZ,ACIA_INITFAIL ; IF NOT, BAIL OUT +; + LD A,C ; BAUDTST TO A + AND $0F ; ISOLATE DIV 2 BAUD BITS + LD C,A ; C = BAUDTST +; + LD A,D ; MSB W/ BAUD RATE TO A + AND $0F ; ISOLATE DIV 2 BAUD BITS + LD L,A ; L = BAUDREQ +; + LD A,C ; A = BAUDTST + LD B,%00000000 ; ACIA VAL FOR DIV 1 + CP L ; BAUDTST = BAUDREQ? + JR Z,ACIA_INITBROK ; IF MATCH, WE ARE DONE +; + SUB 4 ; DIVIDE BY 16 (NOW DIV 16 TOT) + JR C,ACIA_INITFAIL ; FAIL IF UNDERFLOW + LD B,%00000001 ; ACIA VAL FOR DIV 16 + CP L ; BAUDTST = BAUDREQ? + JR Z,ACIA_INITBROK ; IF MATCH, WE ARE DONE +; + SUB 2 ; DIVIDE BY 4 (NOW DIV 64 TOT) + JR C,ACIA_INITFAIL ; FAIL IF UNDERFLOW + LD B,%00000010 ; ACIA R4 VAL FOR DIV 32 + CP L ; BAUDTST = BAUDREQ? + JR Z,ACIA_INITBROK ; IF MATCH, WE ARE DONE +; +ACIA_INITFAIL: +; +#IF (ACIADEBUG) + PRTS(" BAD CFG$") +#ENDIF +; + OR $FF + RET ; INVALID CONFIG +; +ACIA_INITBROK: + ; REG B HAS WORKING CONFIG VALUE W/ BAUD RATE BITS + LD C,B ; WORKING VAL TO C + LD A,E ; LSB OF INCOMING CONFIG + AND %00111111 ; ISOLATE LOW 6 BITS TO COMPARE + LD B,8 ; WORD SELECT TABLE SIZE + LD HL,ACIA_WSTBL ; POINT TO TABLE +ACIA_INITWS: + CP (HL) ; MATCH? + JR Z,ACIA_INITWS2 ; IF SO, REG B HAS ACIA VAL + 1 + INC HL ; NEXT ENTRY + DJNZ ACIA_INITWS ; KEEP CHECKING TILL DONE + JR ACIA_INITFAIL ; FAIL IF NO MATCH + +ACIA_WSTBL: + .DB %00001011 ; 8/O/1 + .DB %00011011 ; 8/E/1 + .DB %00000011 ; 8/N/1 + .DB %00000111 ; 8/N/2 + .DB %00001010 ; 7/O/1 + .DB %00011010 ; 7/E/1 + .DB %00001110 ; 7/O/2 + .DB %00011110 ; 7/E/2 + +ACIA_INITWS2: + LD A,B ; PUT FANAL VALUE IN A + DEC A ; ZERO INDEX ADJUSTMENT + RLA ; MOVE BITS TO + RLA ; ... PROPER LOCATION + OR C ; COMBINE WITH WORKING VALUE + JR ACIA_INITGO +; +ACIA_INITSAFE: + LD A,%00010110 ; DEFAULT CONFIG +; +ACIA_INITGO: ; #IF (INTMODE > 0) + OR %10000000 ; ENABLE RCV INT +#ENDIF ; - ; RESET THE RECEIVE BUFFER - LD E,(IY+6) - LD D,(IY+7) ; DE := _CNT - XOR A ; A := 0 - LD (DE),A ; _CNT = 0 - INC DE ; DE := ADR OF _HD - PUSH DE ; SAVE IT - INC DE - INC DE - INC DE - INC DE ; DE := ADR OF _BUF - POP HL ; HL := ADR OF _HD - LD (HL),E - INC HL - LD (HL),D ; _HD := _BUF - INC HL - LD (HL),E - INC HL - LD (HL),D ; _TL := _BUF + LD (ACIA_CMD),A ; SAVE SHADOW REGISTER ; +#IF (ACIADEBUG) + PRTS(" CMD=$") + CALL PRTHEXBYTE + LD DE,65 + CALL VDELAY ; WAIT FOR FINAL CHAR TO SEND #ENDIF ; - XOR A ; SIGNAL SUCCESS - RET ; RETURN + ; PROGRAM THE ACIA CHIP + LD C,(IY+3) ; COMMAND PORT + LD A,$FF ; MASTER RESET + OUT (C),A ; DO IT + LD A,(ACIA_CMD) ; RESTORE CONFIG VALUE + OUT (C),A ; DO IT +; +#IF (INTMODE > 0) +; + ; RESET THE RECEIVE BUFFER + LD E,(IY+6) + LD D,(IY+7) ; DE := _CNT + XOR A ; A := 0 + LD (DE),A ; _CNT = 0 + INC DE ; DE := ADR OF _HD + PUSH DE ; SAVE IT + INC DE + INC DE + INC DE + INC DE ; DE := ADR OF _BUF + POP HL ; HL := ADR OF _HD + LD (HL),E + INC HL + LD (HL),D ; _HD := _BUF + INC HL + LD (HL),E + INC HL + LD (HL),D ; _TL := _BUF +; +#ENDIF +; + XOR A ; SIGNAL SUCCESS + RET ; RETURN ; ; ; ACIA_QUERY: - LD E,(IY+4) ; FIRST CONFIG BYTE TO E - LD D,(IY+5) ; SECOND CONFIG BYTE TO D - XOR A ; SIGNAL SUCCESS - RET ; DONE + LD E,(IY+4) ; FIRST CONFIG BYTE TO E + LD D,(IY+5) ; SECOND CONFIG BYTE TO D + XOR A ; SIGNAL SUCCESS + RET ; DONE ; ; ; ACIA_DEVICE: - LD D,CIODEV_ACIA ; D := DEVICE TYPE - LD E,(IY) ; E := PHYSICAL UNIT - XOR A ; SIGNAL SUCCESS - RET + LD D,CIODEV_ACIA ; D := DEVICE TYPE + LD E,(IY) ; E := PHYSICAL UNIT + LD C,$00 ; C := DEVICE TYPE, 0x00 IS RS-232 + XOR A ; SIGNAL SUCCESS + RET ; ; ACIA DETECTION ROUTINE ; +; NOTE THAT THE ACIA MODULES ONLY QUALIFY ADDRESS BITS 7 & 6, SO +; THE ACIA'S TWO PORTS APPEAR REPEATEDLY OVER AN ADDRESS RANGE +; OF $40 STARTING FROM THE REAL BASE PORT. +; WE TAKE ADVANTAGE OF THIS TO AVOID CONFLICTING WITH SIO +; AND COMPACT FLASH MODULES DURING DETECTION PROBES. +; ACIA_DETECT: - ;LD C,ACIA_BASE ; BASE PORT ADDRESS - LD C,(IY+3) ; BASE PORT ADDRESS - CALL ACIA_DETECT2 ; CHECK IT - JR Z,ACIA_DETECT1 ; FOUND IT, RECORD IT - ;LD C,ACIA_ALTBASE ; ALT BASE PORT ADDRESS - ;CALL ACIA_DETECT2 ; CHECK IT - ;JR Z,ACIA_DETECT1 ; FOUND IT, RECORD IT - LD A,ACIA_NONE ; NOTHING FOUND - RET ; DONE -; -ACIA_DETECT1: - ; ACIA FOUND, RECORD IT - ;LD A,C ; BASE PORT ADDRESS TO A - ;LD (IY+3),A ; SAVE ACTIVE BASE PORT - LD A,ACIA_ACIA ; RETURN CHIP TYPE - RET ; DONE + LD A,(IY+3) ; BASE PORT ADDRESS + ADD A,$20 ; OFFSET (SEE ABOVE) + LD C,A ; PUT IN C FOR I/O + CALL ACIA_DETECT2 ; CHECK IT + JR Z,ACIA_DETECT1 ; FOUND IT, RECORD IT + LD A,ACIA_NONE ; NOTHING FOUND + RET ; DONE +; +ACIA_DETECT1: + ; ACIA FOUND, RECORD IT + LD A,ACIA_ACIA ; RETURN CHIP TYPE + RET ; DONE ; ACIA_DETECT2: - ; LOOK FOR ACIA AT PORT ADDRESS IN C - LD A,$03 ; MASTER RESET - OUT (C),A ; DO IT - IN A,(C) ; GET STATUS - OR A ; CHECK FOR ZERO - RET NZ ; RETURN IF NOT ZERO - LD A,$02 ; CLEAR MASTER RESET - OUT (C),A ; DO IT - ; CHECK FOR EXPECTED BITS: - ; TDRE=1, DCD & CTS = 0 - AND %00001110 ; BIT MASK FOR "STABLE" BITS - CP %00000010 ; EXPECTED VALUE - RET ; RETURN RESULT, Z = CHIP FOUND + ; LOOK FOR ACIA AT PORT ADDRESS IN C + LD A,$03 ; MASTER RESET + OUT (C),A ; DO IT + IN A,(C) ; GET STATUS + OR A ; CHECK FOR ZERO + RET NZ ; RETURN IF NOT ZERO + LD A,$02 ; CLEAR MASTER RESET + OUT (C),A ; DO IT + ; CHECK FOR EXPECTED BITS: + ; TDRE=1, DCD & CTS = 0 + AND %00001110 ; BIT MASK FOR "STABLE" BITS + CP %00000010 ; EXPECTED VALUE + RET ; RETURN RESULT, Z = CHIP FOUND ; ; ; ACIA_PRTCFG: - ; ANNOUNCE PORT - CALL NEWLINE ; FORMATTING - PRTS("ACIA$") ; FORMATTING - LD A,(IY) ; DEVICE NUM - CALL PRTDECB ; PRINT DEVICE NUM - PRTS(": IO=0x$") ; FORMATTING - LD A,(IY+3) ; GET BASE PORT - CALL PRTHEXBYTE ; PRINT BASE PORT + ; ANNOUNCE PORT + CALL NEWLINE ; FORMATTING + PRTS("ACIA$") ; FORMATTING + LD A,(IY) ; DEVICE NUM + CALL PRTDECB ; PRINT DEVICE NUM + PRTS(": IO=0x$") ; FORMATTING + LD A,(IY+3) ; GET BASE PORT + CALL PRTHEXBYTE ; PRINT BASE PORT - ; PRINT THE ACIA TYPE - CALL PC_SPACE ; FORMATTING - LD A,(IY+1) ; GET ACIA TYPE BYTE - RLCA ; MAKE IT A WORD OFFSET - LD HL,ACIA_TYPE_MAP ; POINT HL TO TYPE MAP TABLE - CALL ADDHLA ; HL := ENTRY - LD E,(HL) ; DEREFERENCE - INC HL ; ... - LD D,(HL) ; ... TO GET STRING POINTER - CALL WRITESTR ; PRINT IT -; - ; ALL DONE IF NO ACIA WAS DETECTED - LD A,(IY+1) ; GET ACIA TYPE BYTE - OR A ; SET FLAGS - RET Z ; IF ZERO, NOT PRESENT + ; PRINT THE ACIA TYPE + CALL PC_SPACE ; FORMATTING + LD A,(IY+1) ; GET ACIA TYPE BYTE + RLCA ; MAKE IT A WORD OFFSET + LD HL,ACIA_TYPE_MAP ; POINT HL TO TYPE MAP TABLE + CALL ADDHLA ; HL := ENTRY + LD E,(HL) ; DEREFERENCE + INC HL ; ... + LD D,(HL) ; ... TO GET STRING POINTER + CALL WRITESTR ; PRINT IT ; - PRTS(" MODE=$") ; FORMATTING - LD E,(IY+4) ; LOAD CONFIG - LD D,(IY+5) ; ... WORD TO DE - CALL PS_PRTSC0 ; PRINT CONFIG + ; ALL DONE IF NO ACIA WAS DETECTED + LD A,(IY+1) ; GET ACIA TYPE BYTE + OR A ; SET FLAGS + RET Z ; IF ZERO, NOT PRESENT ; - XOR A - RET + PRTS(" MODE=$") ; FORMATTING + LD E,(IY+4) ; LOAD CONFIG + LD D,(IY+5) ; ... WORD TO DE + CALL PS_PRTSC0 ; PRINT CONFIG +; + XOR A + RET ; ; ; ACIA_TYPE_MAP: - .DW ACIA_STR_NONE - .DW ACIA_STR_ACIA + .DW ACIA_STR_NONE + .DW ACIA_STR_ACIA -ACIA_STR_NONE .DB "$" -ACIA_STR_ACIA .DB "ACIA$" +ACIA_STR_NONE .DB "$" +ACIA_STR_ACIA .DB "ACIA$" ; ; WORKING VARIABLES ; -ACIA_DEV .DB 0 ; DEVICE NUM USED DURING INIT +ACIA_DEV .DB 0 ; DEVICE NUM USED DURING INIT +ACIA_CMD .DB 0 ; COMMAND PORT SHADOW REGISTER ; #IF (INTMODE == 0) ; -ACIAA_RCVBUF .EQU 0 -ACIAB_RCVBUF .EQU 0 +ACIA0_RCVBUF .EQU 0 +ACIA1_RCVBUF .EQU 0 ; #ELSE ; ; RECEIVE BUFFERS ; -ACIAA_RCVBUF: -ACIAA_BUFCNT .DB 0 ; CHARACTERS IN RING BUFFER -ACIAA_HD .DW ACIAA_BUF ; BUFFER HEAD POINTER -ACIAA_TL .DW ACIAA_BUF ; BUFFER TAIL POINTER -ACIAA_INTP .DW ACIAA_INT ; INT HANDLER POINTER -ACIAA_BUF .FILL 32,0 ; RECEIVE RING BUFFER -ACIAA_BUFEND .EQU $ ; END OF BUFFER -ACIAA_BUFSZ .EQU $ - ACIAA_BUF ; SIZE OF RING BUFFER -; -ACIAB_RCVBUF: -ACIAB_BUFCNT .DB 0 ; CHARACTERS IN RING BUFFER -ACIAB_HD .DW ACIAB_BUF ; BUFFER HEAD POINTER -ACIAB_TL .DW ACIAB_BUF ; BUFFER TAIL POINTER -ACIAB_INTP .DW ACIAB_INT ; INT HANDLER POINTER -ACIAB_BUF .FILL 32,0 ; RECEIVE RING BUFFER -ACIAB_BUFEND .EQU $ ; END OF BUFFER -ACIAB_BUFSZ .EQU $ - ACIAB_BUF ; SIZE OF RING BUFFER +ACIA0_RCVBUF: +ACIA0_BUFCNT .DB 0 ; CHARACTERS IN RING BUFFER +ACIA0_HD .DW ACIA0_BUF ; BUFFER HEAD POINTER +ACIA0_TL .DW ACIA0_BUF ; BUFFER TAIL POINTER +ACIA0_BUF .FILL 32,0 ; RECEIVE RING BUFFER +ACIA0_BUFEND .EQU $ ; END OF BUFFER +ACIA0_BUFSZ .EQU $ - ACIA0_BUF ; SIZE OF RING BUFFER +; +#IF (ACIACNT >= 2) +; +ACIA1_RCVBUF: +ACIA1_BUFCNT .DB 0 ; CHARACTERS IN RING BUFFER +ACIA1_HD .DW ACIA1_BUF ; BUFFER HEAD POINTER +ACIA1_TL .DW ACIA1_BUF ; BUFFER TAIL POINTER +ACIA1_BUF .FILL 32,0 ; RECEIVE RING BUFFER +ACIA1_BUFEND .EQU $ ; END OF BUFFER +ACIA1_BUFSZ .EQU $ - ACIA1_BUF ; SIZE OF RING BUFFER +; +#ENDIF ; #ENDIF ; ; ACIA PORT TABLE ; ACIA_CFG: -ACIAA_CFG: - ; ACIA MODULE A CONFIG - .DB 0 ; DEVICE NUMBER (SET DURING INIT) - .DB 0 ; ACIA TYPE (SET DURING INIT) - .DB 0 ; MODULE ID - .DB ACIAA_BASE ; BASE PORT (SET DURING DETECT) - .DW DEFSERCFG ; LINE CONFIGURATION - .DW ACIAA_RCVBUF ; POINTER TO RCV BUFFER STRUCT -ACIAB_CFG: - ; ACIA MODULE B CONFIG - .DB 0 ; DEVICE NUMBER (SET DURING INIT) - .DB 0 ; ACIA TYPE (SET DURING INIT) - .DB 1 ; MODULE ID - .DB ACIAB_BASE ; BASE PORT (SET DURING DETECT) - .DW DEFSERCFG ; LINE CONFIGURATION - .DW ACIAB_RCVBUF ; POINTER TO RCV BUFFER STRUCT -; -ACIA_CNT .EQU ($ - ACIA_CFG) / 8 +; +ACIA0_CFG: + ; ACIA MODULE A CONFIG + .DB 0 ; DEVICE NUMBER (SET DURING INIT) + .DB 0 ; ACIA TYPE (SET DURING INIT) + .DB 0 ; MODULE ID + .DB ACIA0BASE ; BASE PORT + .DW ACIA0CFG ; LINE CONFIGURATION + .DW ACIA0_RCVBUF ; POINTER TO RCV BUFFER STRUCT + .DW ACIA0_INT ; INT HANDLER POINTER + .DW (ACIA0CLK / ACIA0DIV) & $FFFF ; CLOCK FREQ AS + .DW (ACIA0CLK / ACIA0DIV) >> 16 ; ... DWORD VALUE +; +ACIA_CFGSIZ .EQU $ - ACIA_CFG ; SIZE OF ONE CFG TABLE ENTRY +; +#IF (ACIACNT >= 2) +; +ACIA1_CFG: + ; ACIA MODULE B CONFIG + .DB 0 ; DEVICE NUMBER (SET DURING INIT) + .DB 0 ; ACIA TYPE (SET DURING INIT) + .DB 1 ; MODULE ID + .DB ACIA1BASE ; BASE PORT + .DW ACIA1CFG ; LINE CONFIGURATION + .DW ACIA1_RCVBUF ; POINTER TO RCV BUFFER STRUCT + .DW ACIA1_INT ; INT HANDLER POINTER + .DW (ACIA1CLK / ACIA1DIV) & $FFFF ; CLOCK FREQ AS + .DW (ACIA1CLK / ACIA1DIV) >> 16 ; ... DWORD VALUE +; +#ENDIF +; +ACIA_CFGCNT .EQU ($ - ACIA_CFG) / ACIA_CFGSIZ diff --git a/Source/HBIOS/ansi.asm b/Source/HBIOS/ansi.asm index 77435b24..1f2d5f8a 100644 --- a/Source/HBIOS/ansi.asm +++ b/Source/HBIOS/ansi.asm @@ -287,7 +287,7 @@ ANSI_C0DISP: CP $0A ; LF: LINEFEED JP Z,ANSI_LF CP $0B ; VT: VERTICAL TAB - JP Z,ANSI_LF ; TREAD AS LINEFEED + JP Z,ANSI_LF ; TREAT AS LINEFEED CP $0C ; FF: FORMFEED JP Z,ANSI_LF ; TREAT AS LINEFEED CP $0D ; CR: CARRIAGE RETURN diff --git a/Source/HBIOS/asci.asm b/Source/HBIOS/asci.asm index d3ef04c7..26a71d87 100644 --- a/Source/HBIOS/asci.asm +++ b/Source/HBIOS/asci.asm @@ -5,10 +5,10 @@ ; ; SETUP PARAMETER WORD: ; +-------+---+-------------------+ +---+---+-----------+---+-------+ -; | |RTS| ENCODED BAUD RATE | |DTR|XON| PARITY |STP| 8/7/6 | +; | |RTS| ENCODED BAUD RATE | |DTR|XON| PARITY |STP| 8/7/6 | ; +-------+---+---+---------------+ ----+---+-----------+---+-------+ -; F E D C B A 9 8 7 6 5 4 3 2 1 0 -; -- MSB (D REGISTER) -- -- LSB (E REGISTER) -- +; F E D C B A 9 8 7 6 5 4 3 2 1 0 +; -- MSB (D REGISTER) -- -- LSB (E REGISTER) -- ; ; STAT: ; 7 6 5 4 3 2 1 0 @@ -18,7 +18,7 @@ ; | | | | | | | +-- TIE: TRANSMIT INTERRUPT ENABLE ; | | | | | | +---- TDRE: TRANSMIT DATA REGISTER EMPTY ; | | | | | +------ DCD0/CTS1E: CH0 CARRIER DETECT, CH1 CTS ENABLE -; | | | | +-------- RIE: RECEIVE INTERRUPT ENABLE +; | | | | +-------- RIE: RECEIVE INTERRUPT ENABLE ; | | | +---------- FE: FRAMING ERROR ; | | +------------ PE: PARITY ERROR ; | +-------------- OVRN: OVERRUN ERROR @@ -33,7 +33,7 @@ ; | | | | | | +---- MOD1: PARITY: 0=NONE, 1=ENABLED ; | | | | | +------ MOD2: DATA BITS: 0=7 BITS, 1=8 BITS ; | | | | +-------- MPBR/EFR: MULTIPROCESSOR BIT RECEIVE / ERROR FLAG RESET -; | | | +---------- RTS0/CKA1D: CH0 RTS, CH1 CLOCK DISABLE +; | | | +---------- RTS0/CKA1D: CH0 ~RTS, CH1 CLOCK DISABLE ; | | +------------ TE: TRANSMITTER ENABLE ; | +-------------- RE: RECEIVER ENABLE ; +---------------- MPE: MULTI-PROCESSOR MODE ENABLE @@ -64,355 +64,538 @@ ; | +-------------- DCD0 DISABLE ; +---------------- RDRF INT INHIBIT ; +ASCI_BUFSZ .EQU 32 ; RECEIVE RING BUFFER SIZE +; +ASCI_NONE .EQU 0 ; NOT PRESENT +ASCI_ASCI .EQU 1 ; ORIGINAL ASCI (Z8S180 REV. K) +ASCI_ASCIB .EQU 2 ; REVISED ASCI W/ BRG & FIFO (Z8S180 REV. N) +; +ASCI0_BASE .EQU Z180_BASE ; RELATIVE TO Z180 INTERNAL IO PORTS +ASCI1_BASE .EQU Z180_BASE + 1 ; RELATIVE TO Z180 INTERNAL IO PORTS +; +ASCI_RTS .EQU %00010000 ; ~RTS BIT OF CNTLA REG +; +#IF (INTMODE == 2) +; +ASCI0_IVT .EQU IVT(INT_SER0) +ASCI1_IVT .EQU IVT(INT_SER1) +; +#ENDIF +; +; +; ASCI_PREINIT: ; ; SETUP THE DISPATCH TABLE ENTRIES +; NOTE: INTS WILL BE DISABLED WHEN PREINIT IS CALLED AND THEY MUST REMIAIN +; DISABLED. +; + LD B,ASCI_CFGCNT ; LOOP CONTROL + XOR A ; ZERO TO ACCUM + LD (ASCI_DEV),A ; CURRENT DEVICE NUMBER + LD IY,ASCI_CFG ; POINT TO START OF CFG TABLE +ASCI_PREINIT0: + PUSH BC ; SAVE LOOP CONTROL + CALL ASCI_INITUNIT ; HAND OFF TO GENERIC INIT CODE + POP BC ; RESTORE LOOP CONTROL +; + LD A,(IY+1) ; GET THE ASCI TYPE DETECTED + OR A ; SET FLAGS + JR Z,ASCI_PREINIT2 ; SKIP IT IF NOTHING FOUND +; + PUSH BC ; SAVE LOOP CONTROL + PUSH IY ; CFG ENTRY ADDRESS + POP DE ; ... TO DE + LD BC,ASCI_FNTBL ; BC := FUNCTION TABLE ADDRESS + CALL NZ,CIO_ADDENT ; ADD ENTRY IF ASCI FOUND, BC:DE + POP BC ; RESTORE LOOP CONTROL +; +ASCI_PREINIT2: + LD DE,ASCI_CFGSIZ ; SIZE OF CFG ENTRY + ADD IY,DE ; BUMP IY TO NEXT ENTRY + DJNZ ASCI_PREINIT0 ; LOOP UNTIL DONE +; +#IF (INTMODE >= 1) + ; SETUP INT VECTORS AS APPROPRIATE + LD A,(ASCI_DEV) ; GET DEVICE COUNT + OR A ; SET FLAGS + JR Z,ASCI_PREINIT3 ; IF ZERO, NO ASCI DEVICES, ABORT +; +#IF (INTMODE == 1) + ; ADD IM1 INT CALL LIST ENTRY + LD HL,ASCI_INT ; GET INT VECTOR + CALL HB_ADDIM1 ; ADD TO IM1 CALL LIST +#ENDIF ; -; LD B,2 ; ALWAYS 2 ASCI UNITS ON Z180 -; LD C,0 ; PHYSICAL UNIT INDEX -;ASCI_PREINIT1: -; PUSH BC ; SAVE LOOP CONTROL -; LD D,C ; PHYSICAL UNIT -; LD E,CIODEV_ASCI ; DEVICE TYPE -; LD BC,ASCI_FNTBL ; BC := FUNCTION TABLE ADDRESS -; CALL CIO_ADDENT ; ADD ENTRY, BC IS NOT DESTROYED -; POP BC ; RESTORE LOOP CONTROL -; INC C ; NEXT PHYSICAL UNIT -; DJNZ ASCI_PREINIT1 ; LOOP UNTIL DONE -; - ; ASCI0 CHANNEL - LD D,0 ; DEVICE ID - LD E,CIODEV_ASCI ; DEVICE TYPE - LD BC,ASCI0_FNTBL ; ASCI0 FUNCTION TABLE PTR - CALL CIO_ADDENT - LD DE,-1 ; DE := -1 TO INIT DEFAULT CONFIG - CALL ASCI0_INITDEV ; INIT DEVICE -; - ; ASCI1 CHANNEL - LD D,1 ; DEVICE ID - LD E,CIODEV_ASCI ; DEVICE TYPE - LD BC,ASCI1_FNTBL ; ASCI1 FUNCTION TABLE PTR - CALL CIO_ADDENT - LD DE,-1 ; DE := -1 TO INIT DEFAULT CONFIG - CALL ASCI1_INITDEV ; INIT DEVICE +#IF (INTMODE == 2) + ; SETUP IM2 VECTORS + LD HL,ASCI_INT0 + LD (ASCI0_IVT),HL ; IVT INDEX + LD HL,ASCI_INT1 + LD (ASCI1_IVT),HL ; IVT INDEX +#ENDIF +; +#ENDIF ; +ASCI_PREINIT3: XOR A ; SIGNAL SUCCESS - RET + RET ; AND RETURN ; +; ASCI INITIALIZATION ROUTINE ; +ASCI_INITUNIT: + CALL ASCI_DETECT ; DETERMINE ASCI TYPE + LD (IY+1),A ; SAVE IN CONFIG TABLE + OR A ; SET FLAGS + RET Z ; ABORT IF NOTHING THERE + + ; UPDATE WORKING ASCI DEVICE NUM + LD HL,ASCI_DEV ; POINT TO CURRENT UART DEVICE NUM + LD A,(HL) ; PUT IN ACCUM + INC (HL) ; INCREMENT IT (FOR NEXT LOOP) + LD (IY),A ; UPDATE UNIT NUM ; -ASCI_INIT: + ; IT IS EASY TO SPECIFY A SERIAL CONFIG THAT CANNOT BE IMPLEMENTED + ; DUE TO THE CONSTRAINTS OF THE ASCI. HERE WE FORCE A GENERIC + ; FAILSAFE CONFIG ONTO THE CHANNEL. IF THE SUBSEQUENT "REAL" + ; CONFIG FAILS, AT LEAST THE CHIP WILL BE ABLE TO SPIT DATA OUT + ; AT A RATIONAL BAUD/DATA/PARITY/STOP CONFIG. + CALL ASCI_INITSAFE ; - ; ASCI0 - CALL NEWLINE ; FORMATTING - PRTS("ASCI0: IO=0x$") ; PREFIX - LD A,Z180_TDR0 ; LOAD TDR PORT ADDRESS - CALL PRTHEXBYTE ; PRINT IT - - CALL PC_COMMA ; FORMATTING - LD A,Z180_RDR0 ; LOAD RDR PORT ADDRESS - CALL PRTHEXBYTE ; PRINT IT - - PRTS(" MODE=$") ; FORMATTING - LD DE,(ASCI0_CONFIG) ; LOAD CONFIG - CALL PS_PRTSC0 ; PRINT IT + ; SET DEFAULT CONFIG + LD DE,-1 ; LEAVE CONFIG ALONE + ; CALL INITDEV TO IMPLEMENT CONFIG, BUT NOTE THAT WE CALL + ; THE INITDEVX ENTRY POINT THAT DOES NOT ENABLE/DISABLE INTS! + JP ASCI_INITDEVX ; IMPLEMENT IT AND RETURN ; - ; ASCI1 - CALL NEWLINE ; FORMATTING - PRTS("ASCI1: IO=0x$") ; PREFIX - LD A,Z180_TDR1 ; LOAD TDR PORT ADDRESS - CALL PRTHEXBYTE ; PRINT IT - - CALL PC_COMMA ; FORMATTING - LD A,Z180_RDR1 ; LOAD RDR PORT ADDRESS - CALL PRTHEXBYTE ; PRINT IT - - PRTS(" MODE=$") ; FORMATTING - LD DE,(ASCI1_CONFIG) ; LOAD CONFIG - CALL PS_PRTSC0 ; PRINT IT ; - XOR A - RET ; -; DRIVER ASCI0 FUNCTION TABLE -; -ASCI0_FNTBL: - .DW ASCI0_IN - .DW ASCI0_OUT - .DW ASCI0_IST - .DW ASCI0_OST - .DW ASCI0_INITDEV - .DW ASCI0_QUERY - .DW ASCI0_DEVICE -#IF (($ - ASCI0_FNTBL) != (CIO_FNCNT * 2)) - .ECHO "*** INVALID ASCI0 FUNCTION TABLE ***\n" -#ENDIF +ASCI_INIT: + LD B,ASCI_CFGCNT ; COUNT OF POSSIBLE ASCI UNITS + LD IY,ASCI_CFG ; POINT TO START OF CFG TABLE +ASCI_INIT1: + PUSH BC ; SAVE LOOP CONTROL + LD A,(IY+1) ; GET ASCI TYPE + OR A ; SET FLAGS + CALL NZ,ASCI_PRTCFG ; PRINT IF NOT ZERO + POP BC ; RESTORE LOOP CONTROL + LD DE,ASCI_CFGSIZ ; SIZE OF CFG ENTRY + ADD IY,DE ; BUMP IY TO NEXT ENTRY + DJNZ ASCI_INIT1 ; LOOP TILL DONE ; -ASCI0_IN: - CALL ASCI0_IST - OR A - JR Z,ASCI0_IN - IN0 A,(Z180_RDR0) ; READ THE CHAR - LD E,A - RET + XOR A ; SIGNAL SUCCESS + RET ; DONE ; -ASCI0_IST: - ; CHECK FOR ERROR FLAGS - IN0 A,(Z180_STAT0) - AND 70H ; PARITY, FRAMING, OR OVERRUN ERROR - JR Z,ASCI0_IST1 ; ALL IS WELL, CHECK FOR DATA +; RECEIVE INTERRUPT HANDLER ; - ; CLEAR ERROR(S) OR NOTHING FURTHER CAN BE RECEIVED!!! - IN0 A,(Z180_CNTLA0) - RES 3,A ; CLEAR EFR (ERROR FLAG RESET) - OUT0 (Z180_CNTLA0),A +#IF (INTMODE > 0) ; -ASCI0_IST1: - ; CHECK FOR STAT0.RDRF (DATA READY) - IN0 A,(Z180_STAT0) ; READ LINE STATUS REGISTER - AND $80 ; TEST IF DATA IN RECEIVE BUFFER - JP Z,CIO_IDLE ; DO IDLE PROCESSING AND RETURN - XOR A - INC A ; SIGNAL CHAR READY, A = 1 - RET +; IM1 ENTRY POINT ; -ASCI0_OUT: - CALL ASCI0_OST - OR A - JR Z,ASCI0_OUT - LD A,E - OUT0 (Z180_TDR0),A - RET +ASCI_INT: + ; CHECK/HANDLE FIRST PORT + LD A,(ASCI0_CFG + 1) ; GET ASCI TYPE FOR FIRST ASCI + OR A ; SET FLAGS + CALL NZ,ASCI_INT0 ; CALL IF EXISTS + RET NZ ; DONE IF INT HANDLED ; -ASCI0_OST: - IN0 A,(Z180_STAT0) - AND $02 - JP Z,CIO_IDLE ; DO IDLE PROCESSING AND RETURN - XOR A - INC A ; SIGNAL BUFFER EMPTY, A = 1 - RET + ; CHECK/HANDLE SECOND PORT + LD A,(ASCI1_CFG + 1) ; GET ASCI TYPE FOR SECOND ASCI + OR A ; SET FLAGS + CALL NZ,ASCI_INT1 ; CALL IF EXISTS ; -ASCI0_INITDEV: - ; TEST FOR -1 WHICH MEANS USE CURRENT CONFIG (JUST REINIT) - LD A,D ; TEST DE FOR - AND E ; ... VALUE OF -1 - INC A ; ... SO Z SET IF -1 - JR NZ,ASCI0_INITDEV1 ; IF NEW CONFIG (NOT -1), IMPLEMENT IT - LD DE,(ASCI0_CONFIG) ; OTHERWISE, LOAD EXISTING CONFIG + RET ; DONE ; -ASCI0_INITDEV1: - ; DETERMINE APPROPRIATE CNTLB VALUE (BASED ON BAUDRATE & CPU SPEED) - LD A,D ; BYTE W/ ENCODED BAUD RATE - AND $1F ; ISOLATE BITS - LD L,A ; MOVE TO L - LD H,0 ; CLEAR MSB - PUSH DE ; SAVE CONFIG - CALL ASCI_CNTLB ; DERIVE CNTLB VALUE IN C - POP DE ; RESTORE CONFIG - ;CALL TSTPT - RET NZ ; ABORT ON ERROR +; IM2 ENTRY POINTS +; +ASCI_INT0: + ; INTERRUPT HANDLER FOR FIRST ASCI (ASCI0) + LD IY,ASCI0_CFG ; POINT TO ASCI0 CFG + JR ASCI_INTRCV +; +ASCI_INT1: + ; INTERRUPT HANDLER FOR SECOND ASCI (ASCI1) + LD IY,ASCI1_CFG ; POINT TO ASCI1 CFG + JR ASCI_INTRCV +; +; HANDLE INT FOR A SPECIFIC CHANNEL +; BASED ON UNIT CFG POINTED TO BY IY +; +ASCI_INTRCV: + ; CHECK TO SEE IF SOMETHING IS ACTUALLY THERE + CALL ASCI_ICHK ; CHECK FOR CHAR PENDING + RET Z ; RETURN IF NOTHING AVAILABLE +; +ASCI_INTRCV1: + ; RECEIVE CHARACTER INTO BUFFER + LD A,(IY+3) ; BASE PORT TO A + ADD A,8 ; BUMP TO RDR PORT + LD C,A ; PUT IN C, B IS STILL ZERO + IN A,(C) ; READ PORT + LD B,A ; SAVE BYTE READ + LD L,(IY+6) ; SET HL TO + LD H,(IY+7) ; ... START OF BUFFER STRUCT + LD A,(HL) ; GET COUNT + CP ASCI_BUFSZ ; COMPARE TO BUFFER SIZE + JR Z,ASCI_INTRCV4 ; BAIL OUT IF BUFFER FULL, RCV BYTE DISCARDED + INC A ; INCREMENT THE COUNT + LD (HL),A ; AND SAVE IT + CP ASCI_BUFSZ / 2 ; BUFFER GETTING FULL? + JR NZ,ASCI_INTRCV2 ; IF NOT, BYPASS CLEARING RTS + ; CLEAR RTS + ; THE SECONDARY ASCI PORT ON Z180 ACTUALLY HAS NO RTS LINE + ; AND THE CNTLA BIT FOR THIS PORT CONTROLS THE FUNCTION OF THE + ; MULTIPLEXED CKA1/~TEND0 LINE. BELOW, WE TEST REG C TO SEE IF + ; IT IS AN ODD NUMBERED PORT. IF SO, WE MUST BE ON THE SECONDARY + ; SERIAL PORT, SO WE NEED TO BYPASS MANIPULATING THE RTS BIT. + BIT 0,C ; IS C ADDRESSING AN ODD NUMBERED PORT? + JR NZ,ASCI_INTRCV2 ; IF SO, THIS IS SEC SERIAL, NO RTS! + PUSH BC ; PRESERVE READ CHAR + LD C,(IY+3) ; CNTLA PORT ADR + LD B,0 ; MSB FOR 16 BIT I/O + IN A,(C) ; GET CUR CNTLA VAL + OR ASCI_RTS ; DEASSERT ~RTS + OUT (C),A ; DO IT + POP BC ; RESTORE READ CHAR +ASCI_INTRCV2: + INC HL ; HL NOW HAS ADR OF HEAD PTR + PUSH HL ; SAVE ADR OF HEAD PTR + LD A,(HL) ; DEREFERENCE HL + INC HL + LD H,(HL) + LD L,A ; HL IS NOW ACTUAL HEAD PTR + LD (HL),B ; SAVE CHARACTER RECEIVED IN BUFFER AT HEAD + INC HL ; BUMP HEAD POINTER + POP DE ; RECOVER ADR OF HEAD PTR + LD A,L ; GET LOW BYTE OF HEAD PTR + SUB ASCI_BUFSZ+4 ; SUBTRACT SIZE OF BUFFER AND POINTER + CP E ; IF EQUAL TO START, HEAD PTR IS PAST BUF END + JR NZ,ASCI_INTRCV3 ; IF NOT, BYPASS + LD H,D ; SET HL TO + LD L,E ; ... HEAD PTR ADR + INC HL ; BUMP PAST HEAD PTR + INC HL + INC HL + INC HL ; ... SO HL NOW HAS ADR OF ACTUAL BUFFER START +ASCI_INTRCV3: + EX DE,HL ; DE := HEAD PTR VAL, HL := ADR OF HEAD PTR + LD (HL),E ; SAVE UPDATED HEAD PTR + INC HL + LD (HL),D + ; CHECK FOR MORE PENDING... + CALL ASCI_ICHK ; CHECK FOR CHAR PENDING + JR NZ,ASCI_INTRCV1 ; IF SO, LOOP TO HANDLE +ASCI_INTRCV4: + OR $FF ; NZ SET TO INDICATE INT HANDLED + RET ; AND RETURN ; - LD B,$64 ; B := DEFAULT CNTLB VALUE - - ; DATA BITS - LD A,E ; LOAD CONFIG BYTE - AND $03 ; ISOLATE DATA BITS - CP $03 ; 8 DATA BITS? - JR Z,ASCI0_INITDEV2 ; IF SO, NO CHG, CONTINUE - RES 2,B ; RESET CNTLA BIT 2 FOR 7 DATA BITS - -ASCI0_INITDEV2: - ; STOP BITS - BIT 2,E ; TEST STOP BITS CONFIG BIT - JR Z,ASCI0_INITDEV3 ; IF CLEAR, NO CHG, CONTINUE - SET 0,B ; SET CNTLA BIT 0 FOR 2 STOP BITS - -ASCI0_INITDEV3: - ; PARITY ENABLE - BIT 3,E ; TEST PARITY ENABLE CONFIG BIT - JR Z,ASCI0_INITDEV4 ; NO PARITY, SKIP ALL PARITY CHGS - SET 1,B ; SET CNTLA BIT 1 FOR PARITY ENABLE - - ; PARITY EVEN/ODD - BIT 4,E ; TEST EVEN PARITY CONFIG BIT - JR NZ,ASCI0_INITDEV4 ; EVEN PARITY, NO CHG, CONTINUE - SET 4,C ; SET CNTLB BIT 4 FOR ODD PARITY - -ASCI0_INITDEV4: - ; IMPLEMENT CONFIGURATION - LD A,$66 ; LOAD DEFAULT ASEXT VALUE - OUT0 (Z180_ASEXT0),A ; SET IT - LD A,B ; FINAL CNTLA VALUE TO ACCUM - OUT0 (Z180_CNTLA0),A ; WRITE TO CNTLA REGISTER - LD A,C ; FINAL CNTLB VALUE TO ACCUM - OUT0 (Z180_CNTLB0),A ; WRITE TO CNTLA REGISTER -; - LD (ASCI0_CONFIG),DE ; RECORD UPDATED CONFIG +#ENDIF +; +; DRIVER FUNCTION TABLE +; +ASCI_FNTBL: + .DW ASCI_IN + .DW ASCI_OUT + .DW ASCI_IST + .DW ASCI_OST + .DW ASCI_INITDEV + .DW ASCI_QUERY + .DW ASCI_DEVICE +#IF (($ - ASCI_FNTBL) != (CIO_FNCNT * 2)) + .ECHO "*** INVALID ASCI FUNCTION TABLE ***\n" +#ENDIF +; +#IF (INTMODE == 0) +; +ASCI_IN: + CALL ASCI_IST ; CHECK FOR CHAR READY + JR Z,ASCI_IN ; IF NOT, LOOP + LD A,(IY+3) ; BASE REG + ADD A,8 ; Z180 RDR REG OFFSET + LD C,A ; PUT IN C FOR I/O + LD B,0 ; MSB FOR 16 BIT I/O + IN E,(C) ; GET CHAR XOR A ; SIGNAL SUCCESS RET ; DONE ; -ASCI0_QUERY: - LD DE,(ASCI0_CONFIG) - XOR A - RET +#ELSE +; +ASCI_IN: + CALL ASCI_IST ; SEE IF CHAR AVAILABLE + JR Z,ASCI_IN ; LOOP UNTIL SO + HB_DI ; AVOID COLLISION WITH INT HANDLER + LD L,(IY+6) ; SET HL TO + LD H,(IY+7) ; ... START OF BUFFER STRUCT + LD A,(HL) ; GET COUNT + DEC A ; DECREMENT COUNT + LD (HL),A ; SAVE UPDATED COUNT + CP ASCI_BUFSZ / 4 ; BUFFER LOW THRESHOLD + JR NZ,ASCI_IN1 ; IF NOT, BYPASS SETTING RTS + ; SET RTS + ; THE SECONDARY ASCI PORT ON Z180 ACTUALLY HAS NO RTS LINE + ; AND THE CNTLA BIT FOR THIS PORT CONTROLS THE FUNCTION OF THE + ; MULTIPLEXED CKA1/~TEND0 LINE. BELOW, WE TEST REG C TO SEE IF + ; IT IS AN ODD NUMBERED PORT. IF SO, WE MUST BE ON THE SECONDARY + ; SERIAL PORT, SO WE NEED TO BYPASS MANIPULATING THE RTS BIT. + LD C,(IY+3) ; CNTLA PORT ADR + BIT 0,C ; IS C ADDRESSING AN ODD NUMBERED PORT? + JR NZ,ASCI_IN1 ; IF SO, THIS IS SEC SERIAL, NO RTS! + LD B,0 ; MSB FOR 16 BIT I/O + IN A,(C) ; GET CUR CNTLA VAL + AND ~ASCI_RTS ; ASSERT ~RTS + OUT (C),A ; DO IT +ASCI_IN1: + INC HL ; HL := ADR OF TAIL PTR + INC HL ; " + INC HL ; " + PUSH HL ; SAVE ADR OF TAIL PTR + LD A,(HL) ; DEREFERENCE HL + INC HL + LD H,(HL) + LD L,A ; HL IS NOW ACTUAL TAIL PTR + LD C,(HL) ; C := CHAR TO BE RETURNED + INC HL ; BUMP TAIL PTR + POP DE ; RECOVER ADR OF TAIL PTR + LD A,L ; GET LOW BYTE OF TAIL PTR + SUB ASCI_BUFSZ+2 ; SUBTRACT SIZE OF BUFFER AND POINTER + CP E ; IF EQUAL TO START, TAIL PTR IS PAST BUF END + JR NZ,ASCI_IN2 ; IF NOT, BYPASS + LD H,D ; SET HL TO + LD L,E ; ... TAIL PTR ADR + INC HL ; BUMP PAST TAIL PTR + INC HL ; ... SO HL NOW HAS ADR OF ACTUAL BUFFER START +ASCI_IN2: + EX DE,HL ; DE := TAIL PTR VAL, HL := ADR OF TAIL PTR + LD (HL),E ; SAVE UPDATED TAIL PTR + INC HL ; " + LD (HL),D ; " + LD E,C ; MOVE CHAR TO RETURN TO E + HB_EI ; INTERRUPTS OK AGAIN + XOR A ; SIGNAL SUCCESS + RET ; AND DONE ; -ASCI0_DEVICE: - LD D,CIODEV_ASCI ; D := DEVICE TYPE - LD E,0 ; E := PHYSICAL UNIT +#ENDIF +; +; +; +ASCI_OUT: + CALL ASCI_OST ; CHECK IF OUTPUT REGISTER READY + JR Z,ASCI_OUT ; LOOP UNTIL SO + LD A,(IY+3) ; GET ASCI BASE REG + ADD A,6 ; Z180 TDR REG OFFSET + LD C,A ; PUT IN C FOR I/O + LD B,0 ; MSB FOR 16 BIT I/O + OUT (C),E ; WRITE CHAR XOR A ; SIGNAL SUCCESS - RET + RET ; DONE +; +; +; +#IF (INTMODE == 0) +; +ASCI_IST: + CALL ASCI_ICHK ; ASCI INPUT CHECK + JP Z,CIO_IDLE ; IF NOT READY, RETURN VIA IDLE PROCESSING + RET ; NORMAL RETURN +; +#ELSE +; +ASCI_IST: + LD L,(IY+6) ; GET ADDRESS + LD H,(IY+7) ; ... OF RECEIVE BUFFER + LD A,(HL) ; BUFFER UTILIZATION COUNT + OR A ; SET FLAGS + JP Z,CIO_IDLE ; NOT READY, RETURN VIA IDLE PROCESSING + RET ; DONE ; -; DRIVER ASCI1 FUNCTION TABLE -; -ASCI1_FNTBL: - .DW ASCI1_IN - .DW ASCI1_OUT - .DW ASCI1_IST - .DW ASCI1_OST - .DW ASCI1_INITDEV - .DW ASCI1_QUERY - .DW ASCI1_DEVICE -#IF (($ - ASCI1_FNTBL) != (CIO_FNCNT * 2)) - .ECHO "*** INVALID ASCI1 FUNCTION TABLE ***\n" #ENDIF -ASCI1: - LD A,B ; GET REQUESTED FUNCTION - AND $0F ; ISOLATE SUB-FUNCTION - JR Z,ASCI1_IN - DEC A - JR Z,ASCI1_OUT - DEC A - JR Z,ASCI1_IST - DEC A - JR Z,ASCI1_OST - DEC A - JP Z,ASCI1_INITDEV - DEC A - JP Z,ASCI1_QUERY - DEC A - JP Z,ASCI1_DEVICE - CALL PANIC -; -ASCI1_IN: - CALL ASCI1_IST - OR A - JR Z,ASCI1_IN - IN0 A,(Z180_RDR1) ; READ THE CHAR - LD E,A - RET ; -ASCI1_IST: - ; CHECK FOR ERROR FLAGS - IN0 A,(Z180_STAT1) - AND 70H ; PARITY, FRAMING, OR OVERRUN ERROR - JR Z,ASCI1_IST1 ; ALL IS WELL, CHECK FOR DATA ; - ; CLEAR ERROR(S) OR NOTHING FURTHER CAN BE RECEIVED!!! - IN0 A,(Z180_CNTLA1) - RES 3,A ; CLEAR EFR (ERROR FLAG RESET) - OUT0 (Z180_CNTLA1),A ; -ASCI1_IST1: ; CHECK FOR STAT0.RDRF (DATA READY) - IN0 A,(Z180_STAT1) ; READ LINE STATUS REGISTER - AND $80 ; TEST IF DATA IN RECEIVE BUFFER - JP Z,CIO_IDLE ; DO IDLE PROCESSING AND RETURN - XOR A - INC A ; SIGNAL CHAR READY, A = 1 - RET +ASCI_OST: + LD A,(IY+3) ; GET ASCI BASE REG + ADD A,4 ; Z180 STAT REG OFFSET + LD C,A ; PUT IN C FOR I/O + LD B,0 ; MSB FOR 16 BIT I/O + IN A,(C) ; READ STATUS + AND $02 ; CHECK BIT FOR OUTPUT READY + JP Z,CIO_IDLE ; IF NOT, DO IDLE PROCESSING AND RETURN + XOR A ; OTHERWISE SIGNAL + INC A ; ... BUFFER EMPTY, A = 1 + RET ; DONE ; -ASCI1_OUT: - CALL ASCI1_OST - OR A - JR Z,ASCI1_OUT - LD A,E - OUT0 (Z180_TDR1),A - RET +; AT INITIALIZATION THE SETUP PARAMETER WORD IS TRANSLATED TO THE FORMAT +; REQUIRED BY THE ASCI AND STORED IN A PORT/REGISTER INITIALIZATION TABLE, +; WHICH IS THEN LOADED INTO THE ASCI. ; -ASCI1_OST: - IN0 A,(Z180_STAT1) - AND $02 - JR Z,ASCI1_OST - JP Z,CIO_IDLE ; DO IDLE PROCESSING AND RETURN - XOR A - INC A ; SIGNAL BUFFER EMPTY, A = 1 - RET +; NOTE THAT THERE ARE TWO ENTRY POINTS. INITDEV WILL DISABLE/ENABLE INTS +; AND INITDEVX WILL NOT. THIS IS DONE SO THAT THE PREINIT ROUTINE ABOVE +; CAN AVOID ENABLING/DISABLING INTS. +; +ASCI_INITDEV: + HB_DI ; DISABLE INTS + CALL ASCI_INITDEVX ; DO THE WORK + HB_EI ; INTS BACK ON + RET ; DONE +; +ASCI_INITDEVX: +; +; THIS ENTRY POINT BYPASSES DISABLING/ENABLING INTS WHICH IS REQUIRED BY +; PREINIT ABOVE. PREINIT IS NOT ALLOWED TO ENABLE INTS! ; -ASCI1_INITDEV: ; TEST FOR -1 WHICH MEANS USE CURRENT CONFIG (JUST REINIT) LD A,D ; TEST DE FOR AND E ; ... VALUE OF -1 INC A ; ... SO Z SET IF -1 - JR NZ,ASCI1_INITDEV1 ; IF NEW CONFIG (NOT -1), IMPLEMENT IT - LD DE,(ASCI1_CONFIG) ; OTHERWISE, LOAD EXISTING CONFIG -; -ASCI1_INITDEV1: + JR NZ,ASCI_INITDEV1 ; IF DE == -1, REINIT CURRENT CONFIG +; + ; LOAD EXISTING CONFIG TO REINIT + LD E,(IY+4) ; LOW BYTE + LD D,(IY+5) ; HIGH BYTE +; +ASCI_INITDEV1: +; + LD A,E ; GET CONFIG LSB + AND $E0 ; CHECK FOR DTR, XON, PARITY=MARK/SPACE + JR NZ,ASCI_INITFAIL ; IF ANY BIT SET, FAIL, NOT SUPPORTED +; ; DETERMINE APPROPRIATE CNTLB VALUE (BASED ON BAUDRATE & CPU SPEED) LD A,D ; BYTE W/ ENCODED BAUD RATE AND $1F ; ISOLATE BITS LD L,A ; MOVE TO L LD H,0 ; CLEAR MSB PUSH DE ; SAVE CONFIG - CALL ASCI_CNTLB ; DERIVE CNTLB VALUE + CALL ASCI_CNTLB ; DERIVE CNTLB VALUE TO C POP DE ; RESTORE CONFIG - ;CALL TSTPT - RET NZ ; ABORT ON ERROR + JR NZ,ASCI_INITFAIL ; ABORT ON ERROR +; + ; BUILD CNTLA VALUE IN REGISTER B + LD B,$64 ; START WITH DEFAULT CNTLA VALUE ; - LD B,$64 ; B := DEFAULT CNTLB VALUE - ; DATA BITS LD A,E ; LOAD CONFIG BYTE AND $03 ; ISOLATE DATA BITS CP $03 ; 8 DATA BITS? - JR Z,ASCI1_INITDEV2 ; IF SO, NO CHG, CONTINUE + JR Z,ASCI_INITDEV2 ; IF SO, NO CHG, CONTINUE RES 2,B ; RESET CNTLA BIT 2 FOR 7 DATA BITS - -ASCI1_INITDEV2: +; +ASCI_INITDEV2: ; STOP BITS BIT 2,E ; TEST STOP BITS CONFIG BIT - JR Z,ASCI1_INITDEV3 ; IF CLEAR, NO CHG, CONTINUE + JR Z,ASCI_INITDEV3 ; IF CLEAR, NO CHG, CONTINUE SET 0,B ; SET CNTLA BIT 0 FOR 2 STOP BITS - -ASCI1_INITDEV3: +; +ASCI_INITDEV3: ; PARITY ENABLE BIT 3,E ; TEST PARITY ENABLE CONFIG BIT - JR Z,ASCI1_INITDEV4 ; NO PARITY, SKIP ALL PARITY CHGS + JR Z,ASCI_INITDEV4 ; NO PARITY, SKIP ALL PARITY CHGS SET 1,B ; SET CNTLA BIT 1 FOR PARITY ENABLE ; PARITY EVEN/ODD BIT 4,E ; TEST EVEN PARITY CONFIG BIT - JR NZ,ASCI1_INITDEV4 ; EVEN PARITY, NO CHG, CONTINUE + JR NZ,ASCI_INITDEV4 ; EVEN PARITY, NO CHG, CONTINUE SET 4,C ; SET CNTLB BIT 4 FOR ODD PARITY - -ASCI1_INITDEV4: +; +ASCI_INITDEV4: + ; SAVE CONFIG PERMANENTLY NOW + LD (IY+4),E ; SAVE LOW WORD + LD (IY+5),D ; SAVE HI WORD + JR ASCI_INITGO +; +ASCI_INITSAFE: + LD B,$64 ; CNTLA FAILSAFE VALUE + LD C,$20 ; CNTLB FAILSAFE VALUE +; +ASCI_INITGO: ; IMPLEMENT CONFIGURATION - LD A,$66 ; LOAD DEFAULT ASEXT VALUE - OUT0 (Z180_ASEXT1),A ; SET IT - LD A,B ; FINAL CNTLA VALUE TO ACCUM - OUT0 (Z180_CNTLA1),A ; WRITE TO CNTLA REGISTER - LD A,C ; FINAL CNTLB VALUE TO ACCUM - OUT0 (Z180_CNTLB1),A ; WRITE TO CNTLA REGISTER -; - LD (ASCI1_CONFIG),DE ; RECORD UPDATED CONFIG + LD H,B ; H := CNTLA VAL + LD L,C ; L := CNTLB VAL + LD B,0 ; MSB OF PORT MUST BE ZERO! + LD C,(IY+3) ; GET ASCI BASE REG (CNTLA) + OUT (C),H ; WRITE CNTLA VALUE + INC C ; BUMP TO + INC C ; ... CNTLB REG, B IS STILL 0 + OUT (C),L ; WRITE CNTLB VALUE + INC C ; BUMP TO + INC C ; ... STAT REG, B IS STILL 0 +#IF (INTMODE > 0) + LD A,$08 ; SET RIE BIT ON +#ELSE + XOR A ; CLEAR RIE/TIE +#ENDIF + OUT (C),A ; WRITE STAT REG + LD A,$0E ; BUMP TO + ADD A,C ; ... ASEXT REG + LD C,A ; PUT IN C FOR I/O, B IS STILL 0 + LD A,$66 ; STATIC VALUE FOR ASEXT + OUT (C),A ; WRITE ASEXT REG +; +#IF (INTMODE > 0) +; + ; RESET THE RECEIVE BUFFER + LD E,(IY+6) + LD D,(IY+7) ; DE := _CNT + XOR A ; A := 0 + LD (DE),A ; _CNT = 0 + INC DE ; DE := ADR OF _HD + PUSH DE ; SAVE IT + INC DE + INC DE + INC DE + INC DE ; DE := ADR OF _BUF + POP HL ; HL := ADR OF _HD + LD (HL),E + INC HL + LD (HL),D ; _HD := _BUF + INC HL + LD (HL),E + INC HL + LD (HL),D ; _TL := _BUF +; +#ENDIF +; XOR A ; SIGNAL SUCCESS RET ; DONE ; -ASCI1_QUERY: - LD DE,(ASCI1_CONFIG) - XOR A - RET +ASCI_INITFAIL: + OR $FF ; SIGNAL FAILURE + RET ; RETURN +; +; +; +ASCI_QUERY: + LD E,(IY+4) ; FIRST CONFIG BYTE TO E + LD D,(IY+5) ; SECOND CONFIG BYTE TO D + XOR A ; SIGNAL SUCCESS + RET ; DONE ; -ASCI1_DEVICE: +; +; +ASCI_DEVICE: LD D,CIODEV_ASCI ; D := DEVICE TYPE - LD E,1 ; E := PHYSICAL UNIT + LD E,(IY) ; E := PHYSICAL UNIT + LD C,$00 ; C := DEVICE TYPE, 0x00 IS RS-232 XOR A ; SIGNAL SUCCESS RET ; -; LOCAL DATA -; -ASCI0_CONFIG .DW DEFSERCFG ; SAVED CONFIG FOR ASCI0 -ASCI1_CONFIG .DW DEFSERCFG ; SAVED CONFIG FOR ASCI1 +; ASCI DETECTION ROUTINE +; ALWAYS PRESENT, JUST SAY SO. +; +ASCI_DETECT: + LD A,(IY+3) ; BASE PORT ADR + ADD A,$1A ; BUMP TO ASCI CONSTANT LOW + LD C,A ; PUT IN C + LD B,0 ; MSB FOR 16 BIT I/O + XOR A ; ZERO TO ACCUM + OUT (C),A ; WRITE TO REG + IN A,(C) ; READ IT BACK + INC A ; FF -> 0 + LD A,ASCI_ASCI ; ASSUME ORIG ASCI, NO BRG + RET Z ; IF SO, RETURN + LD A,ASCI_ASCIB ; MUST BE NEWER ASCI W/ BRG + RET ; DONE ; -; DERIVE A CNTLB VALUE BASED ON AN ENCODED BAUD RATE AND CURRENT CPU SPEED +; DERIVE CNTLB VALUE BASED ON AN ENCODED BAUD RATE AND CURRENT CPU SPEED ; ENTRY: HL = ENCODED BAUD RATE ; EXIT: C = CNTLB VALUE, A=0/Z IFF SUCCESS ; @@ -420,24 +603,21 @@ ASCI1_CONFIG .DW DEFSERCFG ; SAVED CONFIG FOR ASCI1 ; DUE TO ENCODING BAUD IS ALWAYS DIVISIBLE BY 75 ; Z180 DIVISOR IS ALWAYS A FACTOR OF 160 ; -; X = CPU_HZ / 160 / 75 ==> SIMPLIFIED ==> X = CPU_KHZ / 12 -; X = X / (BAUD / 75) -; IF X % 3 == 0, THEN (PS=1, X := X / 3) ELSE PS=0 -; IF X % 4 == 0, THEN (DR=1, X := X / 4) ELSE DR=0 +; X := CPU_HZ / 160 / 75 ==> SIMPLIFIED ==> X := CPU_KHZ / 12 +; X := X / (BAUD / 75) +; IF X % 3 == 0, THEN (PS := 1, X := X / 3) ELSE PS=0 +; IF X % 4 == 0, THEN (DR := 1, X := X / 4) ELSE DR=0 ; SS := LOG2(X) ; ASCI_CNTLB: LD DE,1 ; USE DECODE CONSTANT OF 1 TO GET BAUD RATE ALREADY DIVIDED BY 75 CALL DECODE ; DECODE THE BAUDATE INTO DE:HL, DE IS DISCARDED - ;CALL TSTPT RET NZ ; ABORT ON ERROR PUSH HL ; HL HAS (BAUD / 75), SAVE IT LD HL,(CB_CPUKHZ) ; GET CPU CLK IN KHZ - ;LD HL,CPUKHZ ; CPU CLK IN KHZ - ;LD HL,9216 ; *DEBUG* - +; ; DUE TO THE LIMITED DIVISORS POSSIBLE WITH CNTLB, YOU PRETTY MUCH - ; NEED TO USE A CPU SPEED THAT IS A MULTIPLE OF 128KHZ. BELOW, WE + ; NEED TO USE A CPU SPEED THAT IS A MULTIPLE OF 128KHZ. BELOW, WE ; ATTEMPT TO ROUND THE CPU SPEED DETECTED TO A MULTIPLE OF 128KHZ ; WITH ROUNDING. THIS JUST MAXIMIZES POSSIBILITY OF SUCCESS COMPUTING ; THE DIVISOR. @@ -446,25 +626,22 @@ ASCI_CNTLB: LD A,L ; MOVE TO ACCUM AND $80 ; STRIP LOW ORDER 7 BITS LD L,A ; ... AND PUT IT BACK - +; LD DE,12 ; PREPARE TO DIVIDE BY 12 CALL DIV16 ; BC := (CPU_KHZ / 12), REM IN HL, ZF - ;CALL TSTPT POP DE ; RESTORE (BAUD / 75) RET NZ ; ABORT IF REMAINDER PUSH BC ; MOVE WORKING VALUE POP HL ; ... BACK TO HL CALL DIV16 ; BC := X / (BAUD / 75) - ;CALL TSTPT RET NZ ; ABORT IF REMAINDER -; +; ; DETERMINE PS BIT BY ATTEMPTING DIVIDE BY 3 PUSH BC ; SAVE WORKING VALUE ON STACK PUSH BC ; MOVE WORKING VALUE POP HL ; ... TO HL LD DE,3 ; SETUP TO DIVIDE BY 3 CALL DIV16 ; BC := X / 3, REM IN HL, ZF - ;CALL TSTPT POP HL ; HL := PRIOR WORKING VALUE LD E,0 ; INIT E := 0 AS WORKING CNTLB VALUE JR NZ,ASCI_CNTLB1 ; DID NOT WORK, LEAVE PS==0, SKIP AHEAD @@ -473,7 +650,6 @@ ASCI_CNTLB: POP HL ; ... VALUE TO HL ; ASCI_CNTLB1: - ;CALL TSTPT ; DETERMINE DR BIT BY ATTEMPTING DIVIDE BY 4 LD A,L ; LOAD LSB OF WORKING VALUE AND $03 ; ISOLATE LOW ORDER BITS @@ -485,7 +661,6 @@ ASCI_CNTLB1: RR L ; ... ; ASCI_CNTLB2: - ;CALL TSTPT ; DETERMINE SS BITS BY RIGHT SHIFTING AND INCREMENTING LD B,7 ; LOOP COUNTER, MAX VALUE OF SS IS 7 LD C,E ; MOVE WORKING CNTLB VALUE TO C @@ -504,7 +679,139 @@ ASCI_CNTLB3: RET NZ ; ABORT IF NOT ZERO ; ASCI_CNTLB4: - ;CALL TSTPT + XOR A ; SIGNAL SUCCESS + RET ; DONE +; +; SPECIAL INPUT STATUS CHECK ROUTINE FOR ASCI. IF THE ASCI PORT DETECTS A LINE +; ERROR (PARITY, OVERRUN, ETC.) IT WILL STALL UNTIL THE ERROR IS EXPLICITY +; ACKNOWLEDGED. THIS ROUTINE HANDLES ALL OF THAT AND RETURNS WITH A=1 IF CHAR +; READY, ELSE A=0. ZF SET OR CLEARED. +; +ASCI_ICHK: + LD A,(IY+3) ; GET ASCI BASE REG + ADD A,4 ; Z180 STAT REG OFFSET + LD C,A ; PUT IN C FOR I/O + LD B,0 ; MSB FOR 16 BIT I/O + IN A,(C) ; READ STAT REG + PUSH AF ; SAVE STATUS + AND $70 ; PARITY, FRAMING, OR OVERRUN ERROR? + JR Z,ASCI_ICHK1 ; JUMP AHEAD IF NO ERRORS +; + ; CLEAR ERROR(S) OR NOTHING FURTHER CAN BE RECEIVED!!! + LD C,(IY+3) ; GET ASCI BASE REG (CNTLA) + LD B,0 ; MSB FOR 16 BIT I/O + IN A,(C) ; READ CNTLA + RES 3,A ; CLEAR EFR (ERROR FLAG RESET) + OUT (C),A ; WRITE UPDATED CNTLA +; +ASCI_ICHK1: + POP AF ; RESTORE STATUS VALUE + AND $80 ; DATA READY? + JP Z,CIO_IDLE ; IF NOT, DO IDLE PROCESSING AND RETURN + XOR A ; SIGNAL CHAR WAITING + INC A ; ... BY SETTING A TO 1 + RET ; DONE +; +; +; +ASCI_PRTCFG: + ; ANNOUNCE PORT + CALL NEWLINE ; FORMATTING + PRTS("ASCI$") ; FORMATTING + LD A,(IY) ; DEVICE NUM + CALL PRTDECB ; PRINT DEVICE NUM + PRTS(": IO=0x$") ; FORMATTING + LD A,(IY+3) ; GET BASE PORT + CALL PRTHEXBYTE ; PRINT BASE PORT + + ; PRINT THE ASCI TYPE + CALL PC_SPACE ; FORMATTING + LD A,(IY+1) ; GET ASCI TYPE BYTE + RLCA ; MAKE IT A WORD OFFSET + LD HL,ASCI_TYPE_MAP ; POINT HL TO TYPE MAP TABLE + CALL ADDHLA ; HL := ENTRY + LD E,(HL) ; DEREFERENCE + INC HL ; ... + LD D,(HL) ; ... TO GET STRING POINTER + CALL WRITESTR ; PRINT IT +; + ; ALL DONE IF NO ASCI WAS DETECTED + LD A,(IY+1) ; GET ASCI TYPE BYTE + OR A ; SET FLAGS + RET Z ; IF ZERO, NOT PRESENT +; + PRTS(" MODE=$") ; FORMATTING + LD E,(IY+4) ; LOAD CONFIG + LD D,(IY+5) ; ... WORD TO DE + CALL PS_PRTSC0 ; PRINT CONFIG +; XOR A RET ; +; +; +ASCI_TYPE_MAP: + .DW ASCI_STR_NONE + .DW ASCI_STR_ASCI + .DW ASCI_STR_ASCIB + +ASCI_STR_NONE .DB "$" +ASCI_STR_ASCI .DB "ASCI$" +ASCI_STR_ASCIB .DB "ASCI W/BRG$" +; +; WORKING VARIABLES +; +ASCI_DEV .DB 0 ; DEVICE NUM USED DURING INIT +; +#IF (INTMODE == 0) +; +ASCI0_RCVBUF .EQU 0 +ASCI1_RCVBUF .EQU 0 +; +#ELSE +; +; RECEIVE BUFFERS +; +ASCI0_RCVBUF: +ASCI0_BUFCNT .DB 0 ; CHARACTERS IN RING BUFFER +ASCI0_HD .DW ASCI0_BUF ; BUFFER HEAD POINTER +ASCI0_TL .DW ASCI0_BUF ; BUFFER TAIL POINTER +ASCI0_BUF .FILL 32,0 ; RECEIVE RING BUFFER +ASCI0_BUFEND .EQU $ ; END OF BUFFER +ASCI0_BUFSZ .EQU $ - ASCI0_BUF ; SIZE OF RING BUFFER +; +ASCI1_RCVBUF: +ASCI1_BUFCNT .DB 0 ; CHARACTERS IN RING BUFFER +ASCI1_HD .DW ASCI1_BUF ; BUFFER HEAD POINTER +ASCI1_TL .DW ASCI1_BUF ; BUFFER TAIL POINTER +ASCI1_BUF .FILL 32,0 ; RECEIVE RING BUFFER +ASCI1_BUFEND .EQU $ ; END OF BUFFER +ASCI1_BUFSZ .EQU $ - ASCI1_BUF ; SIZE OF RING BUFFER +; +#ENDIF +; +; ASCI PORT TABLE +; +ASCI_CFG: +; +ASCI0_CFG: + ; ASCI MODULE A CONFIG + .DB 0 ; DEVICE NUMBER (SET DURING INIT) + .DB 0 ; ASCI TYPE (SET DURING INIT) + .DB 0 ; MODULE ID + .DB ASCI0_BASE ; BASE PORT + .DW ASCI0CFG ; LINE CONFIGURATION + .DW ASCI0_RCVBUF ; POINTER TO RCV BUFFER STRUCT +; +ASCI_CFGSIZ .EQU $ - ASCI_CFG ; SIZE OF ONE CFG TABLE ENTRY +; +ASCI1_CFG: + ; ASCI MODULE B CONFIG + .DB 0 ; DEVICE NUMBER (SET DURING INIT) + .DB 0 ; ASCI TYPE (SET DURING INIT) + .DB 1 ; MODULE ID + .DB ASCI1_BASE ; BASE PORT + .DW ASCI1CFG ; LINE CONFIGURATION + .DW ASCI1_RCVBUF ; POINTER TO RCV BUFFER STRUCT +; +ASCI_CFGCNT .EQU ($ - ASCI_CFG) / ASCI_CFGSIZ diff --git a/Source/HBIOS/ay.asm b/Source/HBIOS/ay.asm index 7c95df6e..85d9b9e2 100644 --- a/Source/HBIOS/ay.asm +++ b/Source/HBIOS/ay.asm @@ -4,15 +4,28 @@ ; WILL ALSO WORK WITH YM2149 ;====================================================================== ; -#IF (PLATFORM == PLT_N8) -AY_RSEL .EQU N8_PSG+0 -AY_RDAT .EQU N8_PSG+1 -AY_ACR .EQU N8_DEFACR -#ELSE +#IF (AYMODE == AYMODE_SCG) AY_RSEL .EQU $9A AY_RDAT .EQU $9B AY_ACR .EQU $9C #ENDIF +; +#IF (AYMODE == AYMODE_N8) +AY_RSEL .EQU $9C +AY_RDAT .EQU $9D +AY_ACR .EQU N8_DEFACR +#ENDIF +; +#IF (AYMODE == AYMODE_RCZ80) +AY_RSEL .EQU $D8 +AY_RDAT .EQU $D0 +#ENDIF +; +#IF (AYMODE == AYMODE_RCZ180) +AY_RSEL .EQU $68 +AY_RDAT .EQU $60 +#ENDIF +; AY_R0CHAP .EQU $00 AY_R1CHAP .EQU $01 AY_R2CHBP .EQU $02 @@ -81,7 +94,7 @@ AY_BEEP: ; AY_WRTPSG: HB_DI -#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4) | (PLATFORM == PLT_RC180)) +#IF (CPUFAM == CPU_Z180) IN0 A,(Z180_DCNTL) ; GET WAIT STATES PUSH AF ; SAVE VALUE OR %00110000 ; FORCE SLOW OPERATION (I/O W/S=3) @@ -91,7 +104,7 @@ AY_WRTPSG: OUT (AY_RSEL),A LD A,E OUT (AY_RDAT),A -#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4) | (PLATFORM == PLT_RC180)) +#IF (CPUFAM == CPU_Z180) POP AF ; GET SAVED DCNTL VALUE OUT0 (Z180_DCNTL),A ; AND RESTORE IT #ENDIF @@ -101,7 +114,9 @@ AY_WRTPSG: ; CHECK THERE IS A DEVICE PRESENT ; AY_PROBE: +#IF ((AYMODE == AYMODE_SCG) | (AYMODE == AYMODE_N8)) LD A,$FF OUT (AY_ACR),A ; INIT AUX CONTROL REG +#ENDIF XOR A RET diff --git a/Source/HBIOS/blank1024KB.dat b/Source/HBIOS/blank1024KB.dat deleted file mode 100644 index ac034386..00000000 --- a/Source/HBIOS/blank1024KB.dat +++ /dev/null @@ -1 +0,0 @@ -åååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååå \ No newline at end of file diff --git a/Source/HBIOS/blank512KB.dat b/Source/HBIOS/blank512KB.dat deleted file mode 100644 index 9f83fa94..00000000 --- a/Source/HBIOS/blank512KB.dat +++ /dev/null @@ -1 +0,0 @@ -åååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååååå \ No newline at end of file diff --git a/Source/HBIOS/bqrtc.asm b/Source/HBIOS/bqrtc.asm new file mode 100644 index 00000000..34854e22 --- /dev/null +++ b/Source/HBIOS/bqrtc.asm @@ -0,0 +1,377 @@ + +;================================================================================================== +; Benchmark BQ4845P RTC Driver +;================================================================================================== + + +; Register Addresses (HEX / BCD): + +; +---+-----+--------------+-------------------+------------------+----------------+ +; |ADR| D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 | RANGE | REGISTER | +; +---+-----+--------------+-------------------+------------------+----------------+ +; | 0 | 0 | 10-Second | 1-Second | 00-59 | Seconds | +; +---+-----+----+---------+-------------------+------------------+----------------+ +; | | ALM1|ALM2| | | | | +; | 1 | | 10-Second | 1-Second | 00-59 | Seconds Alarm | +; +---+-----+--------------+-------------------+------------------+----------------+ +; | 2 | 0 | 10-Minute | 1-Minute | 00-59 | Minutes | +; +---+-----+----+---------+-------------------+------------------+----------------+ +; | | ALM1|ARM0| | | | | +; | 3 | | 10-Minute | 1-Minute | 00-59 | Minutes Alarm | +; +---+-----+----+---------+-------------------+------------------+----------------+ +; | 4 |PM/AM| 0 | 10-Hour | 1-Hour |01-12 AM/81-92 PM | Hours | +; +---+-----+----+----+----+-------------------+------------------+----------------+ +; | | ALM1| | | | | | +; | 5 |PM/AM|ALM0| 10-Hour | 1-Hour |01-12 AM/81-92 PM | Hours Alarm | +; +---+-----+----+----+----+-------------------+------------------+----------------+ +; | 6 | 0 | 0 | 10-Day | 1-Day | 01-31 | Day | +; +---+-----+----+----+----+-------------------+------------------+----------------+ +; | 7 | ALM1|ALM0| 10-day | 1-Day | 01-31 | Day Alarm | +; +---+-----+----+----+----+----+--------------+------------------+----------------+ +; | 8 | 0 | 0 | 0 | 0 | 0 | Day Of Week | 01-07 | Day Of Week | +; +---+-----+----+----+----+----+--------------+------------------+----------------+ +; | 9 | 0 | 0 | 0 |10Mo| 1-Month | 01-12 | Month | +; +---+-----+----+----+----+-------------------+------------------+----------------+ +; | A | 10-Year | 1-Year | 00-99 | Year | +; +---+-----+----+----+----+----+----+----+----+------------------+----------------+ +; | B | * | WD2| WD1| WD0| RS3| RS2| RS1| RS0| | Rates | +; +---+-----+----+----+----+----+----+----+----+------------------+----------------+ +; | C | * | * | * | * | AIE| PIE|PWRE| ABE| | Interrupt | +; +---+-----+----+----+----+----+----+----+----+------------------+----------------+ +; | D | * | * | * | * | AF | PF |PWRF| BVF| | Flags | +; +---+-----+----+----+----+----+----+----+----+------------------+----------------+ +; | E | * | * | * | * | UTI|STOP|2412| DSE| | Control | +; +---+-----+----+----+----+----+----+----+----+------------------+----------------+ +; | F | * | * | * | * | * | * | * | * | | Unused | +; +---+-----+----+----+----+----+----+----+----+------------------+----------------+ + +; * = Unused bits; unwritable and read as 0. +; 0 = should be set to 0 for valid time/calendar range. +; Clock calendar data is BCD. Automatic leap year adjustment. +; PM/AM = 1 for PM; PM/AM = 0 for AM. +; DSE = 1 enable daylight savings adjustment. +; 24/12 = 1 enable 24-hour data representation; 24/12 = 0 enables 12-hour data representation. +; Day-Of-Week coded as Sunday = 1 through Saturday = 7. +; BVF = 1 for valid battery. +; STOP = 1 turns the RTC on; STOP = 0 stops the RTC in back-up mode. + +; Constants + +BQRTC_SEC .EQU BQRTC_BASE + $00 +BQRTC_SEC_ALM .EQU BQRTC_BASE + $01 +BQRTC_MIN .EQU BQRTC_BASE + $02 +BQRTC_MIN_ALM .EQU BQRTC_BASE + $03 +BQRTC_HOUR .EQU BQRTC_BASE + $04 +BQRTC_HOUR_ALM .EQU BQRTC_BASE + $05 +BQRTC_DAY .EQU BQRTC_BASE + $06 +BQRTC_DAY_ALM .EQU BQRTC_BASE + $07 +BQRTC_WEEK_DAY .EQU BQRTC_BASE + $08 +BQRTC_MONTH .EQU BQRTC_BASE + $09 +BQRTC_YEAR .EQU BQRTC_BASE + $0A +BQRTC_RATE .EQU BQRTC_BASE + $0B +BQRTC_INTERRUPT .EQU BQRTC_BASE + $0C +BQRTC_FLAGS .EQU BQRTC_BASE + $0D +BQRTC_CONTROL .EQU BQRTC_BASE + $0E +BQRTC_UNUSED .EQU BQRTC_BASE + $0F + +BQRTC_HIGH .EQU %11110000 +BQRTC_LOW .EQU %00001111 +BQRTC_WD .EQU %01110000 +BQRTC_RS .EQU %00001111 + +BQRTC_BVF .EQU %00000001 +BQRTC_PWRF .EQU %00000010 +BQRTC_PF .EQU %00000100 +BQRTC_AF .EQU %00001000 + +BQRTC_DSE .EQU %00000001 +BQRTC_2412 .EQU %00000010 +BQRTC_STOP .EQU %00000100 +BQRTC_UTI .EQU %00001000 + +BQRTC_BUFSIZE .EQU 6 ; 6 BYTE BUFFER (YYMMDDHHMMSS) + +; RTC Device Initialization Entry + +BQRTC_INIT: + LD A,(RTC_DISPACT) ; RTC DISPATCHER ALREADY SET? + OR A ; SET FLAGS + RET NZ ; IF ALREADY ACTIVE, ABORT +; + CALL NEWLINE ; Formatting + PRTS("BQRTC: IO=0x$") + LD A, BQRTC_BASE + CALL PRTHEXBYTE + + LD A, BQRTC_DSE | BQRTC_2412 | BQRTC_STOP | BQRTC_UTI + OUT0 (BQRTC_CONTROL), A ; Enable Daylight Savings and 24 Hour + + XOR A ; Zero A + OUT0 (BQRTC_RATE), A ; Disable Periodic Interrupt Rate + OUT0 (BQRTC_INTERRUPT), A ; Disable Interrupts + + CALL BQRTC_LOAD + ; DISPLAY CURRENT TIME + PRTS(" $") + LD A, (BQRTC_BUF_MON) + CALL PRTHEXBYTE + PRTS("/$") + LD A, (BQRTC_BUF_DAY) + CALL PRTHEXBYTE + PRTS("/$") + LD A, (BQRTC_BUF_YEAR) + CALL PRTHEXBYTE + PRTS(" $") + LD A, (BQRTC_BUF_HOUR) + CALL PRTHEXBYTE + PRTS(":$") + LD A, (BQRTC_BUF_MIN) + CALL PRTHEXBYTE + PRTS(":$") + LD A, (BQRTC_BUF_SEC) + CALL PRTHEXBYTE + + LD BC,BQRTC_DISPATCH + CALL RTC_SETDISP + + XOR A ; Signal success + RET + +; RTC Device Function Dispatch Entry +; A: Result (OUT), 0=OK, Z=OK, NZ=Error +; B: Function (IN) + +BQRTC_DISPATCH: + LD A, B ; Get requested function + AND $0F ; Isolate Sub-Function + JP Z, BQRTC_GETTIM ; Get Time + DEC A + JP Z, BQRTC_SETTIM ; Set Time + DEC A + JP Z, BQRTC_GETBYT ; Get NVRAM Byte Value + DEC A + JP Z, BQRTC_SETBYT ; Set NVRAM Byte Value + DEC A + JP Z, BQRTC_GETBLK ; Get NVRAM Data Block Value + DEC A + JP Z, BQRTC_SETBLK ; Set NVRAM Data Block Value + DEC A + JP Z, BQRTC_GETALM ; Get Alarm + DEC A + JP Z, BQRTC_SETALM ; Set Alarm +; +; NVRAM FUNCTIONS ARE NOT AVAILABLE +; +BQRTC_GETBYT: +BQRTC_SETBYT: +BQRTC_GETBLK: +BQRTC_SETBLK: + CALL PANIC + +; RTC Get Time +; A: Result (OUT), 0=OK, Z=OK, NZ=Error +; HL: Date/Time Buffer (OUT) +; Buffer format is BCD: YYMMDDHHMMSS +; 24 hour time format is assumed +; +BQRTC_GETTIM: + EX DE, HL + CALL BQRTC_LOAD + ; Now copy to read destination (Interbank Save) + LD A, BID_BIOS ; Copy from BIOS bank + LD (HB_SRCBNK), A ; Set it + LD A, (HB_INVBNK) ; Copy to current user bank + LD (HB_DSTBNK), A ; Set it + LD BC, BQRTC_BUFSIZE ; Length is 6 bytes +#IF (INTMODE == 1) + DI +#ENDIF + CALL HB_BNKCPY ; Copy the clock data +#IF (INTMODE == 1) + EI +#ENDIF +; + ; CLEAN UP AND RETURN + XOR A ; SIGNAL SUCCESS + RET ; AND RETURN +; +; RTC Set Time +; A: Result (OUT), 0=OK, Z=OK, NZ=Error +; HL: Date/Time Buffer (IN) +; Buffer Format is BCD: YYMMDDHHMMSS +; 24 hour time format is assumed +; +BQRTC_SETTIM: +; + ; Copy incoming time data to our time buffer + LD A, (HB_INVBNK) ; Copy from current user bank + LD (HB_SRCBNK), A ; Set it + LD A, BID_BIOS ; Copy to BIOS bank + LD (HB_DSTBNK), A ; Set it + LD DE, BQRTC_BUF ; Destination Address + LD BC, BQRTC_BUFSIZE ; Length is 6 bytes +#IF (INTMODE == 1) + DI +#ENDIF + CALL HB_BNKCPY ; Copy the clock data +#IF (INTMODE == 1) + EI +#ENDIF + ; Write to clock + LD HL, BQRTC_BUF + CALL BQRTC_SUSPEND + LD A, (HL) + OUT0 (BQRTC_YEAR), A ; Write Year + INC HL + LD A, (HL) + OUT0 (BQRTC_MONTH), A ; Write Month + INC HL + LD A, (HL) + OUT0 (BQRTC_DAY), A ; Write Day + INC HL + LD A, (HL) + OUT0 (BQRTC_HOUR), A ; Write Hour + INC HL + LD A, (HL) + OUT0 (BQRTC_MIN), A ; Write Minute + INC HL + LD A, (HL) + OUT0 (BQRTC_SEC), A ; Write Second + CALL BQRTC_RESUME + ; clean up and return + XOR A ; Signal success + RET ; And return + +; RTC Get Alarm +; A: Result (OUT), 0=OK, Z=OK, NZ=Error +; HL: Date/Time Buffer (OUT) +; Buffer format is BCD: YYMMDDHHMMSS +; 24 hour time format is assumed +; +BQRTC_GETALM: + EX DE, HL + LD HL, BQRTC_BUF + PUSH HL ; Save address of source buffer + CALL BQRTC_SUSPEND + XOR A + LD (HL), A ; Read Year + INC HL + LD (HL), A ; Read Month + INC HL + IN0 A, (BQRTC_DAY_ALM) ; Read Day + LD (HL), A + INC HL + IN0 A, (BQRTC_HOUR_ALM) ; Read Hour + LD (HL), A + INC HL + IN0 A, (BQRTC_MIN_ALM) ; Read Minute + LD (HL), A + INC HL + IN0 A, (BQRTC_SEC_ALM) ; Read Second + LD (HL), A + CALL BQRTC_RESUME + POP HL ; Restore address of source buffer + ; Now copy to read destination (Interbank Save) + LD A, BID_BIOS ; Copy from BIOS bank + LD (HB_SRCBNK), A ; Set it + LD A, (HB_INVBNK) ; Copy to current user bank + LD (HB_DSTBNK), A ; Set it + LD BC, BQRTC_BUFSIZE ; Length is 6 bytes +#IF (INTMODE == 1) + DI +#ENDIF + CALL HB_BNKCPY ; Copy the clock data +#IF (INTMODE == 1) + EI +#ENDIF +; + ; CLEAN UP AND RETURN + XOR A ; SIGNAL SUCCESS + RET ; AND RETURN +; +; RTC Set Alarm +; A: Result (OUT), 0=OK, Z=OK, NZ=Error +; HL: Date/Time Buffer (IN) +; Buffer Format is BCD: YYMMDDHHMMSS +; 24 hour time format is assumed +; +BQRTC_SETALM: + ; Copy incoming time data to our time buffer + LD A, (HB_INVBNK) ; Copy from current user bank + LD (HB_SRCBNK), A ; Set it + LD A, BID_BIOS ; Copy to BIOS bank + LD (HB_DSTBNK), A ; Set it + LD DE, BQRTC_BUF ; Destination Address + LD BC, BQRTC_BUFSIZE ; Length is 6 bytes +#IF (INTMODE == 1) + DI +#ENDIF + CALL HB_BNKCPY ; Copy the clock data +#IF (INTMODE == 1) + EI +#ENDIF + ; Write to clock + LD HL, BQRTC_BUF_DAY + CALL BQRTC_SUSPEND + LD A, (HL) + OUT0 (BQRTC_DAY_ALM), A ; Write Day + INC HL + LD A, (HL) + OUT0 (BQRTC_HOUR_ALM), A ; Write Hour + INC HL + LD A, (HL) + OUT0 (BQRTC_MIN_ALM), A ; Write Minute + INC HL + LD A, (HL) + OUT0 (BQRTC_SEC_ALM), A ; Write Second + CALL BQRTC_RESUME + ; clean up and return + XOR A ; Signal success + RET ; And return + +BQRTC_SUSPEND: + IN0 A, (BQRTC_CONTROL) ; Suspend Clock + OR BQRTC_UTI + OUT0 (BQRTC_CONTROL), A + RET + +BQRTC_RESUME: + IN0 A, (BQRTC_CONTROL) ; Resume Clock + AND ~BQRTC_UTI + OUT0 (BQRTC_CONTROL), A + RET + +BQRTC_LOAD: + LD HL, BQRTC_BUF + PUSH HL ; Save address of source buffer + CALL BQRTC_SUSPEND + IN0 A, (BQRTC_YEAR) ; Read Year + LD (HL), A + INC HL + IN0 A, (BQRTC_MONTH) ; Read Month + LD (HL), A + INC HL + IN0 A, (BQRTC_DAY) ; Read Day + LD (HL), A + INC HL + IN0 A, (BQRTC_HOUR) ; Read Hour + LD (HL), A + INC HL + IN0 A, (BQRTC_MIN) ; Read Minute + LD (HL), A + INC HL + IN0 A, (BQRTC_SEC) ; Read Second + LD (HL), A + CALL BQRTC_RESUME + POP HL ; Restore address of source buffer + RET + +; Working Variables + +BQRTC_BUF: +BQRTC_BUF_YEAR: .DB 0 ; Year +BQRTC_BUF_MON: .DB 0 ; Month +BQRTC_BUF_DAY: .DB 0 ; Day +BQRTC_BUF_HOUR: .DB 0 ; Hour +BQRTC_BUF_MIN: .DB 0 ; Minute +BQRTC_BUF_SEC: .DB 0 ; Second diff --git a/Source/HBIOS/cfg_dyno.asm b/Source/HBIOS/cfg_dyno.asm new file mode 100644 index 00000000..13714081 --- /dev/null +++ b/Source/HBIOS/cfg_dyno.asm @@ -0,0 +1,156 @@ +; +;================================================================================================== +; ROMWBW 2.X CONFIGURATION DEFAULTS FOR DYNO +;================================================================================================== +; +; THIS FILE CONTAINS THE FULL EQU OF DEFAULT CONFIGURATION SETTINGS FOR THE PLATFORM +; INDICATED ABOVE. THIS FILE SHOULD *NOT* NORMALLY BE CHANGED. INSTEAD, YOU SHOULD +; OVERRIDE ANY SETTINGS YOU WANT USING A CONFIGURATION FILE IN THE CONFIG DIRECTORY +; UNDER THIS DIRECTORY. +; +; THIS FILE CAN BE CONSIDERED A REFERENCE THAT LISTS ALL POSSIBLE CONFIGURATION SETTINGS +; FOR THE PLATFORM. +; +#DEFINE PLATFORM_NAME "DYNO" +; +PLATFORM .EQU PLT_DYNO ; PLT_[SBC|ZETA|ZETA2|N8|MK4|UNA|RCZ80|RCZ180|EZZ80|SCZ180|DYNO] +CPUFAM .EQU CPU_Z180 ; CPU FAMILY: CPU_[Z80|Z180] +BIOS .EQU BIOS_WBW ; HARDWARE BIOS: BIOS_[WBW|UNA] +BATCOND .EQU FALSE ; ENABLE LOW BATTERY WARNING MESSAGE +HBIOS_MUTEX .EQU FALSE ; ENABLE REENTRANT CALLS TO HBIOS (ADDS OVERHEAD) +USELZSA2 .EQU TRUE ; ENABLE FONT COMPRESSION +; +BOOT_TIMEOUT .EQU 0 ; AUTO BOOT TIMEOUT IN SECONDS, 0 TO DISABLE +; +CPUOSC .EQU 18432000 ; CPU OSC FREQ IN MHZ +INTMODE .EQU 2 ; INTERRUPTS: 0=NONE, 1=MODE 1, 2=MODE 2 +DEFSERCFG .EQU SER_38400_8N1 ; DEFAULT SERIAL LINE CONFIG (SEE STD.ASM) +; +RAMSIZE .EQU 512 ; SIZE OF RAM IN KB (MUST MATCH YOUR HARDWARE!!!) +MEMMGR .EQU MM_Z180 ; MEMORY MANAGER: MM_[SBC|Z2|N8|Z180] +RAMBIAS .EQU 512 ; OFFSET OF START OF RAM IN PHYSICAL ADDRESS SPACE +MPGSEL_0 .EQU $78 ; Z2 MEM MGR BANK 0 PAGE SELECT REG (WRITE ONLY) +MPGSEL_1 .EQU $79 ; Z2 MEM MGR BANK 1 PAGE SELECT REG (WRITE ONLY) +MPGSEL_2 .EQU $7A ; Z2 MEM MGR BANK 2 PAGE SELECT REG (WRITE ONLY) +MPGSEL_3 .EQU $7B ; Z2 MEM MGR BANK 3 PAGE SELECT REG (WRITE ONLY) +MPGENA .EQU $7C ; Z2 MEM MGR PAGING ENABLE REGISTER (BIT 0, WRITE ONLY) +; +Z180_BASE .EQU $C0 ; Z180: I/O BASE ADDRESS FOR INTERNAL REGISTERS +Z180_CLKDIV .EQU 1 ; Z180: CHK DIV: 0=OSC/2, 1=OSC, 2=OSC*2 +Z180_MEMWAIT .EQU 0 ; Z180: MEMORY WAIT STATES (0-3) +Z180_IOWAIT .EQU 1 ; Z180: I/O WAIT STATES TO ADD ABOVE 1 W/S BUILT-IN (0-3) +; +RTCIO .EQU $0C ; RTC LATCH REGISTER ADR +; +KIOENABLE .EQU FALSE ; ENABLE ZILOG KIO SUPPORT +KIOBASE .EQU $80 ; KIO BASE I/O ADDRESS +; +CTCENABLE .EQU FALSE ; ENABLE ZILOG CTC SUPPORT +; +DIAGENABLE .EQU FALSE ; ENABLES OUTPUT TO 8 BIT LED DIAGNOSTIC PORT +DIAGPORT .EQU $00 ; DIAGNOSTIC PORT ADDRESS +DIAGDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON DIAGNOSTIC LEDS +; +LEDENABLE .EQU FALSE ; ENABLES STATUS LED (SINGLE LED) +LEDPORT .EQU $0E ; STATUS LED PORT ADDRESS +LEDDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON STATUS LED +; +DSKYENABLE .EQU FALSE ; ENABLES DSKY (DO NOT COMBINE WITH PPIDE) +; +CRTACT .EQU FALSE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP +VDAEMU .EQU EMUTYP_ANSI ; VDA EMULATION: EMUTYP_[TTY|ANSI] +ANSITRACE .EQU 1 ; ANSI DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +DSRTCENABLE .EQU FALSE ; DSRTC: ENABLE DS-1302 CLOCK DRIVER (DSRTC.ASM) +DSRTCMODE .EQU DSRTCMODE_STD ; DSRTC: OPERATING MODE: DSRTC_[STD|MFPIC] +DSRTCCHG .EQU FALSE ; DSRTC: FORCE BATTERY CHARGE ON (USE WITH CAUTION!!!) +; +BQRTCENABLE .EQU TRUE ; BQRTC: ENABLE BQ4845 CLOCK DRIVER (BQRTC.ASM) +BQRTC_BASE .EQU $50 ; BQRTC: I/O BASE ADDRESS +; +INTRTCENABLE .EQU FALSE ; ENABLE PERIODIC INTERRUPT CLOCK DRIVER (INTRTC.ASM) +; +HTIMENABLE .EQU FALSE ; ENABLE SIMH TIMER SUPPORT +SIMRTCENABLE .EQU FALSE ; ENABLE SIMH CLOCK DRIVER (SIMRTC.ASM) +; +UARTENABLE .EQU FALSE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +; +ASCIENABLE .EQU TRUE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +ASCI0CFG .EQU DEFSERCFG ; ASCI 0: SERIAL LINE CONFIG +ASCI1CFG .EQU DEFSERCFG ; ASCI 1: SERIAL LINE CONFIG +; +ACIAENABLE .EQU FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +; +SIOENABLE .EQU FALSE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +; +XIOCFG .EQU DEFSERCFG ; XIO: SERIAL LINE CONFIG +; +VDUENABLE .EQU FALSE ; VDU: ENABLE VDU VIDEO/KBD DRIVER (VDU.ASM) +CVDUENABLE .EQU FALSE ; CVDU: ENABLE CVDU VIDEO/KBD DRIVER (CVDU.ASM) +NECENABLE .EQU FALSE ; NEC: ENABLE NEC UPD7220 VIDEO/KBD DRIVER (NEC.ASM) +TMSENABLE .EQU FALSE ; TMS: ENABLE TMS9918 VIDEO/KBD DRIVER (TMS.ASM) +VGAENABLE .EQU FALSE ; VGA: ENABLE VGA VIDEO/KBD DRIVER (VGA.ASM) +; +SPKENABLE .EQU FALSE ; SPK: ENABLE RTC LATCH IOBIT SOUND DRIVER (SPK.ASM) +; +AYENABLE .EQU FALSE ; AY: ENABLE AY PSG SOUND DRIVER +AYMODE .EQU AYMODE_RCZ180 ; AY: DRIVER MODE: AYMODE_[SCG/N8/RCZ80/RCZ180] +; +MDENABLE .EQU TRUE ; MD: ENABLE MEMORY (ROM/RAM) DISK DRIVER (MD.ASM) +MDTRACE .EQU 1 ; MD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +FDENABLE .EQU FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .EQU FDMODE_DYNO ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3|DYNO] +FDTRACE .EQU 1 ; FD: TRACE LEVEL (0=NO,1=FATAL,2=ERRORS,3=ALL) +FDMEDIA .EQU FDM144 ; FD: DEFAULT MEDIA FORMAT FDM[720|144|360|120|111] +FDMEDIAALT .EQU FDM720 ; FD: ALTERNATE MEDIA FORMAT FDM[720|144|360|120|111] +FDMAUTO .EQU TRUE ; FD: AUTO SELECT DEFAULT/ALTERNATE MEDIA FORMATS +; +RFENABLE .EQU FALSE ; RF: ENABLE RAM FLOPPY DRIVER +; +IDEENABLE .EQU FALSE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +IDETRACE .EQU 1 ; IDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +IDECNT .EQU 1 ; IDE: NUMBER OF IDE INTERFACES TO DETECT (1-3), 2 DRIVES EACH +IDE0MODE .EQU IDEMODE_RC ; IDE 0: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE0BASE .EQU $10 ; IDE 0: IO BASE ADDRESS +IDE0DATLO .EQU $00 ; IDE 0: DATA LO PORT FOR 16-BIT I/O +IDE0DATHI .EQU $00 ; IDE 0: DATA HI PORT FOR 16-BIT I/O +IDE0A8BIT .EQU TRUE ; IDE 0A (MASTER): 8 BIT XFER +IDE0B8BIT .EQU TRUE ; IDE 0B (MASTER): 8 BIT XFER +IDE1MODE .EQU IDEMODE_NONE ; IDE 1: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE1BASE .EQU $00 ; IDE 1: IO BASE ADDRESS +IDE1DATLO .EQU $00 ; IDE 1: DATA LO PORT FOR 16-BIT I/O +IDE1DATHI .EQU $00 ; IDE 1: DATA HI PORT FOR 16-BIT I/O +IDE1A8BIT .EQU TRUE ; IDE 1A (MASTER): 8 BIT XFER +IDE1B8BIT .EQU TRUE ; IDE 1B (MASTER): 8 BIT XFER +IDE2MODE .EQU IDEMODE_NONE ; IDE 2: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE2BASE .EQU $00 ; IDE 2: IO BASE ADDRESS +IDE2DATLO .EQU $00 ; IDE 2: DATA LO PORT FOR 16-BIT I/O +IDE2DATHI .EQU $00 ; IDE 2: DATA HI PORT FOR 16-BIT I/O +IDE2A8BIT .EQU TRUE ; IDE 2A (MASTER): 8 BIT XFER +IDE2B8BIT .EQU TRUE ; IDE 2B (MASTER): 8 BIT XFER +; +PPIDEENABLE .EQU FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) +PPIDETRACE .EQU 1 ; PPIDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPIDECNT .EQU 1 ; PPIDE: NUMBER OF PPI CHIPS TO DETECT (1-3), 2 DRIVES PER CHIP +PPIDE0BASE .EQU $4C ; PPIDE 0: PPI REGISTERS BASE ADR +PPIDE0A8BIT .EQU FALSE ; PPIDE 0A (MASTER): 8 BIT XFER +PPIDE0B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +; +SDENABLE .EQU FALSE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDMODE .EQU SDMODE_PPI ; SD: DRIVER MODE: SDMODE_[JUHA|N8|CSIO|PPI|UART|DSD|MK4|SC|MT] +SDCNT .EQU 1 ; SD: NUMBER OF SD CARD DEVICES (1-2), FOR DSD & SC ONLY +SDTRACE .EQU 1 ; SD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +SDCSIOFAST .EQU FALSE ; SD: ENABLE TABLE-DRIVEN BIT INVERTER IN CSIO MODE +; +PRPENABLE .EQU FALSE ; PRP: ENABLE ECB PROPELLER IO BOARD DRIVER (PRP.ASM) +; +PPPENABLE .EQU FALSE ; PPP: ENABLE ZETA PARALLEL PORT PROPELLER BOARD DRIVER (PPP.ASM) +; +HDSKENABLE .EQU FALSE ; HDSK: ENABLE SIMH HDSK DISK DRIVER (HDSK.ASM) +; +PIO_4P .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB 4P BOARD +PIO_ZP .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB ZILOG PERIPHERALS BOARD (PIO.ASM) +PPI_SBC .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR 8255 CHIP +; +UFENABLE .EQU FALSE ; UF: ENABLE ECB USB FIFO DRIVER (UF.ASM) diff --git a/Source/HBIOS/cfg_ezz80.asm b/Source/HBIOS/cfg_ezz80.asm new file mode 100644 index 00000000..1c64c652 --- /dev/null +++ b/Source/HBIOS/cfg_ezz80.asm @@ -0,0 +1,178 @@ +; +;================================================================================================== +; ROMWBW 2.X CONFIGURATION DEFAULTS FOR EASY Z80 +;================================================================================================== +; +; THIS FILE CONTAINS THE FULL SET OF DEFAULT CONFIGURATION SETTINGS FOR THE PLATFORM +; INDICATED ABOVE. THIS FILE SHOULD *NOT* NORMALLY BE CHANGED. INSTEAD, YOU SHOULD +; OVERRIDE ANY SETTINGS YOU WANT USING A CONFIGURATION FILE IN THE CONFIG DIRECTORY +; UNDER THIS DIRECTORY. +; +; THIS FILE CAN BE CONSIDERED A REFERENCE THAT LISTS ALL POSSIBLE CONFIGURATION SETTINGS +; FOR THE PLATFORM. +; +#DEFINE PLATFORM_NAME "EASYZ80" +; +PLATFORM .EQU PLT_EZZ80 ; PLT_[SBC|ZETA|ZETA2|N8|MK4|UNA|RCZ80|RCZ180|EZZ80|SCZ180|DYNO] +CPUFAM .EQU CPU_Z80 ; CPU FAMILY: CPU_[Z80|Z180] +BIOS .EQU BIOS_WBW ; HARDWARE BIOS: BIOS_[WBW|UNA] +BATCOND .EQU FALSE ; ENABLE LOW BATTERY WARNING MESSAGE +HBIOS_MUTEX .EQU FALSE ; ENABLE REENTRANT CALLS TO HBIOS (ADDS OVERHEAD) +USELZSA2 .EQU TRUE ; ENABLE FONT COMPRESSION +; +BOOT_TIMEOUT .EQU 0 ; AUTO BOOT TIMEOUT IN SECONDS, 0 TO DISABLE +; +CPUOSC .EQU 10000000 ; CPU OSC FREQ IN MHZ +INTMODE .EQU 2 ; INTERRUPTS: 0=NONE, 1=MODE 1, 2=MODE 2 +DEFSERCFG .EQU SER_115200_8N1 ; DEFAULT SERIAL LINE CONFIG (SEE STD.ASM) +; +RAMSIZE .EQU 512 ; SIZE OF RAM IN KB (MUST MATCH YOUR HARDWARE!!!) +MEMMGR .EQU MM_Z2 ; MEMORY MANAGER: MM_[SBC|Z2|N8|Z180] +MPGSEL_0 .EQU $78 ; Z2 MEM MGR BANK 0 PAGE SELECT REG (WRITE ONLY) +MPGSEL_1 .EQU $79 ; Z2 MEM MGR BANK 1 PAGE SELECT REG (WRITE ONLY) +MPGSEL_2 .EQU $7A ; Z2 MEM MGR BANK 2 PAGE SELECT REG (WRITE ONLY) +MPGSEL_3 .EQU $7B ; Z2 MEM MGR BANK 3 PAGE SELECT REG (WRITE ONLY) +MPGENA .EQU $7C ; Z2 MEM MGR PAGING ENABLE REGISTER (BIT 0, WRITE ONLY) +; +RTCIO .EQU $C0 ; RTC LATCH REGISTER ADR +WDOGIO .EQU $6F ; WATCHDOG REGISTER ADR +; +KIOENABLE .EQU FALSE ; ENABLE ZILOG KIO SUPPORT +KIOBASE .EQU $80 ; KIO BASE I/O ADDRESS +; +CTCENABLE .EQU TRUE ; ENABLE ZILOG CTC SUPPORT +CTCMODE .EQU CTCMODE_EZ ; CTC MODE: CTCMODE_[ZP|Z2|EZ|RC] +CTCBASE .EQU $88 ; CTC BASE I/O ADDRESS +; +DIAGENABLE .EQU FALSE ; ENABLES OUTPUT TO 8 BIT LED DIAGNOSTIC PORT +DIAGPORT .EQU $00 ; DIAGNOSTIC PORT ADDRESS +DIAGDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON DIAGNOSTIC LEDS +; +LEDENABLE .EQU FALSE ; ENABLES STATUS LED (SINGLE LED) +LEDPORT .EQU $0E ; STATUS LED PORT ADDRESS +LEDDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON STATUS LED +; +DSKYENABLE .EQU FALSE ; ENABLES DSKY (DO NOT COMBINE WITH PPIDE) +; +CRTACT .EQU FALSE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP +VDAEMU .EQU EMUTYP_ANSI ; VDA EMULATION: EMUTYP_[TTY|ANSI] +ANSITRACE .EQU 1 ; ANSI DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +DSRTCENABLE .EQU TRUE ; DSRTC: ENABLE DS-1302 CLOCK DRIVER (DSRTC.ASM) +DSRTCMODE .EQU DSRTCMODE_STD ; DSRTC: OPERATING MODE: DSRTC_[STD|MFPIC] +DSRTCCHG .EQU FALSE ; DSRTC: FORCE BATTERY CHARGE ON (USE WITH CAUTION!!!) +; +BQRTCENABLE .EQU FALSE ; BQRTC: ENABLE BQ4845 CLOCK DRIVER (BQRTC.ASM) +BQRTC_BASE .EQU $50 ; BQRTC: I/O BASE ADDRESS +; +INTRTCENABLE .EQU FALSE ; ENABLE PERIODIC INTERRUPT CLOCK DRIVER (INTRTC.ASM) +; +HTIMENABLE .EQU FALSE ; ENABLE SIMH TIMER SUPPORT +SIMRTCENABLE .EQU FALSE ; ENABLE SIMH CLOCK DRIVER (SIMRTC.ASM) +; +UARTENABLE .EQU TRUE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +UARTOSC .EQU 1843200 ; UART: OSC FREQUENCY IN MHZ +UARTCFG .EQU DEFSERCFG ; UART: LINE CONFIG FOR UART PORTS +UARTSBC .EQU FALSE ; UART: AUTO-DETECT SBC/ZETA ONBOARD UART +UARTCAS .EQU FALSE ; UART: AUTO-DETECT ECB CASSETTE UART +UARTMFP .EQU FALSE ; UART: AUTO-DETECT MF/PIC UART +UART4 .EQU FALSE ; UART: AUTO-DETECT 4UART UART +UARTRC .EQU TRUE ; UART: AUTO-DETECT RC UART +; +ASCIENABLE .EQU FALSE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +; +ACIAENABLE .EQU FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +; +SIOENABLE .EQU TRUE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +SIODEBUG .EQU FALSE ; SIO: ENABLE DEBUG OUTPUT +SIOCNT .EQU 2 ; SIO: NUMBER OF CHIPS TO DETECT (1-2), 2 CHANNELS PER CHIP +SIO0MODE .EQU SIOMODE_EZZ80 ; SIO 0: CHIP TYPE: SIOMODE_[RC|SMB|ZP|EZZ80] +SIO0CTCC .EQU -1 ; SIO 0: CTC CHANNEL CLOCK SCALER, (0-3), -1 FOR NONE +SIO0BASE .EQU $80 ; SIO 0: REGISTERS BASE ADR +SIO0ACLK .EQU 1843200 ; SIO 0A: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO0ADIV .EQU 1 ; SIO 0A: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO0ACFG .EQU DEFSERCFG ; SIO 0A: SERIAL LINE CONFIG +SIO0BCLK .EQU 1843200 ; SIO 0B: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO0BDIV .EQU 1 ; SIO 0B: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO0BCFG .EQU DEFSERCFG ; SIO 0B: SERIAL LINE CONFIG +SIO1MODE .EQU SIOMODE_RC ; SIO 1: CHIP TYPE: SIOMODE_[RC|SMB|ZP|EZZ80] +SIO1CTCC .EQU -1 ; SIO 1: CTC CHANNEL CLOCK SCALER, (0-3), -1 FOR NONE +SIO1BASE .EQU $84 ; SIO 1: REGISTERS BASE ADR +SIO1ACLK .EQU 7372800 ; SIO 1A: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO1ADIV .EQU 1 ; SIO 1A: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO1ACFG .EQU DEFSERCFG ; SIO 1A: SERIAL LINE CONFIG +SIO1BCLK .EQU 7372800 ; SIO 1B: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO1BDIV .EQU 1 ; SIO 1B: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO1BCFG .EQU DEFSERCFG ; SIO 1B: SERIAL LINE CONFIG +; +XIOCFG .EQU DEFSERCFG ; XIO: SERIAL LINE CONFIG +; +VDUENABLE .EQU FALSE ; VDU: ENABLE VDU VIDEO/KBD DRIVER (VDU.ASM) +CVDUENABLE .EQU FALSE ; CVDU: ENABLE CVDU VIDEO/KBD DRIVER (CVDU.ASM) +NECENABLE .EQU FALSE ; NEC: ENABLE NEC UPD7220 VIDEO/KBD DRIVER (NEC.ASM) +TMSENABLE .EQU FALSE ; TMS: ENABLE TMS9918 VIDEO/KBD DRIVER (TMS.ASM) +VGAENABLE .EQU FALSE ; VGA: ENABLE VGA VIDEO/KBD DRIVER (VGA.ASM) +; +SPKENABLE .EQU FALSE ; SPK: ENABLE RTC LATCH IOBIT SOUND DRIVER (SPK.ASM) +; +AYENABLE .EQU FALSE ; AY: ENABLE AY PSG SOUND DRIVER +AYMODE .EQU AYMODE_RCZ80 ; AY: DRIVER MODE: AYMODE_[SCG/N8/RCZ80/RCZ180] +; +MDENABLE .EQU TRUE ; MD: ENABLE MEMORY (ROM/RAM) DISK DRIVER (MD.ASM) +MDTRACE .EQU 1 ; MD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +FDENABLE .EQU FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .EQU FDMODE_RCWDC ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3|DYNO] +FDTRACE .EQU 1 ; FD: TRACE LEVEL (0=NO,1=FATAL,2=ERRORS,3=ALL) +FDMEDIA .EQU FDM144 ; FD: DEFAULT MEDIA FORMAT FDM[720|144|360|120|111] +FDMEDIAALT .EQU FDM720 ; FD: ALTERNATE MEDIA FORMAT FDM[720|144|360|120|111] +FDMAUTO .EQU TRUE ; FD: AUTO SELECT DEFAULT/ALTERNATE MEDIA FORMATS +; +RFENABLE .EQU FALSE ; RF: ENABLE RAM FLOPPY DRIVER +; +IDEENABLE .EQU FALSE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +IDETRACE .EQU 1 ; IDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +IDECNT .EQU 1 ; IDE: NUMBER OF IDE INTERFACES TO DETECT (1-3), 2 DRIVES EACH +IDE0MODE .EQU IDEMODE_RC ; IDE 0: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE0BASE .EQU $10 ; IDE 0: IO BASE ADDRESS +IDE0DATLO .EQU $00 ; IDE 0: DATA LO PORT FOR 16-BIT I/O +IDE0DATHI .EQU $00 ; IDE 0: DATA HI PORT FOR 16-BIT I/O +IDE0A8BIT .EQU TRUE ; IDE 0A (MASTER): 8 BIT XFER +IDE0B8BIT .EQU TRUE ; IDE 0B (MASTER): 8 BIT XFER +IDE1MODE .EQU IDEMODE_NONE ; IDE 1: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE1BASE .EQU $00 ; IDE 1: IO BASE ADDRESS +IDE1DATLO .EQU $00 ; IDE 1: DATA LO PORT FOR 16-BIT I/O +IDE1DATHI .EQU $00 ; IDE 1: DATA HI PORT FOR 16-BIT I/O +IDE1A8BIT .EQU TRUE ; IDE 1A (MASTER): 8 BIT XFER +IDE1B8BIT .EQU TRUE ; IDE 1B (MASTER): 8 BIT XFER +IDE2MODE .EQU IDEMODE_NONE ; IDE 2: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE2BASE .EQU $00 ; IDE 2: IO BASE ADDRESS +IDE2DATLO .EQU $00 ; IDE 2: DATA LO PORT FOR 16-BIT I/O +IDE2DATHI .EQU $00 ; IDE 2: DATA HI PORT FOR 16-BIT I/O +IDE2A8BIT .EQU TRUE ; IDE 2A (MASTER): 8 BIT XFER +IDE2B8BIT .EQU TRUE ; IDE 2B (MASTER): 8 BIT XFER +; +PPIDEENABLE .EQU FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) +PPIDETRACE .EQU 1 ; PPIDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPIDECNT .EQU 1 ; PPIDE: NUMBER OF PPI CHIPS TO DETECT (1-3), 2 DRIVES PER CHIP +PPIDE0BASE .EQU $20 ; PPIDE 0: PPI REGISTERS BASE ADR +PPIDE0A8BIT .EQU FALSE ; PPIDE 0A (MASTER): 8 BIT XFER +PPIDE0B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +; +SDENABLE .EQU FALSE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDMODE .EQU SDMODE_PPI ; SD: DRIVER MODE: SDMODE_[JUHA|N8|CSIO|PPI|UART|DSD|MK4|SC|MT] +SDCNT .EQU 1 ; SD: NUMBER OF SD CARD DEVICES (1-2), FOR DSD & SC ONLY +SDTRACE .EQU 1 ; SD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +SDCSIOFAST .EQU FALSE ; SD: ENABLE TABLE-DRIVEN BIT INVERTER IN CSIO MODE +; +PRPENABLE .EQU FALSE ; PRP: ENABLE ECB PROPELLER IO BOARD DRIVER (PRP.ASM) +; +PPPENABLE .EQU FALSE ; PPP: ENABLE ZETA PARALLEL PORT PROPELLER BOARD DRIVER (PPP.ASM) +; +HDSKENABLE .EQU FALSE ; HDSK: ENABLE SIMH HDSK DISK DRIVER (HDSK.ASM) +; +PIO_4P .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB 4P BOARD +PIO_ZP .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB ZILOG PERIPHERALS BOARD (PIO.ASM) +PPI_SBC .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR 8255 CHIP +; +UFENABLE .EQU FALSE ; UF: ENABLE ECB USB FIFO DRIVER (UF.ASM) diff --git a/Source/HBIOS/cfg_master.asm b/Source/HBIOS/cfg_master.asm new file mode 100644 index 00000000..55806745 --- /dev/null +++ b/Source/HBIOS/cfg_master.asm @@ -0,0 +1,233 @@ +; +;================================================================================================== +; ROMWBW 2.X CONFIGURATION MASTER +;================================================================================================== +; +; THIS FILE IS *NOT* A REAL CONFIGURATION FILE. IT IS A MASTER TEMPLATE FILE +; CONTAINING ALL POSSIBLE CONFIGURATION SETTINGS ACROSS ALL PLATFORMS. IT IS +; USED ONLY AS A REFERENCE TO HELP MANAGE THE FULL SET OF POSSIBLE SETTINGS AND +; KEEP THINGS CONSISTENT. +; +#DEFINE PLATFORM_NAME "ROMWBW" +; +PLATFORM .EQU PLT_SBC ; PLT_[SBC|ZETA|ZETA2|N8|MK4|UNA|RCZ80|RCZ180|EZZ80|SCZ180|DYNO] +CPUFAM .EQU CPU_Z80 ; CPU FAMILY: CPU_[Z80|Z180] +BIOS .EQU BIOS_WBW ; HARDWARE BIOS: BIOS_[WBW|UNA] +BATCOND .EQU FALSE ; ENABLE LOW BATTERY WARNING MESSAGE +HBIOS_MUTEX .EQU FALSE ; ENABLE REENTRANT CALLS TO HBIOS (ADDS OVERHEAD) +USELZSA2 .EQU TRUE ; ENABLE FONT COMPRESSION +; +BOOT_TIMEOUT .EQU 0 ; AUTO BOOT TIMEOUT IN SECONDS, 0 TO DISABLE +; +CPUOSC .EQU 8000000 ; CPU OSC FREQ IN MHZ +INTMODE .EQU 0 ; INTERRUPTS: 0=NONE, 1=MODE 1, 2=MODE 2 +DEFSERCFG .EQU SER_38400_8N1 ; DEFAULT SERIAL LINE CONFIG (SEE STD.ASM) +; +RAMSIZE .EQU 512 ; SIZE OF RAM IN KB (MUST MATCH YOUR HARDWARE!!!) +MEMMGR .EQU MM_NONE ; MEMORY MANAGER: MM_[SBC|Z2|N8|Z180] +RAMBIAS .EQU 0 ; OFFSET OF START OF RAM IN PHYSICAL ADDRESS SPACE +MPCL_RAM .EQU $78 ; SBC MEM MGR RAM PAGE SELECT REG (WRITE ONLY) +MPCL_ROM .EQU $7C ; SBC MEM MGR ROM PAGE SELECT REG (WRITE ONLY) +MPGSEL_0 .EQU $78 ; Z2 MEM MGR BANK 0 PAGE SELECT REG (WRITE ONLY) +MPGSEL_1 .EQU $79 ; Z2 MEM MGR BANK 1 PAGE SELECT REG (WRITE ONLY) +MPGSEL_2 .EQU $7A ; Z2 MEM MGR BANK 2 PAGE SELECT REG (WRITE ONLY) +MPGSEL_3 .EQU $7B ; Z2 MEM MGR BANK 3 PAGE SELECT REG (WRITE ONLY) +MPGENA .EQU $7C ; Z2 MEM MGR PAGING ENABLE REGISTER (BIT 0, WRITE ONLY) +; +Z180_BASE .EQU $40 ; Z180: I/O BASE ADDRESS FOR INTERNAL REGISTERS +Z180_CLKDIV .EQU 1 ; Z180: CHK DIV: 0=OSC/2, 1=OSC, 2=OSC*2 +Z180_MEMWAIT .EQU 0 ; Z180: MEMORY WAIT STATES (0-3) +Z180_IOWAIT .EQU 1 ; Z180: I/O WAIT STATES TO ADD ABOVE 1 W/S BUILT-IN (0-3) +; +N8_PPI0 .EQU $80 ; N8: FIRST PARALLEL PORT REGISTERS BASE ADR +N8_PPI1 .EQU $84 ; N8: SECOND PARALLEL PORT REGISTERS BASE ADR +N8_RTC .EQU $88 ; N8: RTC LATCH REGISTER ADR +N8_ACR .EQU $94 ; N8: AUXILLARY CONTROL REGISTER (ACR) ADR +N8_RMAP .EQU $96 ; N8: ROM PAGE REGISTER ADR +N8_DEFACR .EQU $1B ; N8: AUX CTL REGISTER DEFAULT VALUE (QUIESCIENT STATE) +; +MK4_IDE .EQU $80 ; MK4: IDE REGISTERS BASE ADR +MK4_XAR .EQU $88 ; MK4: EXTERNAL ADDRESS REGISTER (XAR) ADR +MK4_SD .EQU $89 ; MK4: SD CARD CONTROL REGISTER ADR +MK4_RTC .EQU $8A ; MK4: RTC LATCH REGISTER ADR +; +RTCIO .EQU $70 ; RTC LATCH REGISTER ADR +WDOGIO .EQU $6F ; WATCHDOG REGISTER ADR +PPIBASE .EQU $60 ; PRIMARY PARALLEL PORT REGISTERS BASE ADR +; +KIOENABLE .EQU FALSE ; ENABLE ZILOG KIO SUPPORT +KIOBASE .EQU $80 ; KIO BASE I/O ADDRESS +; +CTCENABLE .EQU FALSE ; ENABLE ZILOG CTC SUPPORT +CTCMODE .EQU CTCMODE_ZP ; CTC MODE: CTCMODE_[ZP|Z2|EZ|RC] +CTCBASE .EQU $20 ; CTC BASE I/O ADDRESS +; +DIAGENABLE .EQU FALSE ; ENABLES OUTPUT TO 8 BIT LED DIAGNOSTIC PORT +DIAGPORT .EQU $00 ; DIAGNOSTIC PORT ADDRESS +DIAGDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON DIAGNOSTIC LEDS +; +LEDENABLE .EQU FALSE ; ENABLES STATUS LED (SINGLE LED) +LEDPORT .EQU $0E ; STATUS LED PORT ADDRESS +LEDDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON STATUS LED +; +DSKYENABLE .EQU FALSE ; ENABLES DSKY (DO NOT COMBINE WITH PPIDE) +; +CRTACT .EQU FALSE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP +VDAEMU .EQU EMUTYP_ANSI ; VDA EMULATION: EMUTYP_[TTY|ANSI] +ANSITRACE .EQU 1 ; ANSI DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPKTRACE .EQU 1 ; PPK DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +KBDTRACE .EQU 1 ; KBD DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPKKBLOUT .EQU KBD_US ; PPK KEYBOARD LANGUAGE: KBD_[US|DE] +KBDKBLOUT .EQU KBD_US ; KBD KEYBOARD LANGUAGE: KBD_[US|DE] +; +DSRTCENABLE .EQU FALSE ; DSRTC: ENABLE DS-1302 CLOCK DRIVER (DSRTC.ASM) +DSRTCMODE .EQU DSRTCMODE_STD ; DSRTC: OPERATING MODE: DSRTC_[STD|MFPIC] +DSRTCCHG .EQU FALSE ; DSRTC: FORCE BATTERY CHARGE ON (USE WITH CAUTION!!!) +; +BQRTCENABLE .EQU FALSE ; BQRTC: ENABLE BQ4845 CLOCK DRIVER (BQRTC.ASM) +BQRTC_BASE .EQU $50 ; BQRTC: I/O BASE ADDRESS +; +INTRTCENABLE .EQU FALSE ; ENABLE PERIODIC INTERRUPT CLOCK DRIVER (INTRTC.ASM) +; +HTIMENABLE .EQU FALSE ; ENABLE SIMH TIMER SUPPORT +SIMRTCENABLE .EQU FALSE ; ENABLE SIMH CLOCK DRIVER (SIMRTC.ASM) +; +UARTENABLE .EQU FALSE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +UARTOSC .EQU 1843200 ; UART: OSC FREQUENCY IN MHZ +UARTCFG .EQU DEFSERCFG ; UART: LINE CONFIG FOR UART PORTS +UARTCASSPD .EQU SER_300_8N1 ; UART: ECB CASSETTE UART DEFAULT SPEED +UARTSBC .EQU FALSE ; UART: AUTO-DETECT SBC/ZETA ONBOARD UART +UARTCAS .EQU FALSE ; UART: AUTO-DETECT ECB CASSETTE UART +UARTMFP .EQU FALSE ; UART: AUTO-DETECT MF/PIC UART +UART4 .EQU FALSE ; UART: AUTO-DETECT 4UART UART +UARTRC .EQU FALSE ; UART: AUTO-DETECT RC UART +; +ASCIENABLE .EQU FALSE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +ASCI0CFG .EQU DEFSERCFG ; ASCI 0: SERIAL LINE CONFIG +ASCI1CFG .EQU DEFSERCFG ; ASCI 1: SERIAL LINE CONFIG +; +ACIAENABLE .EQU FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +ACIADEBUG .EQU FALSE ; ACIA: ENABLE DEBUG OUTPUT +ACIACNT .EQU 1 ; ACIA: NUMBER OF CHIPS TO DETECT (1-2) +ACIA0BASE .EQU $80 ; ACIA 0: REGISTERS BASE ADR +ACIA0CLK .EQU CPUOSC ; ACIA 0: OSC FREQ IN HZ +ACIA0DIV .EQU 1 ; ACIA 0: SERIAL CLOCK DIVIDER +ACIA0CFG .EQU DEFSERCFG ; ACIA 0: SERIAL LINE CONFIG (SEE STD.ASM) +ACIA1BASE .EQU $40 ; ACIA 1: REGISTERS BASE ADR +ACIA1CLK .EQU CPUOSC ; ACIA 1: OSC FREQ IN HZ +ACIA1DIV .EQU 1 ; ACIA 1: SERIAL CLOCK DIVIDER +ACIA1CFG .EQU DEFSERCFG ; ACIA 1: SERIAL LINE CONFIG (SEE STD.ASM) +; +SIOENABLE .EQU FALSE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +SIODEBUG .EQU FALSE ; SIO: ENABLE DEBUG OUTPUT +SIOCNT .EQU 2 ; SIO: NUMBER OF CHIPS TO DETECT (1-2), 2 CHANNELS PER CHIP +SIO0MODE .EQU SIOMODE_RC ; SIO 0: CHIP TYPE: SIOMODE_[RC|SMB|ZP|EZZ80] +SIO0CTCC .EQU -1 ; SIO 0: CTC CHANNEL CLOCK SCALER, (0-3), -1 FOR NONE +SIO0BASE .EQU $80 ; SIO 0: REGISTERS BASE ADR +SIO0ACLK .EQU CPUOSC ; SIO 0A: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO0ADIV .EQU 1 ; SIO 0A: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO0ACFG .EQU DEFSERCFG ; SIO 0A: SERIAL LINE CONFIG +SIO0BCLK .EQU CPUOSC ; SIO 0B: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO0BDIV .EQU 1 ; SIO 0B: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO0BCFG .EQU DEFSERCFG ; SIO 0B: SERIAL LINE CONFIG +SIO1MODE .EQU SIOMODE_RC ; SIO 1: CHIP TYPE: SIOMODE_[RC|SMB|ZP|EZZ80] +SIO1CTCC .EQU -1 ; SIO 1: CTC CHANNEL CLOCK SCALER, (0-3), -1 FOR NONE +SIO1BASE .EQU $84 ; SIO 1: REGISTERS BASE ADR +SIO1ACLK .EQU CPUOSC ; SIO 1A: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO1ADIV .EQU 1 ; SIO 1A: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO1ACFG .EQU DEFSERCFG ; SIO 1A: SERIAL LINE CONFIG +SIO1BCLK .EQU CPUOSC ; SIO 1B: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO1BDIV .EQU 1 ; SIO 1B: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO1BCFG .EQU DEFSERCFG ; SIO 1B: SERIAL LINE CONFIG +; +XIOCFG .EQU DEFSERCFG ; XIO: SERIAL LINE CONFIG +; +VDUENABLE .EQU FALSE ; VDU: ENABLE VDU VIDEO/KBD DRIVER (VDU.ASM) +VDUSIZ .EQU V80X25 ; VDU: DISPLAY FORMAT [V80X24|V80X25|V80X30] +CVDUENABLE .EQU FALSE ; CVDU: ENABLE CVDU VIDEO/KBD DRIVER (CVDU.ASM) +NECENABLE .EQU FALSE ; NEC: ENABLE NEC UPD7220 VIDEO/KBD DRIVER (NEC.ASM) +TMSENABLE .EQU FALSE ; TMS: ENABLE TMS9918 VIDEO/KBD DRIVER (TMS.ASM) +TMSMODE .EQU TMSMODE_NONE ; TMS: DRIVER MODE: TMSMODE_[SCG/N8] +VGAENABLE .EQU FALSE ; VGA: ENABLE VGA VIDEO/KBD DRIVER (VGA.ASM) +VGASIZ .EQU V80X25 ; VGA: DISPLAY FORMAT [V80X25|V80X30|V80X43] +; +SPKENABLE .EQU FALSE ; SPK: ENABLE RTC LATCH IOBIT SOUND DRIVER (SPK.ASM) +; +AYENABLE .EQU FALSE ; AY: ENABLE AY PSG SOUND DRIVER +AYMODE .EQU AYMODE_NONE ; AY: DRIVER MODE: AYMODE_[SCG/N8/RCZ80/RCZ180] +; +MDENABLE .EQU TRUE ; MD: ENABLE MEMORY (ROM/RAM) DISK DRIVER (MD.ASM) +MDTRACE .EQU 1 ; MD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +FDENABLE .EQU FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .EQU FDMODE_NONE ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3|DYNO] +FDTRACE .EQU 1 ; FD: TRACE LEVEL (0=NO,1=FATAL,2=ERRORS,3=ALL) +FDMEDIA .EQU FDM144 ; FD: DEFAULT MEDIA FORMAT FDM[720|144|360|120|111] +FDMEDIAALT .EQU FDM720 ; FD: ALTERNATE MEDIA FORMAT FDM[720|144|360|120|111] +FDMAUTO .EQU TRUE ; FD: AUTO SELECT DEFAULT/ALTERNATE MEDIA FORMATS +; +RFENABLE .EQU FALSE ; RF: ENABLE RAM FLOPPY DRIVER +RFCNT .EQU 1 ; RF: NUMBER OF RAM FLOPPY UNITS (1-4) +; +IDEENABLE .EQU FALSE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +IDETRACE .EQU 1 ; IDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +IDECNT .EQU 1 ; IDE: NUMBER OF IDE INTERFACES TO DETECT (1-3), 2 DRIVES EACH +IDE0MODE .EQU IDEMODE_NONE ; IDE 0: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE0BASE .EQU $20 ; IDE 0: IO BASE ADDRESS +IDE0DATLO .EQU $00 ; IDE 0: DATA LO PORT FOR 16-BIT I/O +IDE0DATHI .EQU $00 ; IDE 0: DATA HI PORT FOR 16-BIT I/O +IDE0A8BIT .EQU TRUE ; IDE 0A (MASTER): 8 BIT XFER +IDE0B8BIT .EQU TRUE ; IDE 0B (MASTER): 8 BIT XFER +IDE1MODE .EQU IDEMODE_NONE ; IDE 1: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE1BASE .EQU $00 ; IDE 1: IO BASE ADDRESS +IDE1DATLO .EQU $00 ; IDE 1: DATA LO PORT FOR 16-BIT I/O +IDE1DATHI .EQU $00 ; IDE 1: DATA HI PORT FOR 16-BIT I/O +IDE1A8BIT .EQU TRUE ; IDE 1A (MASTER): 8 BIT XFER +IDE1B8BIT .EQU TRUE ; IDE 1B (MASTER): 8 BIT XFER +IDE2MODE .EQU IDEMODE_NONE ; IDE 2: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE2BASE .EQU $00 ; IDE 2: IO BASE ADDRESS +IDE2DATLO .EQU $00 ; IDE 2: DATA LO PORT FOR 16-BIT I/O +IDE2DATHI .EQU $00 ; IDE 2: DATA HI PORT FOR 16-BIT I/O +IDE2A8BIT .EQU TRUE ; IDE 2A (MASTER): 8 BIT XFER +IDE2B8BIT .EQU TRUE ; IDE 2B (MASTER): 8 BIT XFER +; +PPIDEENABLE .EQU FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) +PPIDETRACE .EQU 1 ; PPIDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPIDECNT .EQU 1 ; PPIDE: NUMBER OF PPI CHIPS TO DETECT (1-3), 2 DRIVES PER CHIP +PPIDE0BASE .EQU $60 ; PPIDE 0: PPI REGISTERS BASE ADR +PPIDE0A8BIT .EQU FALSE ; PPIDE 0A (MASTER): 8 BIT XFER +PPIDE0B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +PPIDE1BASE .EQU $20 ; PPIDE 1: PPI REGISTERS BASE ADR +PPIDE1A8BIT .EQU FALSE ; PPIDE 1A (MASTER): 8 BIT XFER +PPIDE1B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +PPIDE2BASE .EQU $44 ; PPIDE 2: PPI REGISTERS BASE ADR +PPIDE2A8BIT .EQU FALSE ; PPIDE 2A (MASTER): 8 BIT XFER +PPIDE2B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +; +SDENABLE .EQU FALSE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDMODE .EQU SDMODE_NONE ; SD: DRIVER MODE: SDMODE_[JUHA|N8|CSIO|PPI|UART|DSD|MK4|SC|MT] +SDCNT .EQU 1 ; SD: NUMBER OF SD CARD DEVICES (1-2), FOR DSD & SC ONLY +SDTRACE .EQU 1 ; SD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +SDCSIOFAST .EQU FALSE ; SD: ENABLE TABLE-DRIVEN BIT INVERTER IN CSIO MODE +; +PRPENABLE .EQU FALSE ; PRP: ENABLE ECB PROPELLER IO BOARD DRIVER (PRP.ASM) +PRPSDENABLE .EQU TRUE ; PRP: ENABLE PROPIO DRIVER SD CARD SUPPORT +PRPSDTRACE .EQU 1 ; PRP: SD CARD TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PRPCONENABLE .EQU TRUE ; PRP: ENABLE PROPIO DRIVER VIDEO/KBD SUPPORT +; +PPPENABLE .EQU FALSE ; PPP: ENABLE ZETA PARALLEL PORT PROPELLER BOARD DRIVER (PPP.ASM) +PPPSDENABLE .EQU TRUE ; PPP: ENABLE PPP DRIVER SD CARD SUPPORT +PPPSDTRACE .EQU 1 ; PPP: SD CARD TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPPCONENABLE .EQU TRUE ; PPP: ENABLE PPP DRIVER VIDEO/KBD SUPPORT +; +HDSKENABLE .EQU FALSE ; HDSK: ENABLE SIMH HDSK DISK DRIVER (HDSK.ASM) +HDSKTRACE .EQU 1 ; HDSK: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +PIO_4P .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB 4P BOARD +PIO4BASE .EQU $90 ; PIO: PIO REGISTERS BASE ADR FOR ECB 4P BOARD +PIO_ZP .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB ZILOG PERIPHERALS BOARD (PIO.ASM) +PIOZBASE .EQU $88 ; PIO: PIO REGISTERS BASE ADR FOR ECB ZP BOARD +PPI_SBC .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR 8255 CHIP +; +UFENABLE .EQU FALSE ; UF: ENABLE ECB USB FIFO DRIVER (UF.ASM) +UFBASE .EQU $0C ; UF: REGISTERS BASE ADR diff --git a/Source/HBIOS/cfg_mk4.asm b/Source/HBIOS/cfg_mk4.asm index bda99450..6ee68e4e 100644 --- a/Source/HBIOS/cfg_mk4.asm +++ b/Source/HBIOS/cfg_mk4.asm @@ -3,103 +3,185 @@ ; ROMWBW 2.X CONFIGURATION FOR MARK IV ;================================================================================================== ; -; BUILD CONFIGURATION OPTIONS -; -CPUOSC .EQU 18432000 ; CPU OSC FREQ -RAMSIZE .EQU 512 ; SIZE OF RAM IN KB, MUST MATCH YOUR HARDWARE!!! -DEFSERCFG .EQU SER_38400_8N1 ; DEFAULT SERIAL LINE CONFIG (SHOULD MATCH ABOVE) -INTMODE .EQU 2 ; 0=NONE, 1=INT MODE 1, 2=INT MODE 2 -; -CRTACT .EQU FALSE ; CRT ACTIVATION AT STARTUP -VDAEMU .EQU EMUTYP_ANSI ; DEFAULT VDA EMULATION (EMUTYP_TTY, EMUTYP_ANSI, ...) -; -DSKYENABLE .EQU FALSE ; TRUE FOR DSKY SUPPORT (DO NOT COMBINE WITH PPIDE) -; -HTIMENABLE .EQU FALSE ; TRUE FOR SIMH TIMER SUPPORT -SIMRTCENABLE .EQU FALSE ; SIMH CLOCK DRIVER -DSRTCENABLE .EQU TRUE ; DS-1302 CLOCK DRIVER -DSRTCMODE .EQU DSRTCMODE_STD ; DSRTCMODE_STD, DSRTCMODE_MFPIC -DSRTCCHG .EQU FALSE ; DS-1302 CONFIGURE CHARGE ON (TRUE) OR OFF (FALSE) -; -ASCIENABLE .EQU TRUE ; TRUE FOR Z180 ASCI SUPPORT -UARTENABLE .EQU TRUE ; TRUE FOR UART SUPPORT (ALMOST ALWAYS WANT THIS TO BE TRUE) -UARTOSC .EQU 1843200 ; UART OSC FREQUENCY -SIOENABLE .EQU FALSE ; TRUE FOR ZILOG SIO/2 SUPPORT -SIOMODE .EQU SIOMODE_RC ; SIOMODE_RC, SIOMODE_SMB -ACIAENABLE .EQU FALSE ; TRUE FOR MOTOROLA 6850 ACIA SUPPORT -; -VDUENABLE .EQU FALSE ; TRUE FOR VDU BOARD SUPPORT -CVDUENABLE .EQU FALSE ; TRUE FOR CVDU BOARD SUPPORT -NECENABLE .EQU FALSE ; TRUE FOR uPD7220 BOARD SUPPORT -TMSENABLE .EQU FALSE ; TRUE FOR N8 (TMS9918) VIDEO/KBD SUPPORT -VGAENABLE .EQU FALSE ; TRUE FOR VGA VIDEO/KBD SUPPORT -; -SPKENABLE .EQU FALSE ; TRUE FOR RTC LATCH IOBIT SOUND -AYENABLE .EQU FALSE ; TRUE FOR AY PSG SOUND -; -MDENABLE .EQU TRUE ; TRUE FOR ROM/RAM DISK SUPPORT (ALMOST ALWAYS WANT THIS ENABLED) -MDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF MDENABLE = TRUE) -; -FDENABLE .EQU FALSE ; TRUE FOR FLOPPY SUPPORT -FDMODE .EQU FDMODE_N8 ; FDMODE_DIO, FDMODE_ZETA, FDMODE_DIDE, FDMODE_N8, FDMODE_DIO3 -FDTRACE .EQU 1 ; 0=SILENT, 1=FATAL ERRORS, 2=ALL ERRORS, 3=EVERYTHING (ONLY RELEVANT IF FDENABLE = TRUE) -FDMEDIA .EQU FDM144 ; FDM720, FDM144, FDM360, FDM120 (ONLY RELEVANT IF FDENABLE = TRUE) -FDMEDIAALT .EQU FDM720 ; ALTERNATE MEDIA TO TRY, SAME CHOICES AS ABOVE (ONLY RELEVANT IF FDMAUTO = TRUE) -FDMAUTO .EQU TRUE ; SELECT BETWEEN MEDIA OPTS ABOVE AUTOMATICALLY -; -RFENABLE .EQU FALSE ; TRUE FOR RAM FLOPPY SUPPORT -RFCNT .EQU 1 ; NUMBER OF RAM FLOPPY UNITS -; -IDEENABLE .EQU TRUE ; TRUE FOR IDE SUPPORT -IDEMODE .EQU IDEMODE_MK4 ; IDEMODE_DIO, IDEMODE_DIDE, IDEMODE_MK4 -IDECNT .EQU 2 ; NUMBER OF IDE UNITS -IDETRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF IDEENABLE = TRUE) -IDE8BIT .EQU TRUE ; USE IDE 8BIT TRANSFERS (PROBABLY ONLY WORKS FOR CF CARDS!) -; -PPIDEENABLE .EQU FALSE ; TRUE FOR PPIDE SUPPORT (DO NOT COMBINE WITH DSKYENABLE) -PPIDEMODE .EQU PPIDEMODE_DIO3 ; PPIDEMODE_SBC, PPPIDEMODE_DIO3, PPIDEMODE_MFP, PPIDEMODE_N8, PPIDEMODE_RC -PPIDECNT .EQU 1 ; NUMBER OF PPIDE UNITS -PPIDETRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPIDEENABLE = TRUE) -PPIDE8BIT .EQU FALSE ; USE IDE 8BIT TRANSFERS (PROBABLY ONLY WORKS FOR CF CARDS!) -PPIDESLOW .EQU FALSE ; ADD DELAYS TO HELP PROBLEMATIC HARDWARE (TRY THIS IF PPIDE IS UNRELIABLE) -; -SDENABLE .EQU TRUE ; TRUE FOR SD SUPPORT -SDMODE .EQU SDMODE_MK4 ; SDMODE_JUHA, SDMODE_CSIO, SDMODE_UART, SDMODE_PPI, SDMODE_DSD, SDMODE_MK4 -SDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF IDEENABLE = TRUE) -SDCSIOFAST .EQU TRUE ; TABLE-DRIVEN BIT INVERTER -; -PRPENABLE .EQU FALSE ; TRUE FOR PROPIO SUPPORT -PRPIOB .EQU $A8 ; PORT IO ADDRESS BASE -PRPSDENABLE .EQU TRUE ; TRUE FOR PROPIO SD SUPPORT -PRPSDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PRPSDENABLE = TRUE) -PRPCONENABLE .EQU TRUE ; TRUE FOR PROPIO CONSOLE SUPPORT (PS/2 KBD & VGA VIDEO) -; -PPPENABLE .EQU FALSE ; TRUE FOR PARPORTPROP SUPPORT -PPPSDENABLE .EQU TRUE ; TRUE FOR PARPORTPROP SD SUPPORT -PPPSDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPPENABLE = TRUE) -PPPCONENABLE .EQU TRUE ; TRUE FOR PROPIO CONSOLE SUPPORT (PS/2 KBD & VGA VIDEO) -; -HDSKENABLE .EQU FALSE ; TRUE FOR SIMH HDSK SUPPORT -HDSKTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF IDEENABLE = TRUE) -; -PPKTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPKENABLE = TRUE) -KBDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF KBDENABLE = TRUE) -; -TERMENABLE .EQU FALSE ; TERM PSEUDO DEVICE, WILL BE ENABLED IF A VDA IS ENABLED -ANSITRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF ANSIENABLE = TRUE) -; -BOOTTYPE .EQU BT_MENU ; BT_MENU (WAIT FOR KEYPRESS), BT_AUTO (BOOT_DEFAULT AFTER BOOT_TIMEOUT SECS) -BOOT_TIMEOUT .EQU 20 ; APPROX TIMEOUT IN SECONDS FOR AUTOBOOT, 0 FOR IMMEDIATE -BOOT_DEFAULT .EQU 'Z' ; SELECTION TO INVOKE AT TIMEOUT -; -; 18.432MHz OSC @ FULL SPEED -; -Z180_CLKDIV .EQU 1 ; 0=OSC/2, 1=OSC, 2=OSC*2 -Z180_MEMWAIT .EQU 0 ; MEMORY WAIT STATES TO INSERT (0-3) -Z180_IOWAIT .EQU 1 ; IO WAIT STATES TO INSERT (0-3) -; -; 18.432MHz OSC @ DOUBLE SPEED -; -;Z180_CLKDIV .EQU 2 ; 0=OSC/2, 1=OSC, 2=OSC*2 -;Z180_MEMWAIT .EQU 1 ; MEMORY WAIT STATES TO INSERT (0-3) -;Z180_IOWAIT .EQU 1 ; IO WAIT STATES TO INSERT (0-3) +; THIS FILE CONTAINS THE FULL SET OF DEFAULT CONFIGURATION SETTINGS FOR THE PLATFORM +; INDICATED ABOVE. THIS FILE SHOULD *NOT* NORMALLY BE CHANGED. INSTEAD, YOU SHOULD +; OVERRIDE ANY SETTINGS YOU WANT USING A CONFIGURATION FILE IN THE CONFIG DIRECTORY +; UNDER THIS DIRECTORY. +; +; THIS FILE CAN BE CONSIDERED A REFERENCE THAT LISTS ALL POSSIBLE CONFIGURATION SETTINGS +; FOR THE PLATFORM. +; +#DEFINE PLATFORM_NAME "MARK IV" +; +PLATFORM .EQU PLT_MK4 ; PLT_[SBC|ZETA|ZETA2|N8|MK4|UNA|RCZ80|RCZ180|EZZ80|SCZ180|DYNO] +CPUFAM .EQU CPU_Z180 ; CPU FAMILY: CPU_[Z80|Z180] +BIOS .EQU BIOS_WBW ; HARDWARE BIOS: BIOS_[WBW|UNA] +BATCOND .EQU FALSE ; ENABLE LOW BATTERY WARNING MESSAGE +HBIOS_MUTEX .EQU FALSE ; ENABLE REENTRANT CALLS TO HBIOS (ADDS OVERHEAD) +USELZSA2 .EQU TRUE ; ENABLE FONT COMPRESSION +; +BOOT_TIMEOUT .EQU 0 ; AUTO BOOT TIMEOUT IN SECONDS, 0 TO DISABLE +; +CPUOSC .EQU 18432000 ; CPU OSC FREQ IN MHZ +INTMODE .EQU 2 ; INTERRUPTS: 0=NONE, 1=MODE 1, 2=MODE 2 +DEFSERCFG .EQU SER_38400_8N1 ; DEFAULT SERIAL LINE CONFIG (SEE STD.ASM) +; +RAMSIZE .EQU 512 ; SIZE OF RAM IN KB (MUST MATCH YOUR HARDWARE!!!) +MEMMGR .EQU MM_Z180 ; MEMORY MANAGER: MM_[SBC|Z2|N8|Z180] +RAMBIAS .EQU 512 ; OFFSET OF START OF RAM IN PHYSICAL ADDRESS SPACE +; +Z180_BASE .EQU $40 ; Z180: I/O BASE ADDRESS FOR INTERNAL REGISTERS +Z180_CLKDIV .EQU 1 ; Z180: CHK DIV: 0=OSC/2, 1=OSC, 2=OSC*2 +Z180_MEMWAIT .EQU 0 ; Z180: MEMORY WAIT STATES (0-3) +Z180_IOWAIT .EQU 1 ; Z180: I/O WAIT STATES TO ADD ABOVE 1 W/S BUILT-IN (0-3) +; +MK4_IDE .EQU $80 ; MK4: IDE REGISTERS BASE ADR +MK4_XAR .EQU $88 ; MK4: EXTERNAL ADDRESS REGISTER (XAR) ADR +MK4_SD .EQU $89 ; MK4: SD CARD CONTROL REGISTER ADR +MK4_RTC .EQU $8A ; MK4: RTC LATCH REGISTER ADR +; +RTCIO .EQU MK4_RTC ; RTC LATCH REGISTER ADR +; +KIOENABLE .EQU FALSE ; ENABLE ZILOG KIO SUPPORT +KIOBASE .EQU $80 ; KIO BASE I/O ADDRESS +; +CTCENABLE .EQU FALSE ; ENABLE ZILOG CTC SUPPORT +; +DIAGENABLE .EQU FALSE ; ENABLES OUTPUT TO 8 BIT LED DIAGNOSTIC PORT +DIAGPORT .EQU $00 ; DIAGNOSTIC PORT ADDRESS +DIAGDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON DIAGNOSTIC LEDS +; +LEDENABLE .EQU FALSE ; ENABLES STATUS LED (SINGLE LED) +LEDPORT .EQU $0E ; STATUS LED PORT ADDRESS +LEDDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON STATUS LED +; +DSKYENABLE .EQU FALSE ; ENABLES DSKY (DO NOT COMBINE WITH PPIDE) +; +CRTACT .EQU FALSE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP +VDAEMU .EQU EMUTYP_ANSI ; VDA EMULATION: EMUTYP_[TTY|ANSI] +ANSITRACE .EQU 1 ; ANSI DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPKTRACE .EQU 1 ; PPK DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +KBDTRACE .EQU 1 ; KBD DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPKKBLOUT .EQU KBD_US ; PPK KEYBOARD LANGUAGE: KBD_[US|DE] +KBDKBLOUT .EQU KBD_US ; KBD KEYBOARD LANGUAGE: KBD_[US|DE] +; +DSRTCENABLE .EQU TRUE ; DSRTC: ENABLE DS-1302 CLOCK DRIVER (DSRTC.ASM) +DSRTCMODE .EQU DSRTCMODE_STD ; DSRTC: OPERATING MODE: DSRTC_[STD|MFPIC] +DSRTCCHG .EQU FALSE ; DSRTC: FORCE BATTERY CHARGE ON (USE WITH CAUTION!!!) +; +BQRTCENABLE .EQU FALSE ; BQRTC: ENABLE BQ4845 CLOCK DRIVER (BQRTC.ASM) +BQRTC_BASE .EQU $50 ; BQRTC: I/O BASE ADDRESS +; +INTRTCENABLE .EQU FALSE ; ENABLE PERIODIC INTERRUPT CLOCK DRIVER (INTRTC.ASM) +; +HTIMENABLE .EQU FALSE ; ENABLE SIMH TIMER SUPPORT +SIMRTCENABLE .EQU FALSE ; ENABLE SIMH CLOCK DRIVER (SIMRTC.ASM) +; +UARTENABLE .EQU TRUE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +UARTOSC .EQU 1843200 ; UART: OSC FREQUENCY IN MHZ +UARTCFG .EQU DEFSERCFG ; UART: LINE CONFIG FOR UART PORTS +UARTCASSPD .EQU SER_300_8N1 ; UART: ECB CASSETTE UART DEFAULT SPEED +UARTSBC .EQU FALSE ; UART: AUTO-DETECT SBC/ZETA ONBOARD UART +UARTCAS .EQU TRUE ; UART: AUTO-DETECT ECB CASSETTE UART +UARTMFP .EQU FALSE ; UART: AUTO-DETECT MF/PIC UART +UART4 .EQU TRUE ; UART: AUTO-DETECT 4UART UART +UARTRC .EQU FALSE ; UART: AUTO-DETECT RC UART +; +ASCIENABLE .EQU TRUE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +ASCI0CFG .EQU DEFSERCFG ; ASCI 0: SERIAL LINE CONFIG +ASCI1CFG .EQU DEFSERCFG ; ASCI 1: SERIAL LINE CONFIG +; +ACIAENABLE .EQU FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +; +SIOENABLE .EQU FALSE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +; +XIOCFG .EQU DEFSERCFG ; XIO: SERIAL LINE CONFIG +; +VDUENABLE .EQU FALSE ; VDU: ENABLE VDU VIDEO/KBD DRIVER (VDU.ASM) +VDUSIZ .EQU V80X25 ; VDU: DISPLAY FORMAT [V80X24|V80X25|V80X30] +CVDUENABLE .EQU FALSE ; CVDU: ENABLE CVDU VIDEO/KBD DRIVER (CVDU.ASM) +NECENABLE .EQU FALSE ; NEC: ENABLE NEC UPD7220 VIDEO/KBD DRIVER (NEC.ASM) +TMSENABLE .EQU FALSE ; TMS: ENABLE TMS9918 VIDEO/KBD DRIVER (TMS.ASM) +TMSMODE .EQU TMSMODE_SCG ; TMS: DRIVER MODE: TMSMODE_[SCG/N8] +VGAENABLE .EQU FALSE ; VGA: ENABLE VGA VIDEO/KBD DRIVER (VGA.ASM) +VGASIZ .EQU V80X25 ; VGA: DISPLAY FORMAT [V80X25|V80X30|V80X43] +; +SPKENABLE .EQU FALSE ; SPK: ENABLE RTC LATCH IOBIT SOUND DRIVER (SPK.ASM) +; +AYENABLE .EQU FALSE ; AY: ENABLE AY PSG SOUND DRIVER +AYMODE .EQU AYMODE_SCG ; AY: DRIVER MODE: AYMODE_[SCG/N8/RCZ80/RCZ180] +; +MDENABLE .EQU TRUE ; MD: ENABLE MEMORY (ROM/RAM) DISK DRIVER (MD.ASM) +MDTRACE .EQU 1 ; MD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +FDENABLE .EQU FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .EQU FDMODE_N8 ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3|DYNO] +FDTRACE .EQU 1 ; FD: TRACE LEVEL (0=NO,1=FATAL,2=ERRORS,3=ALL) +FDMEDIA .EQU FDM144 ; FD: DEFAULT MEDIA FORMAT FDM[720|144|360|120|111] +FDMEDIAALT .EQU FDM720 ; FD: ALTERNATE MEDIA FORMAT FDM[720|144|360|120|111] +FDMAUTO .EQU TRUE ; FD: AUTO SELECT DEFAULT/ALTERNATE MEDIA FORMATS +; +RFENABLE .EQU FALSE ; RF: ENABLE RAM FLOPPY DRIVER +RFCNT .EQU 1 ; RF: NUMBER OF RAM FLOPPY UNITS (1-4) +; +IDEENABLE .EQU TRUE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +IDETRACE .EQU 1 ; IDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +IDECNT .EQU 1 ; IDE: NUMBER OF IDE INTERFACES TO DETECT (1-3), 2 DRIVES EACH +IDE0MODE .EQU IDEMODE_MK4 ; IDE 0: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE0BASE .EQU $80 ; IDE 0: IO BASE ADDRESS +IDE0DATLO .EQU $00 ; IDE 0: DATA LO PORT FOR 16-BIT I/O +IDE0DATHI .EQU $00 ; IDE 0: DATA HI PORT FOR 16-BIT I/O +IDE0A8BIT .EQU TRUE ; IDE 0A (MASTER): 8 BIT XFER +IDE0B8BIT .EQU TRUE ; IDE 0B (MASTER): 8 BIT XFER +IDE1MODE .EQU IDEMODE_DIDE ; IDE 1: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE1BASE .EQU $20 ; IDE 1: IO BASE ADDRESS +IDE1DATLO .EQU $28 ; IDE 1: DATA LO PORT FOR 16-BIT I/O +IDE1DATHI .EQU $28 ; IDE 1: DATA HI PORT FOR 16-BIT I/O +IDE1A8BIT .EQU FALSE ; IDE 1A (MASTER): 8 BIT XFER +IDE1B8BIT .EQU FALSE ; IDE 1B (MASTER): 8 BIT XFER +IDE2MODE .EQU IDEMODE_DIDE ; IDE 2: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE2BASE .EQU $30 ; IDE 2: IO BASE ADDRESS +IDE2DATLO .EQU $38 ; IDE 2: DATA LO PORT FOR 16-BIT I/O +IDE2DATHI .EQU $38 ; IDE 2: DATA HI PORT FOR 16-BIT I/O +IDE2A8BIT .EQU FALSE ; IDE 2A (MASTER): 8 BIT XFER +IDE2B8BIT .EQU FALSE ; IDE 2B (MASTER): 8 BIT XFER +; +PPIDEENABLE .EQU FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) +PPIDETRACE .EQU 1 ; PPIDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPIDECNT .EQU 2 ; PPIDE: NUMBER OF PPI CHIPS TO DETECT (1-3), 2 DRIVES PER CHIP +PPIDE0BASE .EQU $44 ; PPIDE 0: PPI REGISTERS BASE ADR +PPIDE0A8BIT .EQU FALSE ; PPIDE 0A (MASTER): 8 BIT XFER +PPIDE0B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +PPIDE1BASE .EQU $20 ; PPIDE 1: PPI REGISTERS BASE ADR +PPIDE1A8BIT .EQU FALSE ; PPIDE 1A (MASTER): 8 BIT XFER +PPIDE1B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +PPIDE2BASE .EQU $00 ; PPIDE 2: PPI REGISTERS BASE ADR +PPIDE2A8BIT .EQU FALSE ; PPIDE 2A (MASTER): 8 BIT XFER +PPIDE2B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +; +SDENABLE .EQU TRUE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDMODE .EQU SDMODE_MK4 ; SD: DRIVER MODE: SDMODE_[JUHA|N8|CSIO|PPI|UART|DSD|MK4|SC|MT] +SDCNT .EQU 1 ; SD: NUMBER OF SD CARD DEVICES (1-2), FOR DSD & SC ONLY +SDTRACE .EQU 1 ; SD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +SDCSIOFAST .EQU TRUE ; SD: ENABLE TABLE-DRIVEN BIT INVERTER IN CSIO MODE +; +PRPENABLE .EQU FALSE ; PRP: ENABLE ECB PROPELLER IO BOARD DRIVER (PRP.ASM) +PRPSDENABLE .EQU TRUE ; PRP: ENABLE PROPIO DRIVER SD CARD SUPPORT +PRPSDTRACE .EQU 1 ; PRP: SD CARD TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PRPCONENABLE .EQU TRUE ; PRP: ENABLE PROPIO DRIVER VIDEO/KBD SUPPORT +; +PPPENABLE .EQU FALSE ; PPP: ENABLE ZETA PARALLEL PORT PROPELLER BOARD DRIVER (PPP.ASM) +PPPSDENABLE .EQU TRUE ; PPP: ENABLE PPP DRIVER SD CARD SUPPORT +PPPSDTRACE .EQU 1 ; PPP: SD CARD TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPPCONENABLE .EQU TRUE ; PPP: ENABLE PPP DRIVER VIDEO/KBD SUPPORT +; +HDSKENABLE .EQU FALSE ; HDSK: ENABLE SIMH HDSK DISK DRIVER (HDSK.ASM) +; +PIO_4P .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB 4P BOARD +PIO4BASE .EQU $90 ; PIO: PIO REGISTERS BASE ADR FOR ECB 4P BOARD +PIO_ZP .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB ZILOG PERIPHERALS BOARD (PIO.ASM) +PIOZBASE .EQU $88 ; PIO: PIO REGISTERS BASE ADR FOR ECB ZP BOARD +PPI_SBC .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR 8255 CHIP +; +UFENABLE .EQU FALSE ; UF: ENABLE ECB USB FIFO DRIVER (UF.ASM) +UFBASE .EQU $0C ; UF: REGISTERS BASE ADR diff --git a/Source/HBIOS/cfg_n8.asm b/Source/HBIOS/cfg_n8.asm index 983b7d1b..cd18db22 100644 --- a/Source/HBIOS/cfg_n8.asm +++ b/Source/HBIOS/cfg_n8.asm @@ -3,96 +3,185 @@ ; ROMWBW 2.X CONFIGURATION FOR N8 ;================================================================================================== ; -; BUILD CONFIGURATION OPTIONS -; -CPUOSC .EQU 18432000 ; CPU OSC FREQ -MEMMGR .EQU MM_N8 ; MM_NONE, MM_SBC, MM_Z2, MM_N8, MM_Z180 -RAMSIZE .EQU 512 ; SIZE OF RAM IN KB, MUST MATCH YOUR HARDWARE!!! -DEFSERCFG .EQU SER_38400_8N1 ; DEFAULT SERIAL LINE CONFIG (SHOULD MATCH ABOVE) -INTMODE .EQU 2 ; 0=NONE, 1=INT MODE 1, 2=INT MODE 2 -; -CRTACT .EQU FALSE ; CRT ACTIVATION AT STARTUP -VDAEMU .EQU EMUTYP_ANSI ; DEFAULT VDA EMULATION (EMUTYP_TTY, EMUTYP_ANSI, ...) -; -DSKYENABLE .EQU FALSE ; TRUE FOR DSKY SUPPORT (DO NOT COMBINE WITH PPIDE) -; -HTIMENABLE .EQU FALSE ; TRUE FOR SIMH TIMER SUPPORT -SIMRTCENABLE .EQU FALSE ; SIMH CLOCK DRIVER -DSRTCENABLE .EQU TRUE ; DS-1302 CLOCK DRIVER -DSRTCMODE .EQU DSRTCMODE_STD ; DSRTCMODE_STD, DSRTCMODE_MFPIC -DSRTCCHG .EQU FALSE ; DS-1302 CONFIGURE CHARGE ON (TRUE) OR OFF (FALSE) -; -ASCIENABLE .EQU TRUE ; TRUE FOR Z180 ASCI SUPPORT -UARTENABLE .EQU TRUE ; TRUE FOR UART SUPPORT (ALMOST ALWAYS WANT THIS TO BE TRUE) -UARTOSC .EQU 1843200 ; UART OSC FREQUENCY -SIOENABLE .EQU FALSE ; TRUE FOR ZILOG SIO/2 SUPPORT -SIOMODE .EQU SIOMODE_RC ; SIOMODE_RC, SIOMODE_SMB -ACIAENABLE .EQU FALSE ; TRUE FOR MOTOROLA 6850 ACIA SUPPORT -; -VDUENABLE .EQU FALSE ; TRUE FOR VDU BOARD SUPPORT -CVDUENABLE .EQU FALSE ; TRUE FOR CVDU BOARD SUPPORT -NECENABLE .EQU FALSE ; TRUE FOR uPD7220 BOARD SUPPORT -TMSENABLE .EQU TRUE ; TRUE FOR N8 (TMS9918) VIDEO/KBD SUPPORT -VGAENABLE .EQU FALSE ; TRUE FOR VGA VIDEO/KBD SUPPORT -; -SPKENABLE .EQU FALSE ; TRUE FOR RTC LATCH IOBIT SOUND -AYENABLE .EQU TRUE ; TRUE FOR AY PSG SOUND -; -MDENABLE .EQU TRUE ; TRUE FOR ROM/RAM DISK SUPPORT (ALMOST ALWAYS WANT THIS ENABLED) -MDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF MDENABLE = TRUE) -; -FDENABLE .EQU TRUE ; TRUE FOR FLOPPY SUPPORT -FDMODE .EQU FDMODE_N8 ; FDMODE_DIO, FDMODE_ZETA, FDMODE_DIDE, FDMODE_N8, FDMODE_DIO3 -FDTRACE .EQU 1 ; 0=SILENT, 1=FATAL ERRORS, 2=ALL ERRORS, 3=EVERYTHING (ONLY RELEVANT IF FDENABLE = TRUE) -FDMEDIA .EQU FDM144 ; FDM720, FDM144, FDM360, FDM120 (ONLY RELEVANT IF FDENABLE = TRUE) -FDMEDIAALT .EQU FDM720 ; ALTERNATE MEDIA TO TRY, SAME CHOICES AS ABOVE (ONLY RELEVANT IF FDMAUTO = TRUE) -FDMAUTO .EQU TRUE ; SELECT BETWEEN MEDIA OPTS ABOVE AUTOMATICALLY -; -RFENABLE .EQU FALSE ; TRUE FOR RAM FLOPPY SUPPORT -RFCNT .EQU 1 ; NUMBER OF RAM FLOPPY UNITS -; -IDEENABLE .EQU FALSE ; TRUE FOR IDE SUPPORT -IDEMODE .EQU IDEMODE_DIO ; IDEMODE_DIO, IDEMODE_DIDE -IDECNT .EQU 1 ; NUMBER OF IDE UNITS -IDETRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF IDEENABLE = TRUE) -IDE8BIT .EQU FALSE ; USE IDE 8BIT TRANSFERS (PROBABLY ONLY WORKS FOR CF CARDS!) -; -PPIDEENABLE .EQU FALSE ; TRUE FOR PPIDE SUPPORT (DO NOT COMBINE WITH DSKYENABLE) -PPIDEMODE .EQU PPIDEMODE_N8 ; PPIDEMODE_SBC, PPPIDEMODE_DIO3, PPIDEMODE_MFP, PPIDEMODE_N8, PPIDEMODE_RC -PPIDETRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPIDEENABLE = TRUE) -PPIDE8BIT .EQU FALSE ; USE IDE 8BIT TRANSFERS (PROBABLY ONLY WORKS FOR CF CARDS!) -; -SDENABLE .EQU TRUE ; TRUE FOR SD SUPPORT -SDMODE .EQU SDMODE_CSIO ; SDMODE_JUHA, SDMODE_CSIO, SDMODE_UART, SDMODE_PPI, SDMODE_DSD -SDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF IDEENABLE = TRUE) -SDCSIOFAST .EQU FALSE ; TABLE-DRIVEN BIT INVERTER -; -PRPENABLE .EQU FALSE ; TRUE FOR PROPIO SUPPORT -PRPSDENABLE .EQU TRUE ; TRUE FOR PROPIO SD SUPPORT -PRPSDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PRPSDENABLE = TRUE) -PRPCONENABLE .EQU TRUE ; TRUE FOR PROPIO CONSOLE SUPPORT (PS/2 KBD & VGA VIDEO) -; -PPPENABLE .EQU FALSE ; TRUE FOR PARPORTPROP SUPPORT -PPPSDENABLE .EQU TRUE ; TRUE FOR PARPORTPROP SD SUPPORT -PPPSDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPPENABLE = TRUE) -PPPCONENABLE .EQU TRUE ; TRUE FOR PROPIO CONSOLE SUPPORT (PS/2 KBD & VGA VIDEO) -; -HDSKENABLE .EQU FALSE ; TRUE FOR SIMH HDSK SUPPORT -; -PPKTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPKENABLE = TRUE) -KBDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF KBDENABLE = TRUE) -; -TERMENABLE .EQU FALSE ; TERM PSEUDO DEVICE, WILL BE ENABLED IF A VDA IS ENABLED -ANSITRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF ANSIENABLE = TRUE) -; -BOOTTYPE .EQU BT_MENU ; BT_MENU (WAIT FOR KEYPRESS), BT_AUTO (BOOT_DEFAULT AFTER BOOT_TIMEOUT SECS) -BOOT_TIMEOUT .EQU 20 ; APPROX TIMEOUT IN SECONDS FOR AUTOBOOT, 0 FOR IMMEDIATE -BOOT_DEFAULT .EQU 'Z' ; SELECTION TO INVOKE AT TIMEOUT -; -Z180_CLKDIV .EQU 1 ; 0=OSC/2, 1=OSC, 2=OSC*2 -Z180_MEMWAIT .EQU 1 ; MEMORY WAIT STATES TO INSERT (0-3) -Z180_IOWAIT .EQU 3 ; IO WAIT STATES TO INSERT (0-3) -; -PIO_4P .EQU FALSE ; TRUE FOR ECB-4PIO PIO SUPPORT -PIO_ZP .EQU FALSE ; TRUE FOR ECB-ZILOG PERIPHERALS BOARD -PPI_SBC .EQU FALSE ; TRUE FOR SBC V2 8255 (IF NOT BEING USED FOR PPIDE) +; THIS FILE CONTAINS THE FULL SET OF DEFAULT CONFIGURATION SETTINGS FOR THE PLATFORM +; INDICATED ABOVE. THIS FILE SHOULD *NOT* NORMALLY BE CHANGED. INSTEAD, YOU SHOULD +; OVERRIDE ANY SETTINGS YOU WANT USING A CONFIGURATION FILE IN THE CONFIG DIRECTORY +; UNDER THIS DIRECTORY. +; +; THIS FILE CAN BE CONSIDERED A REFERENCE THAT LISTS ALL POSSIBLE CONFIGURATION SETTINGS +; FOR THE PLATFORM. +; +#DEFINE PLATFORM_NAME "N8" +; +PLATFORM .EQU PLT_N8 ; PLT_[SBC|ZETA|ZETA2|N8|MK4|UNA|RCZ80|RCZ180|EZZ80|SCZ180|DYNO] +CPUFAM .EQU CPU_Z180 ; CPU FAMILY: CPU_[Z80|Z180] +BIOS .EQU BIOS_WBW ; HARDWARE BIOS: BIOS_[WBW|UNA] +BATCOND .EQU FALSE ; ENABLE LOW BATTERY WARNING MESSAGE +HBIOS_MUTEX .EQU FALSE ; ENABLE REENTRANT CALLS TO HBIOS (ADDS OVERHEAD) +USELZSA2 .EQU TRUE ; ENABLE FONT COMPRESSION +; +BOOT_TIMEOUT .EQU 0 ; AUTO BOOT TIMEOUT IN SECONDS, 0 TO DISABLE +; +CPUOSC .EQU 18432000 ; CPU OSC FREQ IN MHZ +INTMODE .EQU 2 ; INTERRUPTS: 0=NONE, 1=MODE 1, 2=MODE 2 +DEFSERCFG .EQU SER_38400_8N1 ; DEFAULT SERIAL LINE CONFIG (SEE STD.ASM) +; +RAMSIZE .EQU 512 ; SIZE OF RAM IN KB (MUST MATCH YOUR HARDWARE!!!) +MEMMGR .EQU MM_N8 ; MEMORY MANAGER: MM_[SBC|Z2|N8|Z180] +RAMBIAS .EQU 0 ; OFFSET OF START OF RAM IN PHYSICAL ADDRESS SPACE +; +Z180_BASE .EQU $40 ; Z180: I/O BASE ADDRESS FOR INTERNAL REGISTERS +Z180_CLKDIV .EQU 1 ; Z180: CHK DIV: 0=OSC/2, 1=OSC, 2=OSC*2 +Z180_MEMWAIT .EQU 0 ; Z180: MEMORY WAIT STATES (0-3) +Z180_IOWAIT .EQU 1 ; Z180: I/O WAIT STATES TO ADD ABOVE 1 W/S BUILT-IN (0-3) +; +N8_PPI0 .EQU $80 ; N8: FIRST PARALLEL PORT REGISTERS BASE ADR +N8_PPI1 .EQU $84 ; N8: SECOND PARALLEL PORT REGISTERS BASE ADR +N8_RTC .EQU $88 ; N8: RTC LATCH REGISTER ADR +N8_ACR .EQU $94 ; N8: AUXILLARY CONTROL REGISTER (ACR) ADR +N8_RMAP .EQU $96 ; N8: ROM PAGE REGISTER ADR +N8_DEFACR .EQU $1B ; N8: AUX CTL REGISTER DEFAULT VALUE (QUIESCIENT STATE) +; +RTCIO .EQU N8_RTC ; RTC LATCH REGISTER ADR +PPIBASE .EQU N8_PPI0 ; PRIMARY PARALLEL PORT REGISTERS BASE ADR +; +KIOENABLE .EQU FALSE ; ENABLE ZILOG KIO SUPPORT +KIOBASE .EQU $80 ; KIO BASE I/O ADDRESS +; +CTCENABLE .EQU FALSE ; ENABLE ZILOG CTC SUPPORT +; +DIAGENABLE .EQU FALSE ; ENABLES OUTPUT TO 8 BIT LED DIAGNOSTIC PORT +DIAGPORT .EQU $00 ; DIAGNOSTIC PORT ADDRESS +DIAGDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON DIAGNOSTIC LEDS +; +LEDENABLE .EQU FALSE ; ENABLES STATUS LED (SINGLE LED) +LEDPORT .EQU $0E ; STATUS LED PORT ADDRESS +LEDDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON STATUS LED +; +DSKYENABLE .EQU FALSE ; ENABLES DSKY (DO NOT COMBINE WITH PPIDE) +; +CRTACT .EQU FALSE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP +VDAEMU .EQU EMUTYP_ANSI ; VDA EMULATION: EMUTYP_[TTY|ANSI] +ANSITRACE .EQU 1 ; ANSI DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPKTRACE .EQU 1 ; PPK DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +KBDTRACE .EQU 1 ; KBD DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPKKBLOUT .EQU KBD_US ; PPK KEYBOARD LANGUAGE: KBD_[US|DE] +KBDKBLOUT .EQU KBD_US ; KBD KEYBOARD LANGUAGE: KBD_[US|DE] +; +DSRTCENABLE .EQU TRUE ; DSRTC: ENABLE DS-1302 CLOCK DRIVER (DSRTC.ASM) +DSRTCMODE .EQU DSRTCMODE_STD ; DSRTC: OPERATING MODE: DSRTC_[STD|MFPIC] +DSRTCCHG .EQU FALSE ; DSRTC: FORCE BATTERY CHARGE ON (USE WITH CAUTION!!!) +; +BQRTCENABLE .EQU FALSE ; BQRTC: ENABLE BQ4845 CLOCK DRIVER (BQRTC.ASM) +BQRTC_BASE .EQU $50 ; BQRTC: I/O BASE ADDRESS +; +INTRTCENABLE .EQU FALSE ; ENABLE PERIODIC INTERRUPT CLOCK DRIVER (INTRTC.ASM) +; +HTIMENABLE .EQU FALSE ; ENABLE SIMH TIMER SUPPORT +SIMRTCENABLE .EQU FALSE ; ENABLE SIMH CLOCK DRIVER (SIMRTC.ASM) +; +UARTENABLE .EQU TRUE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +UARTOSC .EQU 1843200 ; UART: OSC FREQUENCY IN MHZ +UARTCFG .EQU DEFSERCFG ; UART: LINE CONFIG FOR UART PORTS +UARTCASSPD .EQU SER_300_8N1 ; UART: ECB CASSETTE UART DEFAULT SPEED +UARTSBC .EQU FALSE ; UART: AUTO-DETECT SBC/ZETA ONBOARD UART +UARTCAS .EQU TRUE ; UART: AUTO-DETECT ECB CASSETTE UART +UARTMFP .EQU FALSE ; UART: AUTO-DETECT MF/PIC UART +UART4 .EQU TRUE ; UART: AUTO-DETECT 4UART UART +UARTRC .EQU FALSE ; UART: AUTO-DETECT RC UART +; +ASCIENABLE .EQU TRUE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +ASCI0CFG .EQU DEFSERCFG ; ASCI 0: SERIAL LINE CONFIG +ASCI1CFG .EQU DEFSERCFG ; ASCI 1: SERIAL LINE CONFIG +; +ACIAENABLE .EQU FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +; +SIOENABLE .EQU FALSE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +; +XIOCFG .EQU DEFSERCFG ; XIO: SERIAL LINE CONFIG +; +VDUENABLE .EQU FALSE ; VDU: ENABLE VDU VIDEO/KBD DRIVER (VDU.ASM) +VDUSIZ .EQU V80X25 ; VDU: DISPLAY FORMAT [V80X24|V80X25|V80X30] +CVDUENABLE .EQU FALSE ; CVDU: ENABLE CVDU VIDEO/KBD DRIVER (CVDU.ASM) +NECENABLE .EQU FALSE ; NEC: ENABLE NEC UPD7220 VIDEO/KBD DRIVER (NEC.ASM) +TMSENABLE .EQU TRUE ; TMS: ENABLE TMS9918 VIDEO/KBD DRIVER (TMS.ASM) +TMSMODE .EQU TMSMODE_N8 ; TMS: DRIVER MODE: TMSMODE_[SCG/N8] +VGAENABLE .EQU FALSE ; VGA: ENABLE VGA VIDEO/KBD DRIVER (VGA.ASM) +VGASIZ .EQU V80X25 ; VGA: DISPLAY FORMAT [V80X25|V80X30|V80X43] +; +SPKENABLE .EQU FALSE ; SPK: ENABLE RTC LATCH IOBIT SOUND DRIVER (SPK.ASM) +; +AYENABLE .EQU TRUE ; AY: ENABLE AY PSG SOUND DRIVER +AYMODE .EQU AYMODE_N8 ; AY: DRIVER MODE: AYMODE_[SCG/N8/RCZ80/RCZ180] +; +MDENABLE .EQU TRUE ; MD: ENABLE MEMORY (ROM/RAM) DISK DRIVER (MD.ASM) +MDTRACE .EQU 1 ; MD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +FDENABLE .EQU TRUE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .EQU FDMODE_N8 ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3|DYNO] +FDTRACE .EQU 1 ; FD: TRACE LEVEL (0=NO,1=FATAL,2=ERRORS,3=ALL) +FDMEDIA .EQU FDM144 ; FD: DEFAULT MEDIA FORMAT FDM[720|144|360|120|111] +FDMEDIAALT .EQU FDM720 ; FD: ALTERNATE MEDIA FORMAT FDM[720|144|360|120|111] +FDMAUTO .EQU TRUE ; FD: AUTO SELECT DEFAULT/ALTERNATE MEDIA FORMATS +; +RFENABLE .EQU FALSE ; RF: ENABLE RAM FLOPPY DRIVER +RFCNT .EQU 1 ; RF: NUMBER OF RAM FLOPPY UNITS (1-4) +; +IDEENABLE .EQU FALSE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +IDETRACE .EQU 1 ; IDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +IDECNT .EQU 1 ; IDE: NUMBER OF IDE INTERFACES TO DETECT (1-3), 2 DRIVES EACH +IDE0MODE .EQU IDEMODE_DIO ; IDE 0: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE0BASE .EQU $20 ; IDE 0: IO BASE ADDRESS +IDE0DATLO .EQU $20 ; IDE 0: DATA LO PORT FOR 16-BIT I/O +IDE0DATHI .EQU $28 ; IDE 0: DATA HI PORT FOR 16-BIT I/O +IDE0A8BIT .EQU FALSE ; IDE 0A (MASTER): 8 BIT XFER +IDE0B8BIT .EQU FALSE ; IDE 0B (MASTER): 8 BIT XFER +IDE1MODE .EQU IDEMODE_NONE ; IDE 1: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE1BASE .EQU $00 ; IDE 1: IO BASE ADDRESS +IDE1DATLO .EQU $00 ; IDE 1: DATA LO PORT FOR 16-BIT I/O +IDE1DATHI .EQU $00 ; IDE 1: DATA HI PORT FOR 16-BIT I/O +IDE1A8BIT .EQU TRUE ; IDE 1A (MASTER): 8 BIT XFER +IDE1B8BIT .EQU TRUE ; IDE 1B (MASTER): 8 BIT XFER +IDE2MODE .EQU IDEMODE_NONE ; IDE 2: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE2BASE .EQU $00 ; IDE 2: IO BASE ADDRESS +IDE2DATLO .EQU $00 ; IDE 2: DATA LO PORT FOR 16-BIT I/O +IDE2DATHI .EQU $00 ; IDE 2: DATA HI PORT FOR 16-BIT I/O +IDE2A8BIT .EQU TRUE ; IDE 2A (MASTER): 8 BIT XFER +IDE2B8BIT .EQU TRUE ; IDE 2B (MASTER): 8 BIT XFER +; +PPIDEENABLE .EQU FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) +PPIDETRACE .EQU 1 ; PPIDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPIDECNT .EQU 1 ; PPIDE: NUMBER OF PPI CHIPS TO DETECT (1-3), 2 DRIVES PER CHIP +PPIDE0BASE .EQU $80 ; PPIDE 0: PPI REGISTERS BASE ADR +PPIDE0A8BIT .EQU FALSE ; PPIDE 0A (MASTER): 8 BIT XFER +PPIDE0B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +PPIDE1BASE .EQU $00 ; PPIDE 1: PPI REGISTERS BASE ADR +PPIDE1A8BIT .EQU FALSE ; PPIDE 1A (MASTER): 8 BIT XFER +PPIDE1B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +PPIDE2BASE .EQU $00 ; PPIDE 2: PPI REGISTERS BASE ADR +PPIDE2A8BIT .EQU FALSE ; PPIDE 2A (MASTER): 8 BIT XFER +PPIDE2B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +; +SDENABLE .EQU TRUE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDMODE .EQU SDMODE_CSIO ; SD: DRIVER MODE: SDMODE_[JUHA|N8|CSIO|PPI|UART|DSD|MK4|SC|MT] +SDCNT .EQU 1 ; SD: NUMBER OF SD CARD DEVICES (1-2), FOR DSD & SC ONLY +SDTRACE .EQU 1 ; SD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +SDCSIOFAST .EQU FALSE ; SD: ENABLE TABLE-DRIVEN BIT INVERTER IN CSIO MODE +; +PRPENABLE .EQU FALSE ; PRP: ENABLE ECB PROPELLER IO BOARD DRIVER (PRP.ASM) +PRPSDENABLE .EQU TRUE ; PRP: ENABLE PROPIO DRIVER SD CARD SUPPORT +PRPSDTRACE .EQU 1 ; PRP: SD CARD TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PRPCONENABLE .EQU TRUE ; PRP: ENABLE PROPIO DRIVER VIDEO/KBD SUPPORT +; +PPPENABLE .EQU FALSE ; PPP: ENABLE ZETA PARALLEL PORT PROPELLER BOARD DRIVER (PPP.ASM) +; +HDSKENABLE .EQU FALSE ; HDSK: ENABLE SIMH HDSK DISK DRIVER (HDSK.ASM) +; +PIO_4P .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB 4P BOARD +PIO4BASE .EQU $90 ; PIO: PIO REGISTERS BASE ADR FOR ECB 4P BOARD +PIO_ZP .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB ZILOG PERIPHERALS BOARD (PIO.ASM) +PIOZBASE .EQU $88 ; PIO: PIO REGISTERS BASE ADR FOR ECB ZP BOARD +PPI_SBC .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR 8255 CHIP +; +UFENABLE .EQU FALSE ; UF: ENABLE ECB USB FIFO DRIVER (UF.ASM) +FIFO_BASE .EQU $0C ; UF: REGISTERS BASE ADR diff --git a/Source/HBIOS/cfg_rc.asm b/Source/HBIOS/cfg_rc.asm deleted file mode 100644 index 1945fd24..00000000 --- a/Source/HBIOS/cfg_rc.asm +++ /dev/null @@ -1,86 +0,0 @@ -; -;================================================================================================== -; ROMWBW 2.X CONFIGURATION DEFAULTS FOR RC2014 -;================================================================================================== -; -; BUILD CONFIGURATION OPTIONS -; -CPUOSC .EQU 7372800 ; CPU OSC FREQ -RAMSIZE .EQU 512 ; SIZE OF RAM IN KB, MUST MATCH YOUR HARDWARE!!! -DEFSERCFG .EQU SER_115200_8N1 ; DEFAULT SERIAL LINE CONFIG (SHOULD MATCH ABOVE) -INTMODE .EQU 1 ; 0=NONE, 1=INT MODE 1, 2=INT MODE 2 -; -CRTACT .EQU FALSE ; CRT ACTIVATION AT STARTUP -VDAEMU .EQU EMUTYP_ANSI ; DEFAULT VDA EMULATION (EMUTYP_TTY, EMUTYP_ANSI, ...) -; -DSKYENABLE .EQU FALSE ; TRUE FOR DSKY SUPPORT (DO NOT COMBINE WITH PPIDE) -; -HTIMENABLE .EQU FALSE ; TRUE FOR SIMH TIMER SUPPORT -SIMRTCENABLE .EQU FALSE ; SIMH CLOCK DRIVER -DSRTCENABLE .EQU FALSE ; DS-1302 CLOCK DRIVER -DSRTCMODE .EQU DSRTCMODE_STD ; DSRTCMODE_STD, DSRTCMODE_MFPIC -DSRTCCHG .EQU FALSE ; DS-1302 CONFIGURE CHARGE ON (TRUE) OR OFF (FALSE) -; -ASCIENABLE .EQU FALSE ; TRUE FOR Z180 ASCI SUPPORT -UARTENABLE .EQU FALSE ; TRUE FOR UART SUPPORT (ALMOST ALWAYS WANT THIS TO BE TRUE) -UARTOSC .EQU 1843200 ; UART OSC FREQUENCY -ACIAENABLE .EQU TRUE ; TRUE FOR MOTOROLA 6850 ACIA SUPPORT -; -SIOENABLE .EQU TRUE ; TRUE FOR ZILOG SIO SUPPORT -SIOMODE .EQU SIOMODE_RC ; SIOMODE_RC, SIOMODE_SMB, SIOMODE_ZP -DEFSIOACFG .EQU DEFSERCFG ; DEFAULT SERIAL LINE CONFIG -DEFSIOBCFG .EQU DEFSERCFG ; DEFAULT SERIAL LINE CONFIG -DEFSIODIV .EQU 1 ; 1=RC2014/SMB, 2/4/8/16/32/64/128/256=ZP depending on jumper X5 -DEFSIOCLK .EQU CPUOSC ; 2457600/4915200=ZP,7372800=RC/SMB - SIO FIXED OSC FREQUENCY -SIODEBUG .EQU FALSE ;PS -; -VDUENABLE .EQU FALSE ; TRUE FOR VDU BOARD SUPPORT -CVDUENABLE .EQU FALSE ; TRUE FOR CVDU BOARD SUPPORT -NECENABLE .EQU FALSE ; TRUE FOR uPD7220 BOARD SUPPORT -TMSENABLE .EQU FALSE ; TRUE FOR N8 (TMS9918) VIDEO/KBD SUPPORT -VGAENABLE .EQU FALSE ; TRUE FOR VGA VIDEO/KBD SUPPORT -; -SPKENABLE .EQU FALSE ; TRUE FOR RTC LATCH IOBIT SOUND -AYENABLE .EQU FALSE ; TRUE FOR AY PSG SOUND -; -MDENABLE .EQU TRUE ; TRUE FOR ROM/RAM DISK SUPPORT (ALMOST ALWAYS WANT THIS ENABLED) -MDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF MDENABLE = TRUE) -; -FDENABLE .EQU FALSE ; TRUE FOR FLOPPY SUPPORT -FDMODE .EQU FDMODE_RCWDC ; FDMODE_DIO, FDMODE_ZETA, FDMODE_DIDE, FDMODE_N8, FDMODE_DIO3 -FDTRACE .EQU 1 ; 0=SILENT, 1=FATAL ERRORS, 2=ALL ERRORS, 3=EVERYTHING (ONLY RELEVANT IF FDENABLE = TRUE) -FDMEDIA .EQU FDM144 ; FDM720, FDM144, FDM360, FDM120 (ONLY RELEVANT IF FDENABLE = TRUE) -FDMEDIAALT .EQU FDM720 ; ALTERNATE MEDIA TO TRY, SAME CHOICES AS ABOVE (ONLY RELEVANT IF FDMAUTO = TRUE) -FDMAUTO .EQU TRUE ; SELECT BETWEEN MEDIA OPTS ABOVE AUTOMATICALLY -; -RFENABLE .EQU FALSE ; TRUE FOR RAM FLOPPY SUPPORT -; -IDEENABLE .EQU FALSE ; TRUE FOR IDE SUPPORT -IDEMODE .EQU IDEMODE_RC ; IDEMODE_DIO, IDEMODE_DIDE, IDEMODE_RC -IDETRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF IDEENABLE = TRUE) -IDE8BIT .EQU TRUE ; USE IDE 8BIT TRANSFERS (PROBABLY ONLY WORKS FOR CF CARDS!) -; -PPIDEENABLE .EQU FALSE ; TRUE FOR PPIDE SUPPORT (DO NOT COMBINE WITH DSKYENABLE) -PPIDEMODE .EQU PPIDEMODE_RC ; PPIDEMODE_SBC, PPPIDEMODE_DIO3, PPIDEMODE_MFP, PPIDEMODE_N8, PPIDEMODE_RC -PPIDETRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPIDEENABLE = TRUE) -PPIDE8BIT .EQU FALSE ; USE IDE 8BIT TRANSFERS (PROBABLY ONLY WORKS FOR CF CARDS!) -; -SDENABLE .EQU FALSE ; TRUE FOR SD SUPPORT -SDMODE .EQU SDMODE_PPI ; SDMODE_JUHA, SDMODE_CSIO, SDMODE_UART, SDMODE_PPI, SDMODE_DSD -SDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF IDEENABLE = TRUE) -SDCSIOFAST .EQU FALSE ; TABLE-DRIVEN BIT INVERTER -; -PRPENABLE .EQU FALSE ; TRUE FOR PROPIO SUPPORT -; -PPPENABLE .EQU FALSE ; TRUE FOR PARPORTPROP SUPPORT -PPPSDENABLE .EQU TRUE ; TRUE FOR PARPORTPROP SD SUPPORT -PPPSDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPPENABLE = TRUE) -PPPCONENABLE .EQU TRUE ; TRUE FOR PROPIO CONSOLE SUPPORT (PS/2 KBD & VGA VIDEO) -; -HDSKENABLE .EQU FALSE ; TRUE FOR SIMH HDSK SUPPORT -; -TERMENABLE .EQU FALSE ; TERM PSEUDO DEVICE, WILL BE ENABLED IF A VDA IS ENABLED -; -BOOTTYPE .EQU BT_MENU ; BT_MENU (WAIT FOR KEYPRESS), BT_AUTO (BOOT_DEFAULT AFTER BOOT_TIMEOUT SECS) -BOOT_TIMEOUT .EQU 20 ; APPROX TIMEOUT IN SECONDS FOR AUTOBOOT, 0 FOR IMMEDIATE -BOOT_DEFAULT .EQU 'Z' ; SELECTION TO INVOKE AT TIMEOUT diff --git a/Source/HBIOS/cfg_rc180.asm b/Source/HBIOS/cfg_rc180.asm deleted file mode 100644 index 0d531dc8..00000000 --- a/Source/HBIOS/cfg_rc180.asm +++ /dev/null @@ -1,86 +0,0 @@ -; -;================================================================================================== -; ROMWBW 2.X CONFIGURATION DEFAULTS FOR RC2014 -;================================================================================================== -; -; BUILD CONFIGURATION OPTIONS -; -CPUOSC .EQU 18432000 ; CPU OSC FREQ -RAMSIZE .EQU 512 ; SIZE OF RAM IN KB, MUST MATCH YOUR HARDWARE!!! -DEFSERCFG .EQU SER_38400_8N1 ; DEFAULT SERIAL LINE CONFIG (SHOULD MATCH ABOVE) -INTMODE .EQU 2 ; 0=NONE, 1=INT MODE 1, 2=INT MODE 2 -; -CRTACT .EQU FALSE ; CRT ACTIVATION AT STARTUP -VDAEMU .EQU EMUTYP_ANSI ; DEFAULT VDA EMULATION (EMUTYP_TTY, EMUTYP_ANSI, ...) -; -DSKYENABLE .EQU FALSE ; TRUE FOR DSKY SUPPORT (DO NOT COMBINE WITH PPIDE) -; -HTIMENABLE .EQU FALSE ; TRUE FOR SIMH TIMER SUPPORT -SIMRTCENABLE .EQU FALSE ; SIMH CLOCK DRIVER -DSRTCENABLE .EQU FALSE ; DS-1302 CLOCK DRIVER -DSRTCMODE .EQU DSRTCMODE_STD ; DSRTCMODE_STD, DSRTCMODE_MFPIC -DSRTCCHG .EQU FALSE ; DS-1302 CONFIGURE CHARGE ON (TRUE) OR OFF (FALSE) -; -ASCIENABLE .EQU TRUE ; TRUE FOR Z180 ASCI SUPPORT -UARTENABLE .EQU FALSE ; TRUE FOR UART SUPPORT (ALMOST ALWAYS WANT THIS TO BE TRUE) -UARTOSC .EQU 1843200 ; UART OSC FREQUENCY -SIOENABLE .EQU FALSE ; TRUE FOR ZILOG SIO/2 SUPPORT -SIOMODE .EQU SIOMODE_RC ; SIOMODE_RC, SIOMODE_SMB -ACIAENABLE .EQU FALSE ; TRUE FOR MOTOROLA 6850 ACIA SUPPORT -; -VDUENABLE .EQU FALSE ; TRUE FOR VDU BOARD SUPPORT -CVDUENABLE .EQU FALSE ; TRUE FOR CVDU BOARD SUPPORT -NECENABLE .EQU FALSE ; TRUE FOR uPD7220 BOARD SUPPORT -TMSENABLE .EQU FALSE ; TRUE FOR N8 (TMS9918) VIDEO/KBD SUPPORT -VGAENABLE .EQU FALSE ; TRUE FOR VGA VIDEO/KBD SUPPORT -; -SPKENABLE .EQU FALSE ; TRUE FOR RTC LATCH IOBIT SOUND -AYENABLE .EQU FALSE ; TRUE FOR AY PSG SOUND -; -MDENABLE .EQU TRUE ; TRUE FOR ROM/RAM DISK SUPPORT (ALMOST ALWAYS WANT THIS ENABLED) -MDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF MDENABLE = TRUE) -; -FDENABLE .EQU FALSE ; TRUE FOR FLOPPY SUPPORT -FDMODE .EQU FDMODE_RCWDC ; FDMODE_DIO, FDMODE_ZETA, FDMODE_DIDE, FDMODE_N8, FDMODE_DIO3 -FDTRACE .EQU 1 ; 0=SILENT, 1=FATAL ERRORS, 2=ALL ERRORS, 3=EVERYTHING (ONLY RELEVANT IF FDENABLE = TRUE) -FDMEDIA .EQU FDM144 ; FDM720, FDM144, FDM360, FDM120 (ONLY RELEVANT IF FDENABLE = TRUE) -FDMEDIAALT .EQU FDM720 ; ALTERNATE MEDIA TO TRY, SAME CHOICES AS ABOVE (ONLY RELEVANT IF FDMAUTO = TRUE) -FDMAUTO .EQU TRUE ; SELECT BETWEEN MEDIA OPTS ABOVE AUTOMATICALLY -; -RFENABLE .EQU FALSE ; TRUE FOR RAM FLOPPY SUPPORT -; -IDEENABLE .EQU FALSE ; TRUE FOR IDE SUPPORT -IDEMODE .EQU IDEMODE_RC ; IDEMODE_DIO, IDEMODE_DIDE, IDEMODE_RC -IDETRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF IDEENABLE = TRUE) -IDE8BIT .EQU TRUE ; USE IDE 8BIT TRANSFERS (PROBABLY ONLY WORKS FOR CF CARDS!) -; -PPIDEENABLE .EQU FALSE ; TRUE FOR PPIDE SUPPORT (DO NOT COMBINE WITH DSKYENABLE) -PPIDEMODE .EQU PPIDEMODE_RC ; PPIDEMODE_SBC, PPPIDEMODE_DIO3, PPIDEMODE_MFP, PPIDEMODE_N8, PPIDEMODE_RC -PPIDETRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPIDEENABLE = TRUE) -PPIDE8BIT .EQU FALSE ; USE IDE 8BIT TRANSFERS (PROBABLY ONLY WORKS FOR CF CARDS!) -; -SDENABLE .EQU FALSE ; TRUE FOR SD SUPPORT -SDMODE .EQU SDMODE_PPI ; SDMODE_JUHA, SDMODE_CSIO, SDMODE_UART, SDMODE_PPI, SDMODE_DSD -SDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF IDEENABLE = TRUE) -SDCSIOFAST .EQU FALSE ; TABLE-DRIVEN BIT INVERTER -; -PRPENABLE .EQU FALSE ; TRUE FOR PROPIO SUPPORT -; -PPPENABLE .EQU FALSE ; TRUE FOR PARPORTPROP SUPPORT -PPPSDENABLE .EQU TRUE ; TRUE FOR PARPORTPROP SD SUPPORT -PPPSDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPPENABLE = TRUE) -PPPCONENABLE .EQU TRUE ; TRUE FOR PROPIO CONSOLE SUPPORT (PS/2 KBD & VGA VIDEO) -; -HDSKENABLE .EQU FALSE ; TRUE FOR SIMH HDSK SUPPORT -; -TERMENABLE .EQU FALSE ; TERM PSEUDO DEVICE, WILL BE ENABLED IF A VDA IS ENABLED -; -BOOTTYPE .EQU BT_MENU ; BT_MENU (WAIT FOR KEYPRESS), BT_AUTO (BOOT_DEFAULT AFTER BOOT_TIMEOUT SECS) -BOOT_TIMEOUT .EQU 20 ; APPROX TIMEOUT IN SECONDS FOR AUTOBOOT, 0 FOR IMMEDIATE -BOOT_DEFAULT .EQU 'Z' ; SELECTION TO INVOKE AT TIMEOUT -; -; 18.432MHz OSC @ FULL SPEED -; -Z180_CLKDIV .EQU 1 ; 0=OSC/2, 1=OSC, 2=OSC*2 -Z180_MEMWAIT .EQU 0 ; MEMORY WAIT STATES TO INSERT (0-3) -Z180_IOWAIT .EQU 1 ; IO WAIT STATES TO INSERT (0-3) diff --git a/Source/HBIOS/cfg_rcz180.asm b/Source/HBIOS/cfg_rcz180.asm new file mode 100644 index 00000000..ee65373f --- /dev/null +++ b/Source/HBIOS/cfg_rcz180.asm @@ -0,0 +1,188 @@ +; +;================================================================================================== +; ROMWBW 2.X CONFIGURATION DEFAULTS FOR RC2014 +;================================================================================================== +; +; THIS FILE CONTAINS THE FULL SET OF DEFAULT CONFIGURATION SETTINGS FOR THE PLATFORM +; INDICATED ABOVE. THIS FILE SHOULD *NOT* NORMALLY BE CHANGED. INSTEAD, YOU SHOULD +; OVERRIDE ANY SETTINGS YOU WANT USING A CONFIGURATION FILE IN THE CONFIG DIRECTORY +; UNDER THIS DIRECTORY. +; +; THIS FILE CAN BE CONSIDERED A REFERENCE THAT LISTS ALL POSSIBLE CONFIGURATION SETTINGS +; FOR THE PLATFORM. +; +#DEFINE PLATFORM_NAME "RC2014" +; +PLATFORM .EQU PLT_RCZ180 ; PLT_[SBC|ZETA|ZETA2|N8|MK4|UNA|RCZ80|RCZ180|EZZ80|SCZ180|DYNO] +CPUFAM .EQU CPU_Z180 ; CPU FAMILY: CPU_[Z80|Z180] +BIOS .EQU BIOS_WBW ; HARDWARE BIOS: BIOS_[WBW|UNA] +BATCOND .EQU FALSE ; ENABLE LOW BATTERY WARNING MESSAGE +HBIOS_MUTEX .EQU FALSE ; ENABLE REENTRANT CALLS TO HBIOS (ADDS OVERHEAD) +USELZSA2 .EQU TRUE ; ENABLE FONT COMPRESSION +; +BOOT_TIMEOUT .EQU 0 ; AUTO BOOT TIMEOUT IN SECONDS, 0 TO DISABLE +; +CPUOSC .EQU 18432000 ; CPU OSC FREQ IN MHZ +INTMODE .EQU 2 ; INTERRUPTS: 0=NONE, 1=MODE 1, 2=MODE 2 +DEFSERCFG .EQU SER_115200_8N1 ; DEFAULT SERIAL LINE CONFIG (SEE STD.ASM) +; +RAMSIZE .EQU 512 ; SIZE OF RAM IN KB (MUST MATCH YOUR HARDWARE!!!) +MEMMGR .EQU MM_Z180 ; MEMORY MANAGER: MM_[SBC|Z2|N8|Z180] +RAMBIAS .EQU 512 ; OFFSET OF START OF RAM IN PHYSICAL ADDRESS SPACE +MPGSEL_0 .EQU $78 ; Z2 MEM MGR BANK 0 PAGE SELECT REG (WRITE ONLY) +MPGSEL_1 .EQU $79 ; Z2 MEM MGR BANK 1 PAGE SELECT REG (WRITE ONLY) +MPGSEL_2 .EQU $7A ; Z2 MEM MGR BANK 2 PAGE SELECT REG (WRITE ONLY) +MPGSEL_3 .EQU $7B ; Z2 MEM MGR BANK 3 PAGE SELECT REG (WRITE ONLY) +MPGENA .EQU $7C ; Z2 MEM MGR PAGING ENABLE REGISTER (BIT 0, WRITE ONLY) +; +Z180_BASE .EQU $C0 ; Z180: I/O BASE ADDRESS FOR INTERNAL REGISTERS +Z180_CLKDIV .EQU 1 ; Z180: CHK DIV: 0=OSC/2, 1=OSC, 2=OSC*2 +Z180_MEMWAIT .EQU 0 ; Z180: MEMORY WAIT STATES (0-3) +Z180_IOWAIT .EQU 1 ; Z180: I/O WAIT STATES TO ADD ABOVE 1 W/S BUILT-IN (0-3) +; +RTCIO .EQU $0C ; RTC LATCH REGISTER ADR +; +KIOENABLE .EQU FALSE ; ENABLE ZILOG KIO SUPPORT +KIOBASE .EQU $80 ; KIO BASE I/O ADDRESS +; +CTCENABLE .EQU FALSE ; ENABLE ZILOG CTC SUPPORT +; +DIAGENABLE .EQU TRUE ; ENABLES OUTPUT TO 8 BIT LED DIAGNOSTIC PORT +DIAGPORT .EQU $00 ; DIAGNOSTIC PORT ADDRESS +DIAGDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON DIAGNOSTIC LEDS +; +LEDENABLE .EQU FALSE ; ENABLES STATUS LED (SINGLE LED) +LEDPORT .EQU $0E ; STATUS LED PORT ADDRESS +LEDDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON STATUS LED +; +DSKYENABLE .EQU FALSE ; ENABLES DSKY (DO NOT COMBINE WITH PPIDE) +; +CRTACT .EQU FALSE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP +VDAEMU .EQU EMUTYP_ANSI ; VDA EMULATION: EMUTYP_[TTY|ANSI] +ANSITRACE .EQU 1 ; ANSI DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +DSRTCENABLE .EQU TRUE ; DSRTC: ENABLE DS-1302 CLOCK DRIVER (DSRTC.ASM) +DSRTCMODE .EQU DSRTCMODE_STD ; DSRTC: OPERATING MODE: DSRTC_[STD|MFPIC] +DSRTCCHG .EQU FALSE ; DSRTC: FORCE BATTERY CHARGE ON (USE WITH CAUTION!!!) +; +BQRTCENABLE .EQU FALSE ; BQRTC: ENABLE BQ4845 CLOCK DRIVER (BQRTC.ASM) +BQRTC_BASE .EQU $50 ; BQRTC: I/O BASE ADDRESS +; +INTRTCENABLE .EQU FALSE ; ENABLE PERIODIC INTERRUPT CLOCK DRIVER (INTRTC.ASM) +; +HTIMENABLE .EQU FALSE ; ENABLE SIMH TIMER SUPPORT +SIMRTCENABLE .EQU FALSE ; ENABLE SIMH CLOCK DRIVER (SIMRTC.ASM) +; +UARTENABLE .EQU TRUE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +UARTOSC .EQU 1843200 ; UART: OSC FREQUENCY IN MHZ +UARTCFG .EQU DEFSERCFG ; UART: LINE CONFIG FOR UART PORTS +UARTSBC .EQU FALSE ; UART: AUTO-DETECT SBC/ZETA ONBOARD UART +UARTCAS .EQU FALSE ; UART: AUTO-DETECT ECB CASSETTE UART +UARTMFP .EQU FALSE ; UART: AUTO-DETECT MF/PIC UART +UART4 .EQU FALSE ; UART: AUTO-DETECT 4UART UART +UARTRC .EQU TRUE ; UART: AUTO-DETECT RC UART +; +ASCIENABLE .EQU TRUE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +ASCI0CFG .EQU DEFSERCFG ; ASCI 0: SERIAL LINE CONFIG +ASCI1CFG .EQU DEFSERCFG ; ASCI 1: SERIAL LINE CONFIG +; +ACIAENABLE .EQU FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +; +SIOENABLE .EQU TRUE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +SIODEBUG .EQU FALSE ; SIO: ENABLE DEBUG OUTPUT +SIOCNT .EQU 2 ; SIO: NUMBER OF CHIPS TO DETECT (1-2), 2 CHANNELS PER CHIP +SIO0MODE .EQU SIOMODE_RC ; SIO 0: CHIP TYPE: SIOMODE_[RC|SMB|ZP|EZZ80] +SIO0CTCC .EQU -1 ; SIO 0: CTC CHANNEL CLOCK SCALER, (0-3), -1 FOR NONE +SIO0BASE .EQU $80 ; SIO 0: REGISTERS BASE ADR +SIO0ACLK .EQU 7372800 ; SIO 0A: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO0ADIV .EQU 1 ; SIO 0A: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO0ACFG .EQU SER_115200_8N1 ; SER_115200_8N1 0A: SERIAL LINE CONFIG +SIO0BCLK .EQU 7372800 ; SIO 0B: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO0BDIV .EQU 1 ; SIO 0B: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO0BCFG .EQU SER_115200_8N1 ; SIO 0B: SERIAL LINE CONFIG +SIO1MODE .EQU SIOMODE_RC ; SIO 1: CHIP TYPE: SIOMODE_[RC|SMB|ZP|EZZ80] +SIO1CTCC .EQU -1 ; SIO 1: CTC CHANNEL CLOCK SCALER, (0-3), -1 FOR NONE +SIO1BASE .EQU $84 ; SIO 1: REGISTERS BASE ADR +SIO1ACLK .EQU 7372800 ; SIO 1A: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO1ADIV .EQU 1 ; SIO 1A: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO1ACFG .EQU SER_115200_8N1 ; SER_115200_8N1 1A: SERIAL LINE CONFIG +SIO1BCLK .EQU 7372800 ; SIO 1B: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO1BDIV .EQU 1 ; SIO 1B: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO1BCFG .EQU SER_115200_8N1 ; SIO 1B: SERIAL LINE CONFIG +; +XIOCFG .EQU DEFSERCFG ; XIO: SERIAL LINE CONFIG +; +VDUENABLE .EQU FALSE ; VDU: ENABLE VDU VIDEO/KBD DRIVER (VDU.ASM) +CVDUENABLE .EQU FALSE ; CVDU: ENABLE CVDU VIDEO/KBD DRIVER (CVDU.ASM) +NECENABLE .EQU FALSE ; NEC: ENABLE NEC UPD7220 VIDEO/KBD DRIVER (NEC.ASM) +TMSENABLE .EQU FALSE ; TMS: ENABLE TMS9918 VIDEO/KBD DRIVER (TMS.ASM) +VGAENABLE .EQU FALSE ; VGA: ENABLE VGA VIDEO/KBD DRIVER (VGA.ASM) +; +SPKENABLE .EQU FALSE ; SPK: ENABLE RTC LATCH IOBIT SOUND DRIVER (SPK.ASM) +; +AYENABLE .EQU FALSE ; AY: ENABLE AY PSG SOUND DRIVER +AYMODE .EQU AYMODE_RCZ180 ; AY: DRIVER MODE: AYMODE_[SCG/N8/RCZ80/RCZ180] +; +MDENABLE .EQU TRUE ; MD: ENABLE MEMORY (ROM/RAM) DISK DRIVER (MD.ASM) +MDTRACE .EQU 1 ; MD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +FDENABLE .EQU FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .EQU FDMODE_RCWDC ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3|DYNO] +FDTRACE .EQU 1 ; FD: TRACE LEVEL (0=NO,1=FATAL,2=ERRORS,3=ALL) +FDMEDIA .EQU FDM144 ; FD: DEFAULT MEDIA FORMAT FDM[720|144|360|120|111] +FDMEDIAALT .EQU FDM720 ; FD: ALTERNATE MEDIA FORMAT FDM[720|144|360|120|111] +FDMAUTO .EQU TRUE ; FD: AUTO SELECT DEFAULT/ALTERNATE MEDIA FORMATS +; +RFENABLE .EQU FALSE ; RF: ENABLE RAM FLOPPY DRIVER +; +IDEENABLE .EQU FALSE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +IDETRACE .EQU 1 ; IDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +IDECNT .EQU 1 ; IDE: NUMBER OF IDE INTERFACES TO DETECT (1-3), 2 DRIVES EACH +IDE0MODE .EQU IDEMODE_RC ; IDE 0: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE0BASE .EQU $10 ; IDE 0: IO BASE ADDRESS +IDE0DATLO .EQU $00 ; IDE 0: DATA LO PORT FOR 16-BIT I/O +IDE0DATHI .EQU $00 ; IDE 0: DATA HI PORT FOR 16-BIT I/O +IDE0A8BIT .EQU TRUE ; IDE 0A (MASTER): 8 BIT XFER +IDE0B8BIT .EQU TRUE ; IDE 0B (MASTER): 8 BIT XFER +IDE1MODE .EQU IDEMODE_NONE ; IDE 1: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE1BASE .EQU $00 ; IDE 1: IO BASE ADDRESS +IDE1DATLO .EQU $00 ; IDE 1: DATA LO PORT FOR 16-BIT I/O +IDE1DATHI .EQU $00 ; IDE 1: DATA HI PORT FOR 16-BIT I/O +IDE1A8BIT .EQU TRUE ; IDE 1A (MASTER): 8 BIT XFER +IDE1B8BIT .EQU TRUE ; IDE 1B (MASTER): 8 BIT XFER +IDE2MODE .EQU IDEMODE_NONE ; IDE 2: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE2BASE .EQU $00 ; IDE 2: IO BASE ADDRESS +IDE2DATLO .EQU $00 ; IDE 2: DATA LO PORT FOR 16-BIT I/O +IDE2DATHI .EQU $00 ; IDE 2: DATA HI PORT FOR 16-BIT I/O +IDE2A8BIT .EQU TRUE ; IDE 2A (MASTER): 8 BIT XFER +IDE2B8BIT .EQU TRUE ; IDE 2B (MASTER): 8 BIT XFER +; +PPIDEENABLE .EQU FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) +PPIDETRACE .EQU 1 ; PPIDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPIDECNT .EQU 1 ; PPIDE: NUMBER OF PPI CHIPS TO DETECT (1-3), 2 DRIVES PER CHIP +PPIDE0BASE .EQU $20 ; PPIDE 0: PPI REGISTERS BASE ADR +PPIDE0A8BIT .EQU FALSE ; PPIDE 0A (MASTER): 8 BIT XFER +PPIDE0B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +PPIDE1BASE .EQU $00 ; PPIDE 1: PPI REGISTERS BASE ADR +PPIDE1A8BIT .EQU FALSE ; PPIDE 1A (MASTER): 8 BIT XFER +PPIDE1B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +PPIDE2BASE .EQU $00 ; PPIDE 2: PPI REGISTERS BASE ADR +PPIDE2A8BIT .EQU FALSE ; PPIDE 2A (MASTER): 8 BIT XFER +PPIDE2B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER; +SDENABLE .EQU FALSE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDMODE .EQU SDMODE_PPI ; SD: DRIVER MODE: SDMODE_[JUHA|N8|CSIO|PPI|UART|DSD|MK4|SC|MT] +SDCNT .EQU 1 ; SD: NUMBER OF SD CARD DEVICES (1-2), FOR DSD & SC ONLY +SDTRACE .EQU 1 ; SD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +SDCSIOFAST .EQU FALSE ; SD: ENABLE TABLE-DRIVEN BIT INVERTER IN CSIO MODE +; +PRPENABLE .EQU FALSE ; PRP: ENABLE ECB PROPELLER IO BOARD DRIVER (PRP.ASM) +; +PPPENABLE .EQU FALSE ; PPP: ENABLE ZETA PARALLEL PORT PROPELLER BOARD DRIVER (PPP.ASM) +; +HDSKENABLE .EQU FALSE ; HDSK: ENABLE SIMH HDSK DISK DRIVER (HDSK.ASM) +; +PIO_4P .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB 4P BOARD +PIO_ZP .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB ZILOG PERIPHERALS BOARD (PIO.ASM) +PPI_SBC .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR 8255 CHIP +; +UFENABLE .EQU FALSE ; UF: ENABLE ECB USB FIFO DRIVER (UF.ASM) diff --git a/Source/HBIOS/cfg_rcz80.asm b/Source/HBIOS/cfg_rcz80.asm new file mode 100644 index 00000000..40b3d129 --- /dev/null +++ b/Source/HBIOS/cfg_rcz80.asm @@ -0,0 +1,193 @@ +; +;================================================================================================== +; ROMWBW 2.X CONFIGURATION DEFAULTS FOR RC2014 Z80 +;================================================================================================== +; +; THIS FILE CONTAINS THE FULL SET OF DEFAULT CONFIGURATION SETTINGS FOR THE PLATFORM +; INDICATED ABOVE. THIS FILE SHOULD *NOT* NORMALLY BE CHANGED. INSTEAD, YOU SHOULD +; OVERRIDE ANY SETTINGS YOU WANT USING A CONFIGURATION FILE IN THE CONFIG DIRECTORY +; UNDER THIS DIRECTORY. +; +; THIS FILE CAN BE CONSIDERED A REFERENCE THAT LISTS ALL POSSIBLE CONFIGURATION SETTINGS +; FOR THE PLATFORM. +; +#DEFINE PLATFORM_NAME "RC2014" +; +PLATFORM .EQU PLT_RCZ80 ; PLT_[SBC|ZETA|ZETA2|N8|MK4|UNA|RCZ80|RCZ180|EZZ80|SCZ180|DYNO] +CPUFAM .EQU CPU_Z80 ; CPU FAMILY: CPU_[Z80|Z180] +BIOS .EQU BIOS_WBW ; HARDWARE BIOS: BIOS_[WBW|UNA] +BATCOND .EQU FALSE ; ENABLE LOW BATTERY WARNING MESSAGE +HBIOS_MUTEX .EQU FALSE ; ENABLE REENTRANT CALLS TO HBIOS (ADDS OVERHEAD) +USELZSA2 .EQU TRUE ; ENABLE FONT COMPRESSION +; +BOOT_TIMEOUT .EQU 0 ; AUTO BOOT TIMEOUT IN SECONDS, 0 TO DISABLE +; +CPUOSC .EQU 7372800 ; CPU OSC FREQ IN MHZ +INTMODE .EQU 1 ; INTERRUPTS: 0=NONE, 1=MODE 1, 2=MODE 2 +DEFSERCFG .EQU SER_115200_8N1 ; DEFAULT SERIAL LINE CONFIG (SEE STD.ASM) +; +RAMSIZE .EQU 512 ; SIZE OF RAM IN KB (MUST MATCH YOUR HARDWARE!!!) +MEMMGR .EQU MM_Z2 ; MEMORY MANAGER: MM_[SBC|Z2|N8|Z180] +MPGSEL_0 .EQU $78 ; Z2 MEM MGR BANK 0 PAGE SELECT REG (WRITE ONLY) +MPGSEL_1 .EQU $79 ; Z2 MEM MGR BANK 1 PAGE SELECT REG (WRITE ONLY) +MPGSEL_2 .EQU $7A ; Z2 MEM MGR BANK 2 PAGE SELECT REG (WRITE ONLY) +MPGSEL_3 .EQU $7B ; Z2 MEM MGR BANK 3 PAGE SELECT REG (WRITE ONLY) +MPGENA .EQU $7C ; Z2 MEM MGR PAGING ENABLE REGISTER (BIT 0, WRITE ONLY) +; +RTCIO .EQU $C0 ; RTC LATCH REGISTER ADR +; +KIOENABLE .EQU FALSE ; ENABLE ZILOG KIO SUPPORT +KIOBASE .EQU $80 ; KIO BASE I/O ADDRESS +; +CTCENABLE .EQU FALSE ; ENABLE ZILOG CTC SUPPORT +CTCMODE .EQU CTCMODE_RC ; CTC MODE: CTCMODE_[ZP|Z2|EZ|RC] +CTCBASE .EQU $88 ; CTC BASE I/O ADDRESS +; +DIAGENABLE .EQU TRUE ; ENABLES OUTPUT TO 8 BIT LED DIAGNOSTIC PORT +DIAGPORT .EQU $00 ; DIAGNOSTIC PORT ADDRESS +DIAGDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON DIAGNOSTIC LEDS +; +LEDENABLE .EQU FALSE ; ENABLES STATUS LED (SINGLE LED) +LEDPORT .EQU $0E ; STATUS LED PORT ADDRESS +LEDDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON STATUS LED +; +DSKYENABLE .EQU FALSE ; ENABLES DSKY (DO NOT COMBINE WITH PPIDE) +; +CRTACT .EQU FALSE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP +VDAEMU .EQU EMUTYP_ANSI ; VDA EMULATION: EMUTYP_[TTY|ANSI] +ANSITRACE .EQU 1 ; ANSI DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +DSRTCENABLE .EQU TRUE ; DSRTC: ENABLE DS-1302 CLOCK DRIVER (DSRTC.ASM) +DSRTCMODE .EQU DSRTCMODE_STD ; DSRTC: OPERATING MODE: DSRTC_[STD|MFPIC] +DSRTCCHG .EQU FALSE ; DSRTC: FORCE BATTERY CHARGE ON (USE WITH CAUTION!!!) +; +BQRTCENABLE .EQU FALSE ; BQRTC: ENABLE BQ4845 CLOCK DRIVER (BQRTC.ASM) +BQRTC_BASE .EQU $50 ; BQRTC: I/O BASE ADDRESS +; +INTRTCENABLE .EQU FALSE ; ENABLE PERIODIC INTERRUPT CLOCK DRIVER (INTRTC.ASM) +; +HTIMENABLE .EQU FALSE ; ENABLE SIMH TIMER SUPPORT +SIMRTCENABLE .EQU FALSE ; ENABLE SIMH CLOCK DRIVER (SIMRTC.ASM) +; +UARTENABLE .EQU TRUE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +UARTOSC .EQU 1843200 ; UART: OSC FREQUENCY IN MHZ +UARTCFG .EQU DEFSERCFG ; UART: LINE CONFIG FOR UART PORTS +UARTSBC .EQU FALSE ; UART: AUTO-DETECT SBC/ZETA ONBOARD UART +UARTCAS .EQU FALSE ; UART: AUTO-DETECT ECB CASSETTE UART +UARTMFP .EQU FALSE ; UART: AUTO-DETECT MF/PIC UART +UART4 .EQU FALSE ; UART: AUTO-DETECT 4UART UART +UARTRC .EQU TRUE ; UART: AUTO-DETECT RC UART +; +ASCIENABLE .EQU FALSE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +; +ACIAENABLE .EQU TRUE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +ACIADEBUG .EQU FALSE ; ACIA: ENABLE DEBUG OUTPUT +ACIACNT .EQU 1 ; ACIA: NUMBER OF CHIPS TO DETECT (1-2) +ACIA0BASE .EQU $80 ; ACIA 0: REGISTERS BASE ADR +ACIA0CLK .EQU CPUOSC ; ACIA 0: OSC FREQ IN HZ +ACIA0DIV .EQU 1 ; ACIA 0: SERIAL CLOCK DIVIDER +ACIA0CFG .EQU DEFSERCFG ; ACIA 0: SERIAL LINE CONFIG (SEE STD.ASM) +ACIA1BASE .EQU $40 ; ACIA 1: REGISTERS BASE ADR +ACIA1CLK .EQU CPUOSC ; ACIA 1: OSC FREQ IN HZ +ACIA1DIV .EQU 1 ; ACIA 1: SERIAL CLOCK DIVIDER +ACIA1CFG .EQU DEFSERCFG ; ACIA 1: SERIAL LINE CONFIG (SEE STD.ASM) +; +SIOENABLE .EQU TRUE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +SIODEBUG .EQU FALSE ; SIO: ENABLE DEBUG OUTPUT +SIOCNT .EQU 2 ; SIO: NUMBER OF CHIPS TO DETECT (1-2), 2 CHANNELS PER CHIP +SIO0MODE .EQU SIOMODE_RC ; SIO 0: CHIP TYPE: SIOMODE_[RC|SMB|ZP|EZZ80] +SIO0CTCC .EQU -1 ; SIO 0: CTC CHANNEL CLOCK SCALER, (0-3), -1 FOR NONE +SIO0BASE .EQU $80 ; SIO 0: REGISTERS BASE ADR +SIO0ACLK .EQU CPUOSC ; SIO 0A: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO0ADIV .EQU 1 ; SIO 0A: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO0ACFG .EQU DEFSERCFG ; SIO 0A: SERIAL LINE CONFIG +SIO0BCLK .EQU CPUOSC ; SIO 0B: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO0BDIV .EQU 1 ; SIO 0B: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO0BCFG .EQU DEFSERCFG ; SIO 0B: SERIAL LINE CONFIG +SIO1MODE .EQU SIOMODE_RC ; SIO 1: CHIP TYPE: SIOMODE_[RC|SMB|ZP|EZZ80] +SIO1CTCC .EQU -1 ; SIO 1: CTC CHANNEL CLOCK SCALER, (0-3), -1 FOR NONE +SIO1BASE .EQU $84 ; SIO 1: REGISTERS BASE ADR +SIO1ACLK .EQU CPUOSC ; SIO 1A: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO1ADIV .EQU 1 ; SIO 1A: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO1ACFG .EQU DEFSERCFG ; SIO 1A: SERIAL LINE CONFIG +SIO1BCLK .EQU CPUOSC ; SIO 1B: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO1BDIV .EQU 1 ; SIO 1B: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO1BCFG .EQU DEFSERCFG ; SIO 1B: SERIAL LINE CONFIG +; +XIOCFG .EQU DEFSERCFG ; XIO: SERIAL LINE CONFIG +; +VDUENABLE .EQU FALSE ; VDU: ENABLE VDU VIDEO/KBD DRIVER (VDU.ASM) +CVDUENABLE .EQU FALSE ; CVDU: ENABLE CVDU VIDEO/KBD DRIVER (CVDU.ASM) +NECENABLE .EQU FALSE ; NEC: ENABLE NEC UPD7220 VIDEO/KBD DRIVER (NEC.ASM) +TMSENABLE .EQU FALSE ; TMS: ENABLE TMS9918 VIDEO/KBD DRIVER (TMS.ASM) +VGAENABLE .EQU FALSE ; VGA: ENABLE VGA VIDEO/KBD DRIVER (VGA.ASM) +; +SPKENABLE .EQU FALSE ; SPK: ENABLE RTC LATCH IOBIT SOUND DRIVER (SPK.ASM) +; +AYENABLE .EQU FALSE ; AY: ENABLE AY PSG SOUND DRIVER +AYMODE .EQU AYMODE_RCZ80 ; AY: DRIVER MODE: AYMODE_[SCG/N8/RCZ80/RCZ180] +; +MDENABLE .EQU TRUE ; MD: ENABLE MEMORY (ROM/RAM) DISK DRIVER (MD.ASM) +MDTRACE .EQU 1 ; MD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +FDENABLE .EQU FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .EQU FDMODE_RCWDC ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3|DYNO] +FDTRACE .EQU 1 ; FD: TRACE LEVEL (0=NO,1=FATAL,2=ERRORS,3=ALL) +FDMEDIA .EQU FDM144 ; FD: DEFAULT MEDIA FORMAT FDM[720|144|360|120|111] +FDMEDIAALT .EQU FDM720 ; FD: ALTERNATE MEDIA FORMAT FDM[720|144|360|120|111] +FDMAUTO .EQU TRUE ; FD: AUTO SELECT DEFAULT/ALTERNATE MEDIA FORMATS +; +RFENABLE .EQU FALSE ; RF: ENABLE RAM FLOPPY DRIVER +; +IDEENABLE .EQU FALSE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +IDETRACE .EQU 1 ; IDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +IDECNT .EQU 1 ; IDE: NUMBER OF IDE INTERFACES TO DETECT (1-3), 2 DRIVES EACH +IDE0MODE .EQU IDEMODE_RC ; IDE 0: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE0BASE .EQU $10 ; IDE 0: IO BASE ADDRESS +IDE0DATLO .EQU $00 ; IDE 0: DATA LO PORT FOR 16-BIT I/O +IDE0DATHI .EQU $00 ; IDE 0: DATA HI PORT FOR 16-BIT I/O +IDE0A8BIT .EQU TRUE ; IDE 0A (MASTER): 8 BIT XFER +IDE0B8BIT .EQU TRUE ; IDE 0B (MASTER): 8 BIT XFER +IDE1MODE .EQU IDEMODE_NONE ; IDE 1: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE1BASE .EQU $00 ; IDE 1: IO BASE ADDRESS +IDE1DATLO .EQU $00 ; IDE 1: DATA LO PORT FOR 16-BIT I/O +IDE1DATHI .EQU $00 ; IDE 1: DATA HI PORT FOR 16-BIT I/O +IDE1A8BIT .EQU TRUE ; IDE 1A (MASTER): 8 BIT XFER +IDE1B8BIT .EQU TRUE ; IDE 1B (MASTER): 8 BIT XFER +IDE2MODE .EQU IDEMODE_NONE ; IDE 2: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE2BASE .EQU $00 ; IDE 2: IO BASE ADDRESS +IDE2DATLO .EQU $00 ; IDE 2: DATA LO PORT FOR 16-BIT I/O +IDE2DATHI .EQU $00 ; IDE 2: DATA HI PORT FOR 16-BIT I/O +IDE2A8BIT .EQU TRUE ; IDE 2A (MASTER): 8 BIT XFER +IDE2B8BIT .EQU TRUE ; IDE 2B (MASTER): 8 BIT XFER +; +PPIDEENABLE .EQU FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) +PPIDETRACE .EQU 1 ; PPIDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPIDECNT .EQU 1 ; PPIDE: NUMBER OF PPI CHIPS TO DETECT (1-3), 2 DRIVES PER CHIP +PPIDE0BASE .EQU $20 ; PPIDE 0: PPI REGISTERS BASE ADR +PPIDE0A8BIT .EQU FALSE ; PPIDE 0A (MASTER): 8 BIT XFER +PPIDE0B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +PPIDE1BASE .EQU $00 ; PPIDE 1: PPI REGISTERS BASE ADR +PPIDE1A8BIT .EQU FALSE ; PPIDE 1A (MASTER): 8 BIT XFER +PPIDE1B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +PPIDE2BASE .EQU $00 ; PPIDE 2: PPI REGISTERS BASE ADR +PPIDE2A8BIT .EQU FALSE ; PPIDE 2A (MASTER): 8 BIT XFER +PPIDE2B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +; +SDENABLE .EQU FALSE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDMODE .EQU SDMODE_PPI ; SD: DRIVER MODE: SDMODE_[JUHA|N8|CSIO|PPI|UART|DSD|MK4|SC|MT] +SDCNT .EQU 1 ; SD: NUMBER OF SD CARD DEVICES (1-2), FOR DSD & SC ONLY +SDTRACE .EQU 1 ; SD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +SDCSIOFAST .EQU FALSE ; SD: ENABLE TABLE-DRIVEN BIT INVERTER IN CSIO MODE +; +PRPENABLE .EQU FALSE ; PRP: ENABLE ECB PROPELLER IO BOARD DRIVER (PRP.ASM) +; +PPPENABLE .EQU FALSE ; PPP: ENABLE ZETA PARALLEL PORT PROPELLER BOARD DRIVER (PPP.ASM) +; +HDSKENABLE .EQU FALSE ; HDSK: ENABLE SIMH HDSK DISK DRIVER (HDSK.ASM) +; +PIO_4P .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB 4P BOARD +PIO_ZP .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB ZILOG PERIPHERALS BOARD (PIO.ASM) +PPI_SBC .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR 8255 CHIP +; +UFENABLE .EQU FALSE ; UF: ENABLE ECB USB FIFO DRIVER (UF.ASM) diff --git a/Source/HBIOS/cfg_sbc.asm b/Source/HBIOS/cfg_sbc.asm index 1cdc364c..e451213d 100644 --- a/Source/HBIOS/cfg_sbc.asm +++ b/Source/HBIOS/cfg_sbc.asm @@ -3,98 +3,186 @@ ; ROMWBW 2.X CONFIGURATION DEFAULTS FOR SBC ;================================================================================================== ; -; BUILD CONFIGURATION OPTIONS -; -CPUOSC .EQU 8000000 ; CPU OSC FREQ -RAMSIZE .EQU 512 ; SIZE OF RAM IN KB, MUST MATCH YOUR HARDWARE!!! -DEFSERCFG .EQU SER_38400_8N1 ; DEFAULT SERIAL LINE CONFIG (SHOULD MATCH ABOVE) -INTMODE .EQU 0 ; 0=NONE, 1=INT MODE 1, 2=INT MODE 2 -; -CRTACT .EQU FALSE ; CRT ACTIVATION AT STARTUP -VDAEMU .EQU EMUTYP_ANSI ; DEFAULT VDA EMULATION (EMUTYP_TTY, EMUTYP_ANSI, ...) -; -DSKYENABLE .EQU FALSE ; TRUE FOR DSKY SUPPORT (DO NOT COMBINE WITH PPIDE) -; -HTIMENABLE .EQU FALSE ; TRUE FOR SIMH TIMER SUPPORT -SIMRTCENABLE .EQU FALSE ; SIMH CLOCK DRIVER -DSRTCENABLE .EQU TRUE ; DS-1302 CLOCK DRIVER -DSRTCMODE .EQU DSRTCMODE_STD ; DSRTCMODE_STD, DSRTCMODE_MFPIC -DSRTCCHG .EQU FALSE ; DS-1302 CONFIGURE CHARGE ON (TRUE) OR OFF (FALSE) -; -ASCIENABLE .EQU FALSE ; TRUE FOR Z180 ASCI SUPPORT -UARTENABLE .EQU TRUE ; TRUE FOR UART SUPPORT (ALMOST ALWAYS WANT THIS TO BE TRUE) -UARTOSC .EQU 1843200 ; UART OSC FREQUENCY -ACIAENABLE .EQU FALSE ; TRUE FOR MOTOROLA 6850 ACIA SUPPORT -; -SIOENABLE .EQU FALSE ; TRUE FOR ZILOG SIO SUPPORT -SIOMODE .EQU SIOMODE_ZP ; SIOMODE_RC, SIOMODE_SMB, SIOMODE_ZP -DEFSIOACFG .EQU SER_9600_8N1 ; DEFAULT SERIAL LINE CONFIG -DEFSIOBCFG .EQU SER_9600_8N1 ; DEFAULT SERIAL LINE CONFIG -DEFSIODIV .EQU 8 ; 1=RC2014, SMB, 2/4/8/16/32/64/128/256 for ZP depending on jumper X5 -DEFSIOCLK .EQU 4915200 ; 2457600/4915200=ZP,7372800=RC/SMB - SIO FIXED OSC FREQUENCY -SIODEBUG .EQU FALSE ; -; -VDUENABLE .EQU FALSE ; TRUE FOR VDU BOARD SUPPORT -CVDUENABLE .EQU FALSE ; TRUE FOR CVDU BOARD SUPPORT -NECENABLE .EQU FALSE ; TRUE FOR uPD7220 BOARD SUPPORT -TMSENABLE .EQU FALSE ; TRUE FOR N8 (TMS9918) VIDEO/KBD SUPPORT -VGAENABLE .EQU FALSE ; TRUE FOR VGA VIDEO/KBD SUPPORT -VDUSIZ .EQU V80X25 ; VDU DISPLAY FORMAT V80X24, V80X25, V80X30 -; -SPKENABLE .EQU FALSE ; TRUE FOR RTC LATCH IOBIT SOUND -AYENABLE .EQU FALSE ; TRUE FOR AY PSG SOUND -; -MDENABLE .EQU TRUE ; TRUE FOR ROM/RAM DISK SUPPORT (ALMOST ALWAYS WANT THIS ENABLED) -MDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF MDENABLE = TRUE) -; -FDENABLE .EQU FALSE ; TRUE FOR FLOPPY SUPPORT -FDMODE .EQU FDMODE_DIO3 ; FDMODE_DIO, FDMODE_ZETA, FDMODE_DIDE, FDMODE_N8, FDMODE_DIO3 -FDTRACE .EQU 1 ; 0=SILENT, 1=FATAL ERRORS, 2=ALL ERRORS, 3=EVERYTHING (ONLY RELEVANT IF FDENABLE = TRUE) -FDMEDIA .EQU FDM144 ; FDM720, FDM144, FDM360, FDM120 (ONLY RELEVANT IF FDENABLE = TRUE) -FDMEDIAALT .EQU FDM720 ; ALTERNATE MEDIA TO TRY, SAME CHOICES AS ABOVE (ONLY RELEVANT IF FDMAUTO = TRUE) -FDMAUTO .EQU TRUE ; SELECT BETWEEN MEDIA OPTS ABOVE AUTOMATICALLY -; -RFENABLE .EQU FALSE ; TRUE FOR RAM FLOPPY SUPPORT -RFCNT .EQU 1 ; NUMBER OF RAM FLOPPY UNITS (MAX IS 2) -; -IDEENABLE .EQU FALSE ; TRUE FOR IDE SUPPORT -IDEMODE .EQU IDEMODE_DIO ; IDEMODE_DIO, IDEMODE_DIDE -IDETRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF IDEENABLE = TRUE) -IDE8BIT .EQU FALSE ; USE IDE 8BIT TRANSFERS (PROBABLY ONLY WORKS FOR CF CARDS!) -; -PPIDEENABLE .EQU FALSE ; TRUE FOR PPIDE SUPPORT (DO NOT COMBINE WITH DSKYENABLE) -PPIDEMODE .EQU PPIDEMODE_SBC ; PPIDEMODE_SBC, PPPIDEMODE_DIO3, PPIDEMODE_MFP, PPIDEMODE_N8, PPIDEMODE_RC -PPIDETRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPIDEENABLE = TRUE) -PPIDE8BIT .EQU FALSE ; USE IDE 8BIT TRANSFERS (PROBABLY ONLY WORKS FOR CF CARDS!) -; -SDENABLE .EQU FALSE ; TRUE FOR SD SUPPORT -SDMODE .EQU SDMODE_JUHA ; SDMODE_JUHA, SDMODE_CSIO, SDMODE_UART, SDMODE_PPI, SDMODE_DSD -SDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF IDEENABLE = TRUE) -SDCSIOFAST .EQU FALSE ; TABLE-DRIVEN BIT INVERTER -; -PRPENABLE .EQU FALSE ; TRUE FOR PROPIO SUPPORT -PRPSDENABLE .EQU TRUE ; TRUE FOR PROPIO SD SUPPORT -PRPSDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PRPSDENABLE = TRUE) -PRPCONENABLE .EQU TRUE ; TRUE FOR PROPIO CONSOLE SUPPORT (PS/2 KBD & VGA VIDEO) -; -PPPENABLE .EQU FALSE ; TRUE FOR PARPORTPROP SUPPORT -PPPSDENABLE .EQU TRUE ; TRUE FOR PARPORTPROP SD SUPPORT -PPPSDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPPENABLE = TRUE) -PPPCONENABLE .EQU TRUE ; TRUE FOR PROPIO CONSOLE SUPPORT (PS/2 KBD & VGA VIDEO) -; -HDSKENABLE .EQU FALSE ; TRUE FOR SIMH HDSK SUPPORT -HDSKTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF IDEENABLE = TRUE) -; -PPKTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPKENABLE = TRUE) -KBDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF KBDENABLE = TRUE) -; -TERMENABLE .EQU FALSE ; TERM PSEUDO DEVICE, WILL BE ENABLED IF A VDA IS ENABLED -ANSITRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF ANSIENABLE = TRUE) -; -BOOTTYPE .EQU BT_MENU ; BT_MENU (WAIT FOR KEYPRESS), BT_AUTO (BOOT_DEFAULT AFTER BOOT_TIMEOUT SECS) -BOOT_TIMEOUT .EQU 20 ; APPROX TIMEOUT IN SECONDS FOR AUTOBOOT, 0 FOR IMMEDIATE -BOOT_DEFAULT .EQU 'Z' ; SELECTION TO INVOKE AT TIMEOUT - -PIO_4P .EQU FALSE ; TRUE FOR ECB-4PIO PIO SUPPORT -PIO_ZP .EQU FALSE ; TRUE FOR ECB-ZILOG PERIPHERALS BOARD -PPI_SBC .EQU FALSE ; TRUE FOR SBC V2 8255 (IF NOT BEING USED FOR PPIDE) +; THIS FILE CONTAINS THE FULL SET OF DEFAULT CONFIGURATION SETTINGS FOR THE PLATFORM +; INDICATED ABOVE. THIS FILE SHOULD *NOT* NORMALLY BE CHANGED. INSTEAD, YOU SHOULD +; OVERRIDE ANY SETTINGS YOU WANT USING A CONFIGURATION FILE IN THE CONFIG DIRECTORY +; UNDER THIS DIRECTORY. +; +; THIS FILE CAN BE CONSIDERED A REFERENCE THAT LISTS ALL POSSIBLE CONFIGURATION SETTINGS +; FOR THE PLATFORM. +; +#DEFINE PLATFORM_NAME "SBC" +; +PLATFORM .EQU PLT_SBC ; PLT_[SBC|ZETA|ZETA2|N8|MK4|UNA|RCZ80|RCZ180|EZZ80|SCZ180|DYNO] +CPUFAM .EQU CPU_Z80 ; CPU FAMILY: CPU_[Z80|Z180] +BIOS .EQU BIOS_WBW ; HARDWARE BIOS: BIOS_[WBW|UNA] +BATCOND .EQU FALSE ; ENABLE LOW BATTERY WARNING MESSAGE +HBIOS_MUTEX .EQU FALSE ; ENABLE REENTRANT CALLS TO HBIOS (ADDS OVERHEAD) +USELZSA2 .EQU TRUE ; ENABLE FONT COMPRESSION +; +BOOT_TIMEOUT .EQU 0 ; AUTO BOOT TIMEOUT IN SECONDS, 0 TO DISABLE +; +CPUOSC .EQU 8000000 ; CPU OSC FREQ IN MHZ +INTMODE .EQU 0 ; INTERRUPTS: 0=NONE, 1=MODE 1, 2=MODE 2 +DEFSERCFG .EQU SER_38400_8N1 ; DEFAULT SERIAL LINE CONFIG (SEE STD.ASM) +; +RAMSIZE .EQU 512 ; SIZE OF RAM IN KB (MUST MATCH YOUR HARDWARE!!!) +MEMMGR .EQU MM_SBC ; MEMORY MANAGER: MM_[SBC|Z2|N8|Z180] +MPCL_RAM .EQU $78 ; SBC MEM MGR RAM PAGE SELECT REG (WRITE ONLY) +MPCL_ROM .EQU $7C ; SBC MEM MGR ROM PAGE SELECT REG (WRITE ONLY) +; +RTCIO .EQU $70 ; RTC LATCH REGISTER ADR +PPIBASE .EQU $60 ; PRIMARY PARALLEL PORT REGISTERS BASE ADR +; +KIOENABLE .EQU FALSE ; ENABLE ZILOG KIO SUPPORT +KIOBASE .EQU $80 ; KIO BASE I/O ADDRESS +; +CTCENABLE .EQU FALSE ; ENABLE ZILOG CTC SUPPORT +CTCMODE .EQU CTCMODE_ZP ; CTC MODE: CTCMODE_[ZP|Z2|EZ|RC] +CTCBASE .EQU $80 ; CTC BASE I/O ADDRESS +; +DIAGENABLE .EQU FALSE ; ENABLES OUTPUT TO 8 BIT LED DIAGNOSTIC PORT +DIAGPORT .EQU $00 ; DIAGNOSTIC PORT ADDRESS +DIAGDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON DIAGNOSTIC LEDS +; +LEDENABLE .EQU FALSE ; ENABLES STATUS LED (SINGLE LED) +LEDPORT .EQU $0E ; STATUS LED PORT ADDRESS +LEDDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON STATUS LED +; +DSKYENABLE .EQU FALSE ; ENABLES DSKY (DO NOT COMBINE WITH PPIDE) +; +CRTACT .EQU FALSE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP +VDAEMU .EQU EMUTYP_ANSI ; VDA EMULATION: EMUTYP_[TTY|ANSI] +ANSITRACE .EQU 1 ; ANSI DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPKTRACE .EQU 1 ; PPK DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +KBDTRACE .EQU 1 ; KBD DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPKKBLOUT .EQU KBD_US ; PPK KEYBOARD LANGUAGE: KBD_[US|DE] +KBDKBLOUT .EQU KBD_US ; KBD KEYBOARD LANGUAGE: KBD_[US|DE] +; +DSRTCENABLE .EQU TRUE ; DSRTC: ENABLE DS-1302 CLOCK DRIVER (DSRTC.ASM) +DSRTCMODE .EQU DSRTCMODE_STD ; DSRTC: OPERATING MODE: DSRTC_[STD|MFPIC] +DSRTCCHG .EQU FALSE ; DSRTC: FORCE BATTERY CHARGE ON (USE WITH CAUTION!!!) +; +BQRTCENABLE .EQU FALSE ; BQRTC: ENABLE BQ4845 CLOCK DRIVER (BQRTC.ASM) +BQRTC_BASE .EQU $50 ; BQRTC: I/O BASE ADDRESS +; +INTRTCENABLE .EQU FALSE ; ENABLE PERIODIC INTERRUPT CLOCK DRIVER (INTRTC.ASM) +; +HTIMENABLE .EQU FALSE ; ENABLE SIMH TIMER SUPPORT +SIMRTCENABLE .EQU FALSE ; ENABLE SIMH CLOCK DRIVER (SIMRTC.ASM) +; +UARTENABLE .EQU TRUE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +UARTOSC .EQU 1843200 ; UART: OSC FREQUENCY IN MHZ +UARTCFG .EQU DEFSERCFG ; UART: LINE CONFIG FOR UART PORTS +UARTCASSPD .EQU SER_300_8N1 ; UART: ECB CASSETTE UART DEFAULT SPEED +UARTSBC .EQU TRUE ; UART: AUTO-DETECT SBC/ZETA ONBOARD UART +UARTCAS .EQU TRUE ; UART: AUTO-DETECT ECB CASSETTE UART +UARTMFP .EQU TRUE ; UART: AUTO-DETECT MF/PIC UART +UART4 .EQU TRUE ; UART: AUTO-DETECT 4UART UART +UARTRC .EQU FALSE ; UART: AUTO-DETECT RC UART +; +ASCIENABLE .EQU FALSE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +; +ACIAENABLE .EQU FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +; +SIOENABLE .EQU TRUE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +SIODEBUG .EQU FALSE ; SIO: ENABLE DEBUG OUTPUT +SIOCNT .EQU 1 ; SIO: NUMBER OF CHIPS TO DETECT (1-2), 2 CHANNELS PER CHIP +SIO0MODE .EQU SIOMODE_ZP ; SIO 0: CHIP TYPE: SIOMODE_[RC|SMB|ZP|EZZ80] +SIO0CTCC .EQU -1 ; SIO 0: CTC CHANNEL CLOCK SCALER, (0-3), -1 FOR NONE +SIO0BASE .EQU $B0 ; SIO 0: REGISTERS BASE ADR +SIO0ACLK .EQU 4915200 ; SIO 0A: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO0ADIV .EQU 8 ; SIO 0A: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO0ACFG .EQU DEFSERCFG ; SIO 0A: SERIAL LINE CONFIG +SIO0BCLK .EQU 4915200 ; SIO 0B: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO0BDIV .EQU 8 ; SIO 0B: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO0BCFG .EQU DEFSERCFG ; SIO 0B: SERIAL LINE CONFIG +; +XIOCFG .EQU DEFSERCFG ; XIO: SERIAL LINE CONFIG +; +VDUENABLE .EQU FALSE ; VDU: ENABLE VDU VIDEO/KBD DRIVER (VDU.ASM) +VDUSIZ .EQU V80X25 ; VDU: DISPLAY FORMAT [V80X24|V80X25|V80X30] +CVDUENABLE .EQU FALSE ; CVDU: ENABLE CVDU VIDEO/KBD DRIVER (CVDU.ASM) +NECENABLE .EQU FALSE ; NEC: ENABLE NEC UPD7220 VIDEO/KBD DRIVER (NEC.ASM) +TMSENABLE .EQU FALSE ; TMS: ENABLE TMS9918 VIDEO/KBD DRIVER (TMS.ASM) +TMSMODE .EQU TMSMODE_SCG ; TMS: DRIVER MODE: TMSMODE_[SCG/N8] +VGAENABLE .EQU FALSE ; VGA: ENABLE VGA VIDEO/KBD DRIVER (VGA.ASM) +VGASIZ .EQU V80X25 ; VGA: DISPLAY FORMAT [V80X25|V80X30|V80X43] +; +SPKENABLE .EQU FALSE ; SPK: ENABLE RTC LATCH IOBIT SOUND DRIVER (SPK.ASM) +; +AYENABLE .EQU FALSE ; AY: ENABLE AY PSG SOUND DRIVER +AYMODE .EQU AYMODE_SCG ; AY: DRIVER MODE: AYMODE_[SCG/N8/RCZ80/RCZ180] +; +MDENABLE .EQU TRUE ; MD: ENABLE MEMORY (ROM/RAM) DISK DRIVER (MD.ASM) +MDTRACE .EQU 1 ; MD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +FDENABLE .EQU FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .EQU FDMODE_DIO3 ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3|DYNO] +FDTRACE .EQU 1 ; FD: TRACE LEVEL (0=NO,1=FATAL,2=ERRORS,3=ALL) +FDMEDIA .EQU FDM144 ; FD: DEFAULT MEDIA FORMAT FDM[720|144|360|120|111] +FDMEDIAALT .EQU FDM720 ; FD: ALTERNATE MEDIA FORMAT FDM[720|144|360|120|111] +FDMAUTO .EQU TRUE ; FD: AUTO SELECT DEFAULT/ALTERNATE MEDIA FORMATS +; +RFENABLE .EQU FALSE ; RF: ENABLE RAM FLOPPY DRIVER +RFCNT .EQU 1 ; RF: NUMBER OF RAM FLOPPY UNITS (1-4) +; +IDEENABLE .EQU FALSE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +IDETRACE .EQU 1 ; IDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +IDECNT .EQU 1 ; IDE: NUMBER OF IDE INTERFACES TO DETECT (1-3), 2 DRIVES EACH +IDE0MODE .EQU IDEMODE_DIO ; IDE 0: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE0BASE .EQU $20 ; IDE 0: IO BASE ADDRESS +IDE0DATLO .EQU $20 ; IDE 0: DATA LO PORT FOR 16-BIT I/O +IDE0DATHI .EQU $28 ; IDE 0: DATA HI PORT FOR 16-BIT I/O +IDE0A8BIT .EQU FALSE ; IDE 0A (MASTER): 8 BIT XFER +IDE0B8BIT .EQU FALSE ; IDE 0B (MASTER): 8 BIT XFER +IDE1MODE .EQU IDEMODE_NONE ; IDE 1: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE1BASE .EQU $00 ; IDE 1: IO BASE ADDRESS +IDE1DATLO .EQU $00 ; IDE 1: DATA LO PORT FOR 16-BIT I/O +IDE1DATHI .EQU $00 ; IDE 1: DATA HI PORT FOR 16-BIT I/O +IDE1A8BIT .EQU TRUE ; IDE 1A (MASTER): 8 BIT XFER +IDE1B8BIT .EQU TRUE ; IDE 1B (MASTER): 8 BIT XFER +IDE2MODE .EQU IDEMODE_NONE ; IDE 2: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE2BASE .EQU $00 ; IDE 2: IO BASE ADDRESS +IDE2DATLO .EQU $00 ; IDE 2: DATA LO PORT FOR 16-BIT I/O +IDE2DATHI .EQU $00 ; IDE 2: DATA HI PORT FOR 16-BIT I/O +IDE2A8BIT .EQU TRUE ; IDE 2A (MASTER): 8 BIT XFER +IDE2B8BIT .EQU TRUE ; IDE 2B (MASTER): 8 BIT XFER +; +PPIDEENABLE .EQU FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) +PPIDETRACE .EQU 1 ; PPIDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPIDECNT .EQU 1 ; PPIDE: NUMBER OF PPI CHIPS TO DETECT (1-3), 2 DRIVES PER CHIP +PPIDE0BASE .EQU $60 ; PPIDE 0: PPI REGISTERS BASE ADR +PPIDE0A8BIT .EQU FALSE ; PPIDE 0A (MASTER): 8 BIT XFER +PPIDE0B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +PPIDE1BASE .EQU $20 ; PPIDE 1: PPI REGISTERS BASE ADR +PPIDE1A8BIT .EQU FALSE ; PPIDE 1A (MASTER): 8 BIT XFER +PPIDE1B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +PPIDE2BASE .EQU $44 ; PPIDE 2: PPI REGISTERS BASE ADR +PPIDE2A8BIT .EQU FALSE ; PPIDE 2A (MASTER): 8 BIT XFER +PPIDE2B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +; +SDENABLE .EQU FALSE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDMODE .EQU SDMODE_JUHA ; SD: DRIVER MODE: SDMODE_[JUHA|N8|CSIO|PPI|UART|DSD|MK4|SC|MT] +SDCNT .EQU 1 ; SD: NUMBER OF SD CARD DEVICES (1-2), FOR DSD & SC ONLY +SDTRACE .EQU 1 ; SD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +SDCSIOFAST .EQU FALSE ; SD: ENABLE TABLE-DRIVEN BIT INVERTER IN CSIO MODE +; +PRPENABLE .EQU FALSE ; PRP: ENABLE ECB PROPELLER IO BOARD DRIVER (PRP.ASM) +PRPSDENABLE .EQU TRUE ; PRP: ENABLE PROPIO DRIVER SD CARD SUPPORT +PRPSDTRACE .EQU 1 ; PRP: SD CARD TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PRPCONENABLE .EQU TRUE ; PRP: ENABLE PROPIO DRIVER VIDEO/KBD SUPPORT +; +PPPENABLE .EQU FALSE ; PPP: ENABLE ZETA PARALLEL PORT PROPELLER BOARD DRIVER (PPP.ASM) +; +HDSKENABLE .EQU FALSE ; HDSK: ENABLE SIMH HDSK DISK DRIVER (HDSK.ASM) +HDSKTRACE .EQU 1 ; HDSK: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +PIO_4P .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB 4P BOARD +PIO4BASE .EQU $90 ; PIO: PIO REGISTERS BASE ADR FOR ECB 4P BOARD +PIO_ZP .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB ZILOG PERIPHERALS BOARD (PIO.ASM) +PIOZBASE .EQU $88 ; PIO: PIO REGISTERS BASE ADR FOR ECB ZP BOARD +PPI_SBC .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR 8255 CHIP +; +UFENABLE .EQU FALSE ; UF: ENABLE ECB USB FIFO DRIVER (UF.ASM) +UFBASE .EQU $0C ; UF: REGISTERS BASE ADR diff --git a/Source/HBIOS/cfg_scz180.asm b/Source/HBIOS/cfg_scz180.asm new file mode 100644 index 00000000..a92c4105 --- /dev/null +++ b/Source/HBIOS/cfg_scz180.asm @@ -0,0 +1,184 @@ +; +;================================================================================================== +; ROMWBW 2.X CONFIGURATION DEFAULTS FOR SC Z180 VARIANTS (SC126, SC130, ETC.) +;================================================================================================== +; +; THIS FILE CONTAINS THE FULL SET OF DEFAULT CONFIGURATION SETTINGS FOR THE PLATFORM +; INDICATED ABOVE. THIS FILE SHOULD *NOT* NORMALLY BE CHANGED. INSTEAD, YOU SHOULD +; OVERRIDE ANY SETTINGS YOU WANT USING A CONFIGURATION FILE IN THE CONFIG DIRECTORY +; UNDER THIS DIRECTORY. +; +; THIS FILE CAN BE CONSIDERED A REFERENCE THAT LISTS ALL POSSIBLE CONFIGURATION SETTINGS +; FOR THE PLATFORM. +; +#DEFINE PLATFORM_NAME "SCZ180" +; +PLATFORM .EQU PLT_SCZ180 ; PLT_[SBC|ZETA|ZETA2|N8|MK4|UNA|RCZ80|RCZ180|EZZ80|SCZ180|DYNO] +CPUFAM .EQU CPU_Z180 ; CPU FAMILY: CPU_[Z80|Z180] +BIOS .EQU BIOS_WBW ; HARDWARE BIOS: BIOS_[WBW|UNA] +BATCOND .EQU FALSE ; ENABLE LOW BATTERY WARNING MESSAGE +HBIOS_MUTEX .EQU FALSE ; ENABLE REENTRANT CALLS TO HBIOS (ADDS OVERHEAD) +USELZSA2 .EQU TRUE ; ENABLE FONT COMPRESSION +; +BOOT_TIMEOUT .EQU 0 ; AUTO BOOT TIMEOUT IN SECONDS, 0 TO DISABLE +; +CPUOSC .EQU 18432000 ; CPU OSC FREQ IN MHZ +INTMODE .EQU 2 ; INTERRUPTS: 0=NONE, 1=MODE 1, 2=MODE 2 +DEFSERCFG .EQU SER_115200_8N1 ; DEFAULT SERIAL LINE CONFIG (SEE STD.ASM) +; +RAMSIZE .EQU 512 ; SIZE OF RAM IN KB (MUST MATCH YOUR HARDWARE!!!) +MEMMGR .EQU MM_Z180 ; MEMORY MANAGER: MM_[SBC|Z2|N8|Z180] +RAMBIAS .EQU 512 ; OFFSET OF START OF RAM IN PHYSICAL ADDRESS SPACE +; +Z180_BASE .EQU $C0 ; Z180: I/O BASE ADDRESS FOR INTERNAL REGISTERS +Z180_CLKDIV .EQU 1 ; Z180: CHK DIV: 0=OSC/2, 1=OSC, 2=OSC*2 +Z180_MEMWAIT .EQU 0 ; Z180: MEMORY WAIT STATES (0-3) +Z180_IOWAIT .EQU 1 ; Z180: I/O WAIT STATES TO ADD ABOVE 1 W/S BUILT-IN (0-3) +; +RTCIO .EQU $0C ; RTC LATCH REGISTER ADR +; +KIOENABLE .EQU FALSE ; ENABLE ZILOG KIO SUPPORT +KIOBASE .EQU $80 ; KIO BASE I/O ADDRESS +; +CTCENABLE .EQU FALSE ; ENABLE ZILOG CTC SUPPORT +; +DIAGENABLE .EQU TRUE ; ENABLES OUTPUT TO 8 BIT LED DIAGNOSTIC PORT +DIAGPORT .EQU $0D ; DIAGNOSTIC PORT ADDRESS +DIAGDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON DIAGNOSTIC LEDS +; +LEDENABLE .EQU FALSE ; ENABLES STATUS LED (SINGLE LED) +LEDPORT .EQU $0E ; STATUS LED PORT ADDRESS +LEDDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON STATUS LED +; +DSKYENABLE .EQU FALSE ; ENABLES DSKY (DO NOT COMBINE WITH PPIDE) +; +CRTACT .EQU FALSE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP +VDAEMU .EQU EMUTYP_ANSI ; VDA EMULATION: EMUTYP_[TTY|ANSI] +ANSITRACE .EQU 1 ; ANSI DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +DSRTCENABLE .EQU TRUE ; DSRTC: ENABLE DS-1302 CLOCK DRIVER (DSRTC.ASM) +DSRTCMODE .EQU DSRTCMODE_STD ; DSRTC: OPERATING MODE: DSRTC_[STD|MFPIC] +DSRTCCHG .EQU FALSE ; DSRTC: FORCE BATTERY CHARGE ON (USE WITH CAUTION!!!) +; +BQRTCENABLE .EQU FALSE ; BQRTC: ENABLE BQ4845 CLOCK DRIVER (BQRTC.ASM) +BQRTC_BASE .EQU $50 ; BQRTC: I/O BASE ADDRESS +; +INTRTCENABLE .EQU FALSE ; ENABLE PERIODIC INTERRUPT CLOCK DRIVER (INTRTC.ASM) +; +HTIMENABLE .EQU FALSE ; ENABLE SIMH TIMER SUPPORT +SIMRTCENABLE .EQU FALSE ; ENABLE SIMH CLOCK DRIVER (SIMRTC.ASM) +; +UARTENABLE .EQU TRUE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +UARTOSC .EQU 1843200 ; UART: OSC FREQUENCY IN MHZ +UARTCFG .EQU DEFSERCFG ; UART: LINE CONFIG FOR UART PORTS +UARTSBC .EQU FALSE ; UART: AUTO-DETECT SBC/ZETA ONBOARD UART +UARTCAS .EQU FALSE ; UART: AUTO-DETECT ECB CASSETTE UART +UARTMFP .EQU FALSE ; UART: AUTO-DETECT MF/PIC UART +UART4 .EQU FALSE ; UART: AUTO-DETECT 4UART UART +UARTRC .EQU TRUE ; UART: AUTO-DETECT RC UART +; +ASCIENABLE .EQU TRUE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +ASCI0CFG .EQU DEFSERCFG ; ASCI 0: SERIAL LINE CONFIG +ASCI1CFG .EQU DEFSERCFG ; ASCI 1: SERIAL LINE CONFIG +; +ACIAENABLE .EQU FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +; +SIOENABLE .EQU TRUE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +SIODEBUG .EQU FALSE ; SIO: ENABLE DEBUG OUTPUT +SIOCNT .EQU 2 ; SIO: NUMBER OF CHIPS TO DETECT (1-2), 2 CHANNELS PER CHIP +SIO0MODE .EQU SIOMODE_RC ; SIO 0: CHIP TYPE: SIOMODE_[RC|SMB|ZP|EZZ80] +SIO0CTCC .EQU -1 ; SIO 0: CTC CHANNEL CLOCK SCALER, (0-3), -1 FOR NONE +SIO0BASE .EQU $80 ; SIO 0: REGISTERS BASE ADR +SIO0ACLK .EQU 7372800 ; SIO 0A: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO0ADIV .EQU 1 ; SIO 0A: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO0ACFG .EQU SER_115200_8N1 ; SIO 0A: SERIAL LINE CONFIG +SIO0BCLK .EQU 7372800 ; SIO 0B: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO0BDIV .EQU 1 ; SIO 0B: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO0BCFG .EQU SER_115200_8N1 ; SIO 0B: SERIAL LINE CONFIG +SIO1MODE .EQU SIOMODE_RC ; SIO 1: CHIP TYPE: SIOMODE_[RC|SMB|ZP|EZZ80] +SIO1CTCC .EQU -1 ; SIO 1: CTC CHANNEL CLOCK SCALER, (0-3), -1 FOR NONE +SIO1BASE .EQU $84 ; SIO 1: REGISTERS BASE ADR +SIO1ACLK .EQU 7372800 ; SIO 1A: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO1ADIV .EQU 1 ; SIO 1A: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO1ACFG .EQU SER_115200_8N1 ; SIO 1A: SERIAL LINE CONFIG +SIO1BCLK .EQU 7372800 ; SIO 1B: OSC FREQ IN HZ, ZP=2457600/4915200, RC/SMB=7372800 +SIO1BDIV .EQU 1 ; SIO 1B: SERIAL CLOCK DIVIDER, RC2014/SMB=1, ZP=2/4/8/16/32/64/128/256 (X5) +SIO1BCFG .EQU SER_115200_8N1 ; SIO 1B: SERIAL LINE CONFIG +; +XIOCFG .EQU DEFSERCFG ; XIO: SERIAL LINE CONFIG +; +VDUENABLE .EQU FALSE ; VDU: ENABLE VDU VIDEO/KBD DRIVER (VDU.ASM) +CVDUENABLE .EQU FALSE ; CVDU: ENABLE CVDU VIDEO/KBD DRIVER (CVDU.ASM) +NECENABLE .EQU FALSE ; NEC: ENABLE NEC UPD7220 VIDEO/KBD DRIVER (NEC.ASM) +TMSENABLE .EQU FALSE ; TMS: ENABLE TMS9918 VIDEO/KBD DRIVER (TMS.ASM) +VGAENABLE .EQU FALSE ; VGA: ENABLE VGA VIDEO/KBD DRIVER (VGA.ASM) +; +SPKENABLE .EQU FALSE ; SPK: ENABLE RTC LATCH IOBIT SOUND DRIVER (SPK.ASM) +; +AYENABLE .EQU FALSE ; AY: ENABLE AY PSG SOUND DRIVER +AYMODE .EQU AYMODE_RCZ180 ; AY: DRIVER MODE: AYMODE_[SCG/N8/RCZ80/RCZ180] +; +MDENABLE .EQU TRUE ; MD: ENABLE MEMORY (ROM/RAM) DISK DRIVER (MD.ASM) +MDTRACE .EQU 1 ; MD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +FDENABLE .EQU FALSE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .EQU FDMODE_RCWDC ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3|DYNO] +FDTRACE .EQU 1 ; FD: TRACE LEVEL (0=NO,1=FATAL,2=ERRORS,3=ALL) +FDMEDIA .EQU FDM144 ; FD: DEFAULT MEDIA FORMAT FDM[720|144|360|120|111] +FDMEDIAALT .EQU FDM720 ; FD: ALTERNATE MEDIA FORMAT FDM[720|144|360|120|111] +FDMAUTO .EQU TRUE ; FD: AUTO SELECT DEFAULT/ALTERNATE MEDIA FORMATS +; +RFENABLE .EQU FALSE ; RF: ENABLE RAM FLOPPY DRIVER +; +IDEENABLE .EQU FALSE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +IDETRACE .EQU 1 ; IDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +IDECNT .EQU 1 ; IDE: NUMBER OF IDE INTERFACES TO DETECT (1-3), 2 DRIVES EACH +IDE0MODE .EQU IDEMODE_RC ; IDE 0: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE0BASE .EQU $10 ; IDE 0: IO BASE ADDRESS +IDE0DATLO .EQU $00 ; IDE 0: DATA LO PORT FOR 16-BIT I/O +IDE0DATHI .EQU $00 ; IDE 0: DATA HI PORT FOR 16-BIT I/O +IDE0A8BIT .EQU TRUE ; IDE 0A (MASTER): 8 BIT XFER +IDE0B8BIT .EQU TRUE ; IDE 0B (MASTER): 8 BIT XFER +IDE1MODE .EQU IDEMODE_NONE ; IDE 1: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE1BASE .EQU $00 ; IDE 1: IO BASE ADDRESS +IDE1DATLO .EQU $00 ; IDE 1: DATA LO PORT FOR 16-BIT I/O +IDE1DATHI .EQU $00 ; IDE 1: DATA HI PORT FOR 16-BIT I/O +IDE1A8BIT .EQU TRUE ; IDE 1A (MASTER): 8 BIT XFER +IDE1B8BIT .EQU TRUE ; IDE 1B (MASTER): 8 BIT XFER +IDE2MODE .EQU IDEMODE_NONE ; IDE 2: DRIVER MODE: IDEMODE_[DIO|DIDE|MK4|RC] +IDE2BASE .EQU $00 ; IDE 2: IO BASE ADDRESS +IDE2DATLO .EQU $00 ; IDE 2: DATA LO PORT FOR 16-BIT I/O +IDE2DATHI .EQU $00 ; IDE 2: DATA HI PORT FOR 16-BIT I/O +IDE2A8BIT .EQU TRUE ; IDE 2A (MASTER): 8 BIT XFER +IDE2B8BIT .EQU TRUE ; IDE 2B (MASTER): 8 BIT XFER +; +PPIDEENABLE .EQU FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) +PPIDETRACE .EQU 1 ; PPIDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPIDECNT .EQU 1 ; PPIDE: NUMBER OF PPI CHIPS TO DETECT (1-3), 2 DRIVES PER CHIP +PPIDE0BASE .EQU $20 ; PPIDE 0: PPI REGISTERS BASE ADR +PPIDE0A8BIT .EQU FALSE ; PPIDE 0A (MASTER): 8 BIT XFER +PPIDE0B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +PPIDE1BASE .EQU $00 ; PPIDE 1: PPI REGISTERS BASE ADR +PPIDE1A8BIT .EQU FALSE ; PPIDE 1A (MASTER): 8 BIT XFER +PPIDE1B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +PPIDE2BASE .EQU $00 ; PPIDE 2: PPI REGISTERS BASE ADR +PPIDE2A8BIT .EQU FALSE ; PPIDE 2A (MASTER): 8 BIT XFER +PPIDE2B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +; +SDENABLE .EQU TRUE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDMODE .EQU SDMODE_SC ; SD: DRIVER MODE: SDMODE_[JUHA|N8|CSIO|PPI|UART|DSD|MK4|SC|MT] +SDCNT .EQU 1 ; SD: NUMBER OF SD CARD DEVICES (1-2), FOR DSD & SC ONLY +SDTRACE .EQU 1 ; SD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +SDCSIOFAST .EQU FALSE ; SD: ENABLE TABLE-DRIVEN BIT INVERTER IN CSIO MODE +; +PRPENABLE .EQU FALSE ; PRP: ENABLE ECB PROPELLER IO BOARD DRIVER (PRP.ASM) +; +PPPENABLE .EQU FALSE ; PPP: ENABLE ZETA PARALLEL PORT PROPELLER BOARD DRIVER (PPP.ASM) +; +HDSKENABLE .EQU FALSE ; HDSK: ENABLE SIMH HDSK DISK DRIVER (HDSK.ASM) +; +PIO_4P .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB 4P BOARD +PIO_ZP .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB ZILOG PERIPHERALS BOARD (PIO.ASM) +PPI_SBC .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR 8255 CHIP +; +UFENABLE .EQU FALSE ; UF: ENABLE ECB USB FIFO DRIVER (UF.ASM) diff --git a/Source/HBIOS/cfg_una.asm b/Source/HBIOS/cfg_una.asm index a451e466..058eca9e 100644 --- a/Source/HBIOS/cfg_una.asm +++ b/Source/HBIOS/cfg_una.asm @@ -3,13 +3,26 @@ ; ROMWBW 2.X CONFIGURATION DEFAULTS FOR UNA ;================================================================================================== ; -; BUILD CONFIGURATION OPTIONS +; THIS FILE CONTAINS THE FULL SET OF DEFAULT CONFIGURATION SETTINGS FOR THE PLATFORM +; INDICATED ABOVE. THIS FILE SHOULD *NOT* NORMALLY BE CHANGED. INSTEAD, YOU SHOULD +; OVERRIDE ANY SETTINGS YOU WANT USING A CONFIGURATION FILE IN THE CONFIG DIRECTORY +; UNDER THIS DIRECTORY. ; -CPUOSC .EQU 18432000 ; CPU OSC FREQ -RAMSIZE .EQU 512 ; SIZE OF RAM IN KB, MUST MATCH YOUR HARDWARE!!! +; THIS FILE CAN BE CONSIDERED A REFERENCE THAT LISTS ALL POSSIBLE CONFIGURATION SETTINGS +; FOR THE PLATFORM. ; -DSKYENABLE .EQU FALSE ; TRUE FOR DSKY SUPPORT (DO NOT COMBINE WITH PPIDE) +#DEFINE PLATFORM_NAME "UNA" ; -BOOTTYPE .EQU BT_MENU ; BT_MENU (WAIT FOR KEYPRESS), BT_AUTO (BOOT_DEFAULT AFTER BOOT_TIMEOUT SECS) -BOOT_TIMEOUT .EQU 21 ; APPROX TIMEOUT IN SECONDS FOR AUTOBOOT, 0 FOR IMMEDIATE -BOOT_DEFAULT .EQU 'Z' ; SELECTION TO INVOKE AT TIMEOUT +PLATFORM .EQU PLT_UNA ; PLT_[SBC|ZETA|ZETA2|N8|MK4|UNA|RCZ80|RCZ180|EZZ80|SCZ180|DYNO] +BIOS .EQU BIOS_UNA ; HARDWARE BIOS: BIOS_[WBW|UNA] +; +BOOT_TIMEOUT .EQU 0 ; AUTO BOOT TIMEOUT IN SECONDS, 0 TO DISABLE +; +CPUOSC .EQU 18432000 ; CPU OSC FREQ IN MHZ +INTMODE .EQU 0 ; INTERRUPTS: 0=NONE, 1=MODE 1, 2=MODE 2 +; +RAMSIZE .EQU 512 ; SIZE OF RAM IN KB (MUST MATCH YOUR HARDWARE!!!) +; +RTCIO .EQU $70 ; RTC LATCH REGISTER ADR +; +DSKYENABLE .EQU FALSE ; ENABLES DSKY (DO NOT COMBINE WITH PPIDE) diff --git a/Source/HBIOS/cfg_zeta.asm b/Source/HBIOS/cfg_zeta.asm index 0933a06c..ca9dfb7e 100644 --- a/Source/HBIOS/cfg_zeta.asm +++ b/Source/HBIOS/cfg_zeta.asm @@ -3,75 +3,134 @@ ; ROMWBW 2.X CONFIGURATION DEFAULTS FOR ZETA V1 ;================================================================================================== ; -; BUILD CONFIGURATION OPTIONS +; THIS FILE CONTAINS THE FULL SET OF DEFAULT CONFIGURATION SETTINGS FOR THE PLATFORM +; INDICATED ABOVE. THIS FILE SHOULD *NOT* NORMALLY BE CHANGED. INSTEAD, YOU SHOULD +; OVERRIDE ANY SETTINGS YOU WANT USING A CONFIGURATION FILE IN THE CONFIG DIRECTORY +; UNDER THIS DIRECTORY. ; -CPUOSC .EQU 20000000 ; CPU OSC FREQ -RAMSIZE .EQU 512 ; SIZE OF RAM IN KB, MUST MATCH YOUR HARDWARE!!! -DEFSERCFG .EQU SER_38400_8N1 ; DEFAULT SERIAL LINE CONFIG (SHOULD MATCH ABOVE) -INTMODE .EQU 0 ; 0=NONE, 1=INT MODE 1, 2=INT MODE 2 +; THIS FILE CAN BE CONSIDERED A REFERENCE THAT LISTS ALL POSSIBLE CONFIGURATION SETTINGS +; FOR THE PLATFORM. ; -CRTACT .EQU FALSE ; CRT ACTIVATION AT STARTUP -VDAEMU .EQU EMUTYP_ANSI ; DEFAULT VDA EMULATION (EMUTYP_TTY, EMUTYP_ANSI, ...) +#DEFINE PLATFORM_NAME "ZETA" ; -DSKYENABLE .EQU FALSE ; TRUE FOR DSKY SUPPORT (DO NOT COMBINE WITH PPIDE) +PLATFORM .EQU PLT_ZETA ; PLT_[SBC|ZETA|ZETA2|N8|MK4|UNA|RCZ80|RCZ180|EZZ80|SCZ180|DYNO] +CPUFAM .EQU CPU_Z80 ; CPU_[Z80|Z180]: CPU FAMILY +BIOS .EQU BIOS_WBW ; BIOS_[WBW|UNA]: HARDWARE BIOS +BATCOND .EQU FALSE ; ENABLE LOW BATTERY WARNING MESSAGE +HBIOS_MUTEX .EQU FALSE ; ENABLE REENTRANT CALLS TO HBIOS (ADDS OVERHEAD) +USELZSA2 .EQU TRUE ; ENABLE FONT COMPRESSION ; -HTIMENABLE .EQU FALSE ; TRUE FOR SIMH TIMER SUPPORT -SIMRTCENABLE .EQU FALSE ; SIMH CLOCK DRIVER -DSRTCENABLE .EQU TRUE ; DS-1302 CLOCK DRIVER -DSRTCMODE .EQU DSRTCMODE_STD ; DSRTCMODE_STD, DSRTCMODE_MFPIC -DSRTCCHG .EQU FALSE ; DS-1302 CONFIGURE CHARGE ON (TRUE) OR OFF (FALSE) +BOOT_TIMEOUT .EQU 0 ; AUTO BOOT TIMEOUT IN SECONDS, 0 TO DISABLE ; -ASCIENABLE .EQU FALSE ; TRUE FOR Z180 ASCI SUPPORT -UARTENABLE .EQU TRUE ; TRUE FOR UART SUPPORT (ALMOST ALWAYS WANT THIS TO BE TRUE) -UARTOSC .EQU 1843200 ; UART OSC FREQUENCY -SIOENABLE .EQU FALSE ; TRUE FOR ZILOG SIO/2 SUPPORT -SIOMODE .EQU SIOMODE_RC ; SIOMODE_RC, SIOMODE_SMB -ACIAENABLE .EQU FALSE ; TRUE FOR MOTOROLA 6850 ACIA SUPPORT +CPUOSC .EQU 20000000 ; CPU OSC FREQ IN MHZ +INTMODE .EQU 0 ; INTERRUPT MODE: 0=NONE, 1=MODE 1, 2=MODE 2 +DEFSERCFG .EQU SER_38400_8N1 ; DEFAULT SERIAL LINE CONFIG (SEE STD.ASM) ; -VDUENABLE .EQU FALSE ; TRUE FOR VDU BOARD SUPPORT -CVDUENABLE .EQU FALSE ; TRUE FOR CVDU BOARD SUPPORT -NECENABLE .EQU FALSE ; TRUE FOR uPD7220 BOARD SUPPORT -TMSENABLE .EQU FALSE ; TRUE FOR N8 (TMS9918) VIDEO/KBD SUPPORT -VGAENABLE .EQU FALSE ; TRUE FOR VGA VIDEO/KBD SUPPORT +RAMSIZE .EQU 512 ; SIZE OF RAM IN KB (MUST MATCH YOUR HARDWARE!!!) +MEMMGR .EQU MM_SBC ; MM_[SBC|Z2|N8|Z180]: MEMORY MANAGER +MPCL_RAM .EQU $78 ; SBC MEM MGR RAM PAGE SELECT REG (WRITE ONLY) +MPCL_ROM .EQU $7C ; SBC MEM MGR ROM PAGE SELECT REG (WRITE ONLY) ; -SPKENABLE .EQU FALSE ; TRUE FOR RTC LATCH IOBIT SOUND -AYENABLE .EQU FALSE ; TRUE FOR AY PSG SOUND +RTCIO .EQU $70 ; RTC LATCH REGISTER ADR +PPIBASE .EQU $60 ; PRIMARY PARALLEL PORT REGISTERS BASE ADR ; -MDENABLE .EQU TRUE ; TRUE FOR ROM/RAM DISK SUPPORT (ALMOST ALWAYS WANT THIS ENABLED) -MDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF MDENABLE = TRUE) +KIOENABLE .EQU FALSE ; ENABLE ZILOG KIO SUPPORT +KIOBASE .EQU $80 ; KIO BASE I/O ADDRESS ; -FDENABLE .EQU TRUE ; TRUE FOR FLOPPY SUPPORT -FDMODE .EQU FDMODE_ZETA ; FDMODE_DIO, FDMODE_ZETA, FDMODE_DIDE, FDMODE_N8, FDMODE_DIO3 -FDTRACE .EQU 1 ; 0=SILENT, 1=FATAL ERRORS, 2=ALL ERRORS, 3=EVERYTHING (ONLY RELEVANT IF FDENABLE = TRUE) -FDMEDIA .EQU FDM144 ; FDM720, FDM144, FDM360, FDM120 (ONLY RELEVANT IF FDENABLE = TRUE) -FDMEDIAALT .EQU FDM720 ; ALTERNATE MEDIA TO TRY, SAME CHOICES AS ABOVE (ONLY RELEVANT IF FDMAUTO = TRUE) -FDMAUTO .EQU TRUE ; SELECT BETWEEN MEDIA OPTS ABOVE AUTOMATICALLY +CTCENABLE .EQU FALSE ; ENABLE ZILOG CTC SUPPORT ; -RFENABLE .EQU FALSE ; TRUE FOR RAM FLOPPY SUPPORT +DIAGENABLE .EQU FALSE ; ENABLES OUTPUT TO 8 BIT LED DIAGNOSTIC PORT +DIAGPORT .EQU $00 ; DIAGNOSTIC PORT ADDRESS +DIAGDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON DIAGNOSTIC LEDS ; -IDEENABLE .EQU FALSE ; TRUE FOR IDE SUPPORT +LEDENABLE .EQU FALSE ; ENABLES STATUS LED (SINGLE LED) +LEDPORT .EQU $0E ; STATUS LED PORT ADDRESS +LEDDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON STATUS LED ; -PPIDEENABLE .EQU FALSE ; TRUE FOR PPIDE SUPPORT (DO NOT COMBINE WITH DSKYENABLE) -PPIDEMODE .EQU PPIDEMODE_SBC ; PPIDEMODE_SBC, PPPIDEMODE_DIO3, PPIDEMODE_MFP, PPIDEMODE_N8, PPIDEMODE_RC -PPIDETRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPIDEENABLE = TRUE) -PPIDE8BIT .EQU FALSE ; USE IDE 8BIT TRANSFERS (PROBABLY ONLY WORKS FOR CF CARDS!) +DSKYENABLE .EQU FALSE ; ENABLES DSKY (DO NOT COMBINE WITH PPIDE) ; -SDENABLE .EQU FALSE ; TRUE FOR SD SUPPORT -SDMODE .EQU SDMODE_PPI ; SDMODE_JUHA, SDMODE_CSIO, SDMODE_UART, SDMODE_PPI, SDMODE_DSD -SDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF IDEENABLE = TRUE) -SDCSIOFAST .EQU FALSE ; TABLE-DRIVEN BIT INVERTER +CRTACT .EQU FALSE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP +VDAEMU .EQU EMUTYP_ANSI ; VDA EMULATION: EMUTYP_[TTY|ANSI] +ANSITRACE .EQU 1 ; ANSI DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) ; -PRPENABLE .EQU FALSE ; TRUE FOR PROPIO SUPPORT +DSRTCENABLE .EQU TRUE ; DSRTC: ENABLE DS-1302 CLOCK DRIVER (DSRTC.ASM) +DSRTCMODE .EQU DSRTCMODE_STD ; DSRTC: OPERATING MODE: DSRTC_[STD|MFPIC] +DSRTCCHG .EQU FALSE ; DSRTC: FORCE BATTERY CHARGE ON (USE WITH CAUTION!!!) ; -PPPENABLE .EQU FALSE ; TRUE FOR PARPORTPROP SUPPORT -PPPSDENABLE .EQU TRUE ; TRUE FOR PARPORTPROP SD SUPPORT -PPPSDTRACE .EQU 1 ; 0=SILENT, 1=ERRORS, 2=EVERYTHING (ONLY RELEVANT IF PPPENABLE = TRUE) -PPPCONENABLE .EQU TRUE ; TRUE FOR PROPIO CONSOLE SUPPORT (PS/2 KBD & VGA VIDEO) +BQRTCENABLE .EQU FALSE ; BQRTC: ENABLE BQ4845 CLOCK DRIVER (BQRTC.ASM) +BQRTC_BASE .EQU $50 ; BQRTC: I/O BASE ADDRESS ; -HDSKENABLE .EQU FALSE ; TRUE FOR SIMH HDSK SUPPORT +HTIMENABLE .EQU FALSE ; ENABLE SIMH TIMER SUPPORT +SIMRTCENABLE .EQU FALSE ; ENABLE SIMH CLOCK DRIVER (SIMRTC.ASM) ; -TERMENABLE .EQU FALSE ; TERM PSEUDO DEVICE, WILL BE ENABLED IF A VDA IS ENABLED +INTRTCENABLE .EQU FALSE ; ENABLE PERIODIC INTERRUPT CLOCK DRIVER (INTRTC.ASM) ; -BOOTTYPE .EQU BT_MENU ; BT_MENU (WAIT FOR KEYPRESS), BT_AUTO (BOOT_DEFAULT AFTER BOOT_TIMEOUT SECS) -BOOT_TIMEOUT .EQU 20 ; APPROX TIMEOUT IN SECONDS FOR AUTOBOOT, 0 FOR IMMEDIATE -BOOT_DEFAULT .EQU 'Z' ; SELECTION TO INVOKE AT TIMEOUT +UARTENABLE .EQU TRUE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +UARTOSC .EQU 1843200 ; UART: OSC FREQUENCY IN MHZ +UARTCFG .EQU DEFSERCFG ; UART: LINE CONFIG FOR UART PORTS +UARTCASSPD .EQU SER_300_8N1 ; UART: ECB CASSETTE UART DEFAULT SPEED +UARTSBC .EQU TRUE ; UART: AUTO-DETECT SBC/ZETA ONBOARD UART +UARTCAS .EQU FALSE ; UART: AUTO-DETECT ECB CASSETTE UART +UARTMFP .EQU FALSE ; UART: AUTO-DETECT MF/PIC UART +UART4 .EQU FALSE ; UART: AUTO-DETECT 4UART UART +UARTRC .EQU FALSE ; UART: AUTO-DETECT RC UART +; +ASCIENABLE .EQU FALSE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +; +ACIAENABLE .EQU FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +; +SIOENABLE .EQU FALSE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +; +XIOCFG .EQU DEFSERCFG ; XIO: SERIAL LINE CONFIG +; +VDUENABLE .EQU FALSE ; VDU: ENABLE VDU VIDEO/KBD DRIVER (VDU.ASM) +CVDUENABLE .EQU FALSE ; CVDU: ENABLE CVDU VIDEO/KBD DRIVER (CVDU.ASM) +NECENABLE .EQU FALSE ; NEC: ENABLE NEC UPD7220 VIDEO/KBD DRIVER (NEC.ASM) +TMSENABLE .EQU FALSE ; TMS: ENABLE TMS9918 VIDEO/KBD DRIVER (TMS.ASM) +VGAENABLE .EQU FALSE ; VGA: ENABLE VGA VIDEO/KBD DRIVER (VGA.ASM) +; +SPKENABLE .EQU FALSE ; SPK: ENABLE RTC LATCH IOBIT SOUND DRIVER (SPK.ASM) +; +AYENABLE .EQU FALSE ; AY: ENABLE AY PSG SOUND DRIVER +; +MDENABLE .EQU TRUE ; MD: ENABLE MEMORY (ROM/RAM) DISK DRIVER (MD.ASM) +MDTRACE .EQU 1 ; MD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +FDENABLE .EQU TRUE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .EQU FDMODE_ZETA ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3|DYNO] +FDTRACE .EQU 1 ; FD: TRACE LEVEL (0=NO,1=FATAL,2=ERRORS,3=ALL) +FDMEDIA .EQU FDM144 ; FD: DEFAULT MEDIA FORMAT FDM[720|144|360|120|111] +FDMEDIAALT .EQU FDM720 ; FD: ALTERNATE MEDIA FORMAT FDM[720|144|360|120|111] +FDMAUTO .EQU TRUE ; FD: AUTO SELECT DEFAULT/ALTERNATE MEDIA FORMATS +; +RFENABLE .EQU FALSE ; RF: ENABLE RAM FLOPPY DRIVER +; +IDEENABLE .EQU FALSE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +; +PPIDEENABLE .EQU FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) +PPIDETRACE .EQU 1 ; PPIDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPIDECNT .EQU 1 ; PPIDE: NUMBER OF PPI CHIPS TO DETECT (1-3), 2 DRIVES PER CHIP +PPIDE0BASE .EQU $60 ; PPIDE 0: PPI REGISTERS BASE ADR +PPIDE0A8BIT .EQU FALSE ; PPIDE 0A (MASTER): 8 BIT XFER +PPIDE0B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +; +SDENABLE .EQU FALSE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDMODE .EQU SDMODE_PPI ; SD: DRIVER MODE: SDMODE_[JUHA|N8|CSIO|PPI|UART|DSD|MK4|SC|MT] +SDCNT .EQU 1 ; SD: NUMBER OF SD CARD DEVICES (1-2), FOR DSD & SC ONLY +SDTRACE .EQU 1 ; SD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +SDCSIOFAST .EQU FALSE ; SD: ENABLE TABLE-DRIVEN BIT INVERTER IN CSIO MODE +; +PRPENABLE .EQU FALSE ; PRP: ENABLE ECB PROPELLER IO BOARD DRIVER (PRP.ASM) +; +PPPENABLE .EQU FALSE ; PPP: ENABLE ZETA PARALLEL PORT PROPELLER BOARD DRIVER (PPP.ASM) +PPPSDENABLE .EQU TRUE ; PPP: ENABLE PPP DRIVER SD CARD SUPPORT +PPPSDTRACE .EQU 1 ; PPP: SD CARD TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPPCONENABLE .EQU TRUE ; PPP: ENABLE PPP DRIVER VIDEO/KBD SUPPORT +; +HDSKENABLE .EQU FALSE ; HDSK: ENABLE SIMH HDSK DISK DRIVER (HDSK.ASM) +; +PIO_4P .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB 4P BOARD +PIO_ZP .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB ZILOG PERIPHERALS BOARD (PIO.ASM) +PPI_SBC .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR 8255 CHIP +; +UFENABLE .EQU FALSE ; UF: ENABLE ECB USB FIFO DRIVER (UF.ASM) diff --git a/Source/HBIOS/cfg_zeta2.asm b/Source/HBIOS/cfg_zeta2.asm index 753f6755..35eaddbe 100644 --- a/Source/HBIOS/cfg_zeta2.asm +++ b/Source/HBIOS/cfg_zeta2.asm @@ -3,8 +3,139 @@ ; ROMWBW 2.X CONFIGURATION DEFAULTS FOR ZETA V2 ;================================================================================================== ; -; BUILD CONFIGURATION OPTIONS +; THIS FILE CONTAINS THE FULL SET OF DEFAULT CONFIGURATION SETTINGS FOR THE PLATFORM +; INDICATED ABOVE. THIS FILE SHOULD *NOT* NORMALLY BE CHANGED. INSTEAD, YOU SHOULD +; OVERRIDE ANY SETTINGS YOU WANT USING A CONFIGURATION FILE IN THE CONFIG DIRECTORY +; UNDER THIS DIRECTORY. ; -#INCLUDE "cfg_zeta.asm" ; USE ZETA CONFIG TO START +; THIS FILE CAN BE CONSIDERED A REFERENCE THAT LISTS ALL POSSIBLE CONFIGURATION SETTINGS +; FOR THE PLATFORM. ; -FDMODE .SET FDMODE_ZETA2 ; FDMODE_DIO, FDMODE_ZETA, FDMODE_DIDE, FDMODE_N8, FDMODE_DIO3 +#DEFINE PLATFORM_NAME "ZETA V2" +; +PLATFORM .EQU PLT_ZETA2 ; PLT_[SBC|ZETA|ZETA2|N8|MK4|UNA|RCZ80|RCZ180|EZZ80|SCZ180|DYNO] +CPUFAM .EQU CPU_Z80 ; CPU FAMILY: CPU_[Z80|Z180] +BIOS .EQU BIOS_WBW ; HARDWARE BIOS: BIOS_[WBW|UNA] +BATCOND .EQU FALSE ; ENABLE LOW BATTERY WARNING MESSAGE +HBIOS_MUTEX .EQU FALSE ; ENABLE REENTRANT CALLS TO HBIOS (ADDS OVERHEAD) +USELZSA2 .EQU TRUE ; ENABLE FONT COMPRESSION +; +BOOT_TIMEOUT .EQU 0 ; AUTO BOOT TIMEOUT IN SECONDS, 0 TO DISABLE +; +CPUOSC .EQU 20000000 ; CPU OSC FREQ IN MHZ +INTMODE .EQU 2 ; INTERRUPTS: 0=NONE, 1=MODE 1, 2=MODE 2 +DEFSERCFG .EQU SER_38400_8N1 ; DEFAULT SERIAL LINE CONFIG (SEE STD.ASM) +; +RAMSIZE .EQU 512 ; SIZE OF RAM IN KB (MUST MATCH YOUR HARDWARE!!!) +MEMMGR .EQU MM_Z2 ; MEMORY MANAGER: MM_[SBC|Z2|N8|Z180] +MPGSEL_0 .EQU $78 ; Z2 MEM MGR BANK 0 PAGE SELECT REG (WRITE ONLY) +MPGSEL_1 .EQU $79 ; Z2 MEM MGR BANK 1 PAGE SELECT REG (WRITE ONLY) +MPGSEL_2 .EQU $7A ; Z2 MEM MGR BANK 2 PAGE SELECT REG (WRITE ONLY) +MPGSEL_3 .EQU $7B ; Z2 MEM MGR BANK 3 PAGE SELECT REG (WRITE ONLY) +MPGENA .EQU $7C ; Z2 MEM MGR PAGING ENABLE REGISTER (BIT 0, WRITE ONLY) +; +RTCIO .EQU $70 ; RTC LATCH REGISTER ADR +PPIBASE .EQU $60 ; PRIMARY PARALLEL PORT REGISTERS BASE ADR +; +KIOENABLE .EQU FALSE ; ENABLE ZILOG KIO SUPPORT +KIOBASE .EQU $80 ; KIO BASE I/O ADDRESS +; +CTCENABLE .EQU TRUE ; ENABLE ZILOG CTC SUPPORT +CTCMODE .EQU CTCMODE_Z2 ; CTC MODE: CTCMODE_[ZP|Z2|EZ|RC] +CTCBASE .EQU $20 ; CTC BASE I/O ADDRESS +; +DIAGENABLE .EQU FALSE ; ENABLES OUTPUT TO 8 BIT LED DIAGNOSTIC PORT +DIAGPORT .EQU $00 ; DIAGNOSTIC PORT ADDRESS +DIAGDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON DIAGNOSTIC LEDS +; +LEDENABLE .EQU FALSE ; ENABLES STATUS LED (SINGLE LED) +LEDPORT .EQU $0E ; STATUS LED PORT ADDRESS +LEDDISKIO .EQU TRUE ; ENABLES DISK I/O ACTIVITY ON STATUS LED +; +DSKYENABLE .EQU FALSE ; ENABLES DSKY (DO NOT COMBINE WITH PPIDE) +; +CRTACT .EQU FALSE ; ACTIVATE CRT (VDU,CVDU,PROPIO,ETC) AT STARTUP +VDAEMU .EQU EMUTYP_ANSI ; VDA EMULATION: EMUTYP_[TTY|ANSI] +ANSITRACE .EQU 1 ; ANSI DRIVER TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +DSRTCENABLE .EQU TRUE ; DSRTC: ENABLE DS-1302 CLOCK DRIVER (DSRTC.ASM) +DSRTCMODE .EQU DSRTCMODE_STD ; DSRTC: OPERATING MODE: DSRTC_[STD|MFPIC] +DSRTCCHG .EQU FALSE ; DSRTC: FORCE BATTERY CHARGE ON (USE WITH CAUTION!!!) +; +BQRTCENABLE .EQU FALSE ; BQRTC: ENABLE BQ4845 CLOCK DRIVER (BQRTC.ASM) +BQRTC_BASE .EQU $50 ; BQRTC: I/O BASE ADDRESS +; +INTRTCENABLE .EQU FALSE ; ENABLE PERIODIC INTERRUPT CLOCK DRIVER (INTRTC.ASM) +; +HTIMENABLE .EQU FALSE ; ENABLE SIMH TIMER SUPPORT +SIMRTCENABLE .EQU FALSE ; ENABLE SIMH CLOCK DRIVER (SIMRTC.ASM) +; +UARTENABLE .EQU TRUE ; UART: ENABLE 8250/16550-LIKE SERIAL DRIVER (UART.ASM) +UARTOSC .EQU 1843200 ; UART: OSC FREQUENCY IN MHZ +UARTCFG .EQU DEFSERCFG ; UART: LINE CONFIG FOR UART PORTS +UARTCASSPD .EQU SER_300_8N1 ; UART: ECB CASSETTE UART DEFAULT SPEED +UARTSBC .EQU TRUE ; UART: AUTO-DETECT SBC/ZETA ONBOARD UART +UARTCAS .EQU FALSE ; UART: AUTO-DETECT ECB CASSETTE UART +UARTMFP .EQU FALSE ; UART: AUTO-DETECT MF/PIC UART +UART4 .EQU FALSE ; UART: AUTO-DETECT 4UART UART +UARTRC .EQU FALSE ; UART: AUTO-DETECT RC UART +; +ASCIENABLE .EQU FALSE ; ASCI: ENABLE Z180 ASCI SERIAL DRIVER (ASCI.ASM) +; +ACIAENABLE .EQU FALSE ; ACIA: ENABLE MOTOROLA 6850 ACIA DRIVER (ACIA.ASM) +; +SIOENABLE .EQU FALSE ; SIO: ENABLE ZILOG SIO SERIAL DRIVER (SIO.ASM) +; +XIOCFG .EQU DEFSERCFG ; XIO: SERIAL LINE CONFIG +; +VDUENABLE .EQU FALSE ; VDU: ENABLE VDU VIDEO/KBD DRIVER (VDU.ASM) +CVDUENABLE .EQU FALSE ; CVDU: ENABLE CVDU VIDEO/KBD DRIVER (CVDU.ASM) +NECENABLE .EQU FALSE ; NEC: ENABLE NEC UPD7220 VIDEO/KBD DRIVER (NEC.ASM) +TMSENABLE .EQU FALSE ; TMS: ENABLE TMS9918 VIDEO/KBD DRIVER (TMS.ASM) +VGAENABLE .EQU FALSE ; VGA: ENABLE VGA VIDEO/KBD DRIVER (VGA.ASM) +; +SPKENABLE .EQU FALSE ; SPK: ENABLE RTC LATCH IOBIT SOUND DRIVER (SPK.ASM) +; +AYENABLE .EQU FALSE ; AY: ENABLE AY PSG SOUND DRIVER +; +MDENABLE .EQU TRUE ; MD: ENABLE MEMORY (ROM/RAM) DISK DRIVER (MD.ASM) +MDTRACE .EQU 1 ; MD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +; +FDENABLE .EQU TRUE ; FD: ENABLE FLOPPY DISK DRIVER (FD.ASM) +FDMODE .EQU FDMODE_ZETA2 ; FD: DRIVER MODE: FDMODE_[DIO|ZETA|DIDE|N8|DIO3|DYNO] +FDTRACE .EQU 1 ; FD: TRACE LEVEL (0=NO,1=FATAL,2=ERRORS,3=ALL) +FDMEDIA .EQU FDM144 ; FD: DEFAULT MEDIA FORMAT FDM[720|144|360|120|111] +FDMEDIAALT .EQU FDM720 ; FD: ALTERNATE MEDIA FORMAT FDM[720|144|360|120|111] +FDMAUTO .EQU TRUE ; FD: AUTO SELECT DEFAULT/ALTERNATE MEDIA FORMATS +; +RFENABLE .EQU FALSE ; RF: ENABLE RAM FLOPPY DRIVER +; +IDEENABLE .EQU FALSE ; IDE: ENABLE IDE DISK DRIVER (IDE.ASM) +; +PPIDEENABLE .EQU FALSE ; PPIDE: ENABLE PARALLEL PORT IDE DISK DRIVER (PPIDE.ASM) +PPIDETRACE .EQU 1 ; PPIDE: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPIDECNT .EQU 1 ; PPIDE: NUMBER OF PPI CHIPS TO DETECT (1-3), 2 DRIVES PER CHIP +PPIDE0BASE .EQU $60 ; PPIDE 0: PPI REGISTERS BASE ADR +PPIDE0A8BIT .EQU FALSE ; PPIDE 0A (MASTER): 8 BIT XFER +PPIDE0B8BIT .EQU FALSE ; PPIDE 0B (SLAVE): 8 BIT XFER +; +SDENABLE .EQU FALSE ; SD: ENABLE SD CARD DISK DRIVER (SD.ASM) +SDMODE .EQU SDMODE_PPI ; SD: DRIVER MODE: SDMODE_[JUHA|N8|CSIO|PPI|UART|DSD|MK4|SC|MT] +SDCNT .EQU 1 ; SD: NUMBER OF SD CARD DEVICES (1-2), FOR DSD & SC ONLY +SDTRACE .EQU 1 ; SD: TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +SDCSIOFAST .EQU FALSE ; SD: ENABLE TABLE-DRIVEN BIT INVERTER IN CSIO MODE +; +PRPENABLE .EQU FALSE ; PRP: ENABLE ECB PROPELLER IO BOARD DRIVER (PRP.ASM) +; +PPPENABLE .EQU FALSE ; PPP: ENABLE ZETA PARALLEL PORT PROPELLER BOARD DRIVER (PPP.ASM) +PPPSDENABLE .EQU TRUE ; PPP: ENABLE PPP DRIVER SD CARD SUPPORT +PPPSDTRACE .EQU 1 ; PPP: SD CARD TRACE LEVEL (0=NO,1=ERRORS,2=ALL) +PPPCONENABLE .EQU TRUE ; PPP: ENABLE PPP DRIVER VIDEO/KBD SUPPORT +; +HDSKENABLE .EQU FALSE ; HDSK: ENABLE SIMH HDSK DISK DRIVER (HDSK.ASM) +; +PIO_4P .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB 4P BOARD +PIO_ZP .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR ECB ZILOG PERIPHERALS BOARD (PIO.ASM) +PPI_SBC .EQU FALSE ; PIO: ENABLE PARALLEL PORT DRIVER FOR 8255 CHIP +; +UFENABLE .EQU FALSE ; UF: ENABLE ECB USB FIFO DRIVER (UF.ASM) diff --git a/Source/HBIOS/ctc.asm b/Source/HBIOS/ctc.asm new file mode 100644 index 00000000..814fd110 --- /dev/null +++ b/Source/HBIOS/ctc.asm @@ -0,0 +1,124 @@ +;___CTC________________________________________________________________________________________________________________ +; +; Z80 CTC +; +; DISPLAY CONFIGURATION DETAILS +;______________________________________________________________________________________________________________________ +; +; ONLY IM2 IMPLEMENTED BELOW. I DON'T SEE ANY REASONABLE WAY TO +; IMPLEMENT AN IM1 TIMER BECAUSE THE CTC PROVIDES NO WAY TO +; DETERMINE IF IT WAS THE CAUSE OF AN INTERRUPT OR A WAY TO +; DETERMINE WHICH CHANNEL CAUSED AN INTERRUPT. +; +#IF (INTMODE != 2) + .ECHO "*** ERROR: CTC REQUIRES INTMODE 2!!!\n" + !!! ; FORCE AN ASSEMBLY ERROR +#ENDIF +; +; CONFIGURATION +; +#IF (CTCMODE == CTCMODE_ZP) +CTCPC .EQU CTCC ; PRESCALE CHANNEL +CTCPCC .EQU 0 ; PRESCALE CHANNEL CONSTANT +CTCTC .EQU CTCD ; TIMER CHANNEL +CTCTCC .EQU 48 ; TIMER CHANNEL CONSTANT +CTCTIVT .EQU INT_CTC0D ; TIMER CHANNEL IVT ENTRY +#ENDIF +; +#IF (CTCMODE == CTCMODE_Z2) +CTCPC .EQU CTCA ; PRESCALE CHANNEL +CTCPCC .EQU 0 ; PRESCALE CHANNEL CONSTANT +CTCTC .EQU CTCB ; TIMER CHANNEL +CTCTCC .EQU 72 ; TIMER CHANNEL CONSTANT +CTCTIVT .EQU INT_CTC0B ; TIMER CHANNEL IVT ENTRY +#ENDIF +; +#IF (CTCMODE == CTCMODE_EZ) +CTCPC .EQU CTCC ; PRESCALE CHANNEL +CTCPCC .EQU 0 ; PRESCALE CHANNEL CONSTANT +CTCTC .EQU CTCD ; TIMER CHANNEL +CTCTCC .EQU 72 ; TIMER CHANNEL CONSTANT +CTCTIVT .EQU INT_CTC0D ; TIMER CHANNEL IVT ENTRY +#ENDIF +; +#IF (CTCMODE == CTCMODE_RC) +CTCPC .EQU CTCC ; PRESCALE CHANNEL +CTCPCC .EQU 0 ; PRESCALE CHANNEL CONSTANT +CTCTC .EQU CTCD ; TIMER CHANNEL +CTCTCC .EQU 144 ; TIMER CHANNEL CONSTANT +CTCTIVT .EQU INT_CTC0D ; TIMER CHANNEL IVT ENTRY +#ENDIF +; +; +; +CTC_PREINIT: + ; SETUP TIMER INTERRUPT IVT SLOT + LD HL,HB_TIMINT ; TIMER INT HANDLER ADR + LD (IVT(CTCTIVT)),HL ; IVT ENTRY FOR TIMER CHANNEL +; + ; CTC USES 4 CONSECUTIVE VECTOR POSITIONS, ONE FOR + ; EACH CHANNEL. BELOW WE SET THE BASE VECTOR TO THE + ; START OF THE IVT, SO THE FIRST FOUR ENTIRES OF THE + ; IVT CORRESPOND TO CTC CHANNELS A-D + LD A,0 + OUT (CTCBASE),A ; SETUP CTC BASE INT VECTOR +; + ; IN ORDER TO DIVIDE THE CTC INPUT CLOCK DOWN TO THE + ; DESIRED 50 HZ PERIODIC INTERRUPT, WE NEED TO CONFIGURE ONE + ; CTC CHANNEL AS A PRESCALER AND ANOTHER AS THE ACTUAL + ; TIMER INTERRUPT. THE PRESCALE CHANNEL OUTPUT MUST BE WIRED + ; TO THE TIMER CHANNEL TRIGGER INPUT VIA HARDWARE. + LD A,%01010111 ; PRESCALE CHANNEL CONTROL WORD VALUE + ; |||||||+-- 1=CONTROL WORD FLAG + ; ||||||+--- 1=SOFTWARE RESET + ; |||||+---- 1=TIME CONSTANT FOLLOWS + ; ||||+----- 0=AUTO TRIGGER WHEN TIME CONST LOADED + ; |||+------ 1=RISING EDGE TRIGGER + ; ||+------- 0=PRESCALER OF 16 (NOT USED) + ; |+-------- 1=COUNTER MODE + ; +--------- 0=NO INTERRUPTS + OUT (CTCPC),A ; SETUP PRESCALE CHANNEL + LD A,CTCPCC ; PRESCALE CHANNEL CONSTANT + OUT (CTCPC),A ; SET PRESCALE CONSTANT + ; + LD A,%11010111 ; TIMER CHANNEL CONTROL WORD VALUE + ; |||||||+-- 1=CONTROL WORD FLAG + ; ||||||+--- 1=SOFTWARE RESET + ; |||||+---- 1=TIME CONSTANT FOLLOWS + ; ||||+----- 0=AUTO TRIGGER WHEN TIME CONST LOADED + ; |||+------ 1=RISING EDGE TRIGGER + ; ||+------- 0=PRESCALER OF 16 (NOT USED) + ; |+-------- 1=COUNTER MODE + ; +--------- 1=ENABLE INTERRUPTS + OUT (CTCTC),A ; SETUP TIMER CHANNEL + LD A,CTCTCC ; TIMER CHANNEL CONSTANT + OUT (CTCTC),A ; SET TIMER CONSTANT +; + XOR A + RET +; +; +; +CTC_INIT: ; MINIMAL INIT +CTC_PRTCFG: + ; ANNOUNCE PORT + CALL NEWLINE ; FORMATTING + PRTS("CTC: MODE=$") ; FORMATTING +#IF (CTCMODE == CTCMODE_ZP) + PRTS("ZP$") +#ENDIF +#IF (CTCMODE == CTCMODE_Z2) + PRTS("Z2$") +#ENDIF +#IF (CTCMODE == CTCMODE_EZ) + PRTS("EZ$") +#ENDIF +#IF (CTCMODE == CTCMODE_RC) + PRTS("RC$") +#ENDIF + PRTS(" IO=0x$") ; FORMATTING + LD A,CTCBASE ; GET BASE PORT + CALL PRTHEXBYTE ; PRINT BASE PORT +; + XOR A + RET diff --git a/Source/HBIOS/cvdu.asm b/Source/HBIOS/cvdu.asm index 29277d78..9ee98c40 100644 --- a/Source/HBIOS/cvdu.asm +++ b/Source/HBIOS/cvdu.asm @@ -10,6 +10,7 @@ ; - IMPLEMENT SET CURSOR STYLE (VDASCS) FUNCTION ; - IMPLEMENT ALTERNATE DISPLAY MODES? ; - IMPLEMENT DYNAMIC READ/WRITE OF CHARACTER BITMAP DATA? +; - IMPLEMENT TIMEOUT ON PROBE ; ;====================================================================== ; CVDU DRIVER - CONSTANTS @@ -26,6 +27,9 @@ CVDU_DATA .EQU CVDU_BASE + $0C ; READ/WRITE M8563 DATA CVDU_ROWS .EQU 25 CVDU_COLS .EQU 80 ; +#DEFINE USEFONT8X16 +#DEFINE CVDU_FONT FONT8X16 +; TERMENABLE .SET TRUE ; INCLUDE TERMINAL PSEUDODEVICE DRIVER ; ;====================================================================== @@ -58,12 +62,12 @@ CVDU_INIT1: ; ADD OURSELVES TO VDA DISPATCH TABLE LD BC,CVDU_FNTBL ; BC := FUNCTION TABLE ADDRESS - LD DE,CVDU_IDAT ; DE := VGA INSTANCE DATA PTR + LD DE,CVDU_IDAT ; DE := CVDU INSTANCE DATA PTR CALL VDA_ADDENT ; ADD ENTRY, A := UNIT ASSIGNED ; INITIALIZE EMULATION LD C,A ; C := ASSIGNED VIDEO DEVICE NUM - LD DE,VGA_FNTBL ; DE := FUNCTION TABLE ADDRESS + LD DE,CVDU_FNTBL ; DE := FUNCTION TABLE ADDRESS LD HL,CVDU_IDAT ; HL := CVDU INSTANCE DATA PTR CALL TERM_ATTACH ; DO IT @@ -281,7 +285,6 @@ CVDU_PROBE: CALL CVDU_WR ; WRITE VALUE TO LOC 0, ADR PTR INCREMENTS CPL ; INVERT TEST VALUE CALL CVDU_WR ; WRITE INVERTED VALUE TO LOC 1 - ; READ TEST PATTERN BACK TO CONFIRM HARDWARE EXISTS LD HL,0 ; POINT TO FIRST BYTE OF VRAM LD C,18 ; ADDRESS REGISTER PAIR @@ -360,7 +363,22 @@ CVDU_LOADFONT: LD C,18 ; UPDATE ADDRESS REGISTER PAIR CALL CVDU_WRX ; DO IT - LD HL,FONT_HI ; POINTER TO FONT DATA +#IF USELZSA2 + LD (CVDU_STACK),SP ; SAVE STACK + LD HL,(CVDU_STACK) ; AND SHIFT IT + LD DE,$2000 ; DOWN 4KB TO + CCF ; CREATE A + SBC HL,DE ; DECOMPRESSION BUFFER + LD SP,HL ; HL POINTS TO BUFFER + EX DE,HL ; START OF STACK BUFFER + PUSH DE ; SAVE IT + LD HL,CVDU_FONT ; START OF FONT DATA + CALL DLZSA2 ; DECOMPRESS TO DE + POP HL ; RECALL STACK BUFFER POSITION +#ELSE + LD HL,CVDU_FONT ; START OF FONT DATA +#ENDIF + LD DE,$2000 ; LENGTH OF FONT DATA LD C,31 ; DATA REGISTER CVDU_LOADFONT1: @@ -371,7 +389,15 @@ CVDU_LOADFONT1: LD A,D ; CHECK DE... OR E ; FOR COUNTER EXHAUSTED JR NZ,CVDU_LOADFONT1 ; LOOP TILL DONE + +#IF USELZSA2 + LD HL,(CVDU_STACK) ; ERASE DECOMPRESS BUFFER + LD SP,HL ; BY RESTORING THE STACK + RET ; DONE +CVDU_STACK .DW 0 +#ELSE RET +#ENDIF ; ;---------------------------------------------------------------------- ; SET CURSOR POSITION TO ROW IN D AND COLUMN IN E diff --git a/Source/HBIOS/dbgmon.asm b/Source/HBIOS/dbgmon.asm index 5cb20df0..9e4c8a3a 100644 --- a/Source/HBIOS/dbgmon.asm +++ b/Source/HBIOS/dbgmon.asm @@ -8,7 +8,7 @@ ; THOMAS SCHERRER BASIC HAR.DWARE TEST ASSEMBLER SOURCES FROM THE Z80 INFO PAGE ; INCLUDING ORIGINAL SCHEMATIC CONCEPT ; HTTP://Z80.INFO/Z80SOURC.TXT -; CODE SAMPLES FROM BRUCE JONES PUBLIC DOMAIN ROM MONITOR FOR THE SBC-200C +; CODE SAMPLES FROM BRUCE JONES PUBLIC DOMAIN ROM MONITOR FOR THE SBC-200C ; HTTP://WWW.RETROTECHNOLOGY.COM/HERBS_STUFF/SD_BRUCE_CODE.ZIP ; INSPIRATION FROM JOEL OWENS "Z-80 SPACE-TIME PRODUCTIONS SINGLE BOARD COMPUTER" ; HTTP://WWW.JOELOWENS.ORG/Z80/Z80INDEX.HTML @@ -31,7 +31,10 @@ BUFLEN .EQU 40 ; INPUT LINE LENGTH JP DSKY_ENTRY JP UART_ENTRY ; -#DEFINE USEDELAY +#IF DSKYENABLE + #DEFINE USEDELAY +#ENDIF +; #INCLUDE "util.asm" ; ;__UART_ENTRY_________________________________________________________________ @@ -42,6 +45,7 @@ BUFLEN .EQU 40 ; INPUT LINE LENGTH UART_ENTRY: LD SP,MON_STACK ; SET THE STACK POINTER EI ; INTS OK NOW + LD HL,UART_ENTRY ; RESTART ADDRESS CALL INITIALIZE ; INITIALIZE SYSTEM LD HL,TXT_READY ; POINT AT TEXT @@ -108,18 +112,26 @@ SERIALCMDLOOP: ;__INITIALIZE_________________________________________________________________ ; ; INITIALIZE SYSTEM +; AT ENTRY, HL SHOULD HAVE ADDRESS OF DESIRED RESTART ADDRESS ;_____________________________________________________________________________ ; INITIALIZE: -#IF (PLATFORM == PLT_UNA) + LD A,$C3 ; JP OPCODE + LD (0),A ; STORE AT $0000 + LD (1),HL ; STORE AT $0001 + +#IF (BIOS == BIOS_UNA) ; INSTALL UNA INVOCATION VECTOR FOR RST 08 LD A,$C3 ; JP INSTRUCTION LD (8),A ; STORE AT 0x0008 LD HL,($FFFE) ; UNA ENTRY VECTOR LD (9),HL ; STORE AT 0x0009 -#ELSE - CALL DELAY_INIT #ENDIF + +;#IF (BIOS == BIOS_WBW) +; CALL DELAY_INIT +;#ENDIF + RET ; ;__BOOT_______________________________________________________________________ @@ -128,7 +140,7 @@ INITIALIZE: ;_____________________________________________________________________________ ; BOOT: -#IF (PLATFORM == PLT_UNA) +#IF (BIOS == BIOS_UNA) LD BC,$01FB ; UNA FUNC = SET BANK LD DE,0 ; ROM BANK 0 CALL $FFFD ; DO IT (RST 08 NOT SAFE HERE) @@ -204,7 +216,7 @@ KLOP1: JP Z,SERIALCMDLOOP ; IF SO, ALL DONE CALL COUT ; OUTPUT KEY TO SCREEN JR KLOP1 ; LOOP -; +; ;__HXLOAD_____________________________________________________________________ ; ; LOAD INTEL HEX FORMAT FILE FROM THE SERIAL PORT, USER OPTION "H" @@ -215,7 +227,7 @@ KLOP1: ; 3) LOAD ADDRESS FIELD (FRAMES 3,4,5,6) ; 4) RECORD TYPE FIELD (FRAMES 7 AND 8) ; 5) DATA FIELD (FRAMES 9 TO 9+2*(RECORD LENGTH)-1 -; 6) CHECKSUM FIELD - SUM OF ALL BYTE VALUES FROM RECORD LENGTH TO AND +; 6) CHECKSUM FIELD - SUM OF ALL BYTE VALUES FROM RECORD LENGTH TO AND ; INCLUDING CHECKSUM FIELD = 0 ] ; ; EXAMPLE OF INTEL HEX FORMAT FILE @@ -230,63 +242,47 @@ KLOP1: ; HXLOAD: CALL NEWLINE ; SHOW READY -HXLOAD0: - CALL KIN ; GET THE FIRST CHARACTER, EXPECTING A ':' -HXLOAD1: - CP 03Ah ; IS IT COLON ':'? START OF LINE OF INTEL HEX FILE - JR NZ,HXLOADERR ; IF NOT, MUST BE ERROR, ABORT ROUTINE - LD E,0 ; FIRST TWO CHARACTERS IS THE RECORD LENGTH FIELD - CALL HEXINS ; GET US TWO CHARACTERS INTO BC, CONVERT IT TO A BYTE - CALL HXCHKSUM ; UPDATE HEX CHECK SUM +HXLOADLINE: + CALL CIN ; GET THE FIRST CHARACTER, EXPECTING A ':' + CP ':' ; IS IT COLON ':'? WAIT FOR START OF NEXT LINE OF INTEL HEX FILE + JR NZ,HXLOADLINE ; IF NOT, GO BACK TO WAIT + LD E,0 ; RESET THE CHECKSUM BYTE + CALL HEXINS ; FIRST TWO CHARACTERS IS THE RECORD LENGTH FIELD LD D,A ; LOAD RECORD LENGTH COUNT INTO D CALL HEXINS ; GET NEXT TWO CHARACTERS, MEMORY LOAD ADDRESS - CALL HXCHKSUM ; UPDATE HEX CHECK SUM - LD H,A ; PUT VALUE IN H REGISTER + LD H,A ; PUT VALUE IN H REGISTER CALL HEXINS ; GET NEXT TWO CHARACTERS, MEMORY LOAD ADDRESS - CALL HXCHKSUM ; UPDATE HEX CHECK SUM - LD L,A ; PUT VALUE IN L REGISTER + LD L,A ; PUT VALUE IN L REGISTER CALL HEXINS ; GET NEXT TWO CHARACTERS, RECORD FIELD TYPE - CALL HXCHKSUM ; UPDATE HEX CHECK SUM - CP 001h ; RECORD FIELD TYPE 00 IS DATA, 01 IS END OF FILE - JR NZ,HXLOAD2 ; MUST BE THE END OF THAT FILE + DEC A ; RECORD FIELD TYPE 01 IS END OF FILE + JR Z,HXLOADEXIT ; MUST BE THE END OF THAT FILE + INC A ; RECORD FIELD TYPE 00 IS DATA + JR NZ,HXLOADTYPERR ; RECORD TYPE IS INCORRECT, ERROR OUT +HXLOADDATA: CALL HEXINS ; GET NEXT TWO CHARACTERS, ASSEMBLE INTO BYTE - CALL HXCHKSUM ; UPDATE HEX CHECK SUM - LD A,E ; RECALL THE CHECKSUM BYTE - AND A ; IS IT ZERO? - JR Z,HXLOADEXIT ; MUST BE O K., GO BACK FOR SOME MORE, ELSE - JR HXLOADERR ; CHECKSUMS DON'T ADD UP, ERROR OUT -HXLOAD2: - LD A,D ; RETRIEVE LINE CHARACTER COUNTER - AND A ; ARE WE DONE WITH THIS LINE? - JR Z,HXLOAD3 ; GET TWO MORE ASCII CHARACTERS, BUILD A BYTE AND CHECKSUM - CALL HEXINS ; GET NEXT TWO CHARS, CONVERT TO BYTE IN A, CHECKSUM IT - CALL HXCHKSUM ; UPDATE HEX CHECK SUM - LD (HL),A ; CHECKSUM OK, MOVE CONVERTED BYTE IN A TO MEMORY LOCATION - INC HL ; INCREMENT POINTER TO NEXT MEMORY LOCATION + LD (HL),A ; MOVE CONVERTED BYTE IN A TO MEMORY LOCATION + INC HL ; INCREMENT POINTER TO NEXT MEMORY LOCATION DEC D ; DECREMENT LINE CHARACTER COUNTER - JR HXLOAD2 ; AND KEEP LOADING INTO MEMORY UNTIL LINE IS COMPLETE -HXLOAD3: - CALL HEXINS ; GET TWO CHARS, BUILD BYTE AND CHECKSUM - CALL HXCHKSUM ; UPDATE HEX CHECK SUM - LD A,E ; CHECK THE CHECKSUM VALUE - AND A ; IS IT ZERO? - JR Z,HXLOADAGAIN ; IF THE CHECKSUM IS STILL OK, CONTINUE ON, ELSE -HXLOADERR: + JR NZ,HXLOADDATA ; AND KEEP LOADING INTO MEMORY UNTIL LINE IS COMPLETE + CALL HEXINS ; GET NEXT TWO CHARACTERS, ASSEMBLE INTO CHECKSUM BYTE + LD A,E ; RECALL THE CHECKSUM BYTE + OR A ; IT SHOULD BE ZERO + JR Z,HXLOADLINE ; ZERO, SO WE HAVE NO ERROR, GO GET ANOTHER LINE +HXLOADCHKERR: LD HL,TXT_CKSUMERR ; GET "CHECKSUM ERROR" MESSAGE CALL PRTSTR ; PRINT MESSAGE FROM (HL) AND TERMINATE THE LOAD - JR HXLOADEXIT ; RETURN TO PROMPT -HXCHKSUM: - LD C,A ; BUILD THE CHECKSUM - LD A,E ; - SUB C ; THE CHECKSUM SHOULD ALWAYS .EQUAL ZERO WHEN CHECKED - LD E,A ; SAVE THE CHECKSUM BACK WHERE IT CAME FROM - LD A,C ; RETRIEVE THE BYTE AND GO BACK - RET ; BACK TO CALLER -HXLOADAGAIN: - CALL KIN ; CATCH THE TRAILING CARRIAGE RETURN - JR HXLOAD0 ; LOAD ANOTHER LINE OF DATA + JP SERIALCMDLOOP ; RETURN TO PROMPT +HXLOADTYPERR: + LD HL,TXT_RECORDERR ; GET "RECORD TYPE ERROR" MESSAGE + CALL PRTSTR ; PRINT MESSAGE FROM (HL) AND TERMINATE THE LOAD + JP SERIALCMDLOOP ; RETURN TO PROMPT HXLOADEXIT: - CALL KIN ; CATCH ANY STRAY TRAILING CHARACTERS + CALL HEXINS ; GET LAST TWO CHARACTERS, ASSEMBLE INTO CHECKSUM BYTE + LD A,E ; RECALL THE CHECKSUM BYTE + OR A ; IT SHOULD BE ZERO + JR NZ,HXLOADCHKERR ; CHECKUM IS INCORRECT, ERROR OUT + LD HL,TXT_LOADED ; GET "LOADED" MESSAGE + CALL PRTSTR ; PRINT MESSAGE FROM (HL) JP SERIALCMDLOOP ; RETURN TO PROMPT ; ;__POUT_______________________________________________________________________ @@ -346,7 +342,7 @@ DUMPMEM: GDATA: INC DE ; BUMP DE FOR LATER COMPARE - CALL NEWLINE ; + CALL NEWLINE ; BLKRD: CALL PHL ; PRINT START LOCATION CALL PC_COLON @@ -366,7 +362,7 @@ NXTONE: LD A,(HL) ; GET BYTE CALL PRTHEXBYTE ; DISPLAY IT CALL PC_SPACE ; -UPDH: +UPDH: INC HL ; POINT NEXT DEC C ; DEC LOC COUNT JR NZ,NXTONE ; IF LINE NOT DONE @@ -381,10 +377,10 @@ PCRLF0: LD A,(HL) ; O K. TO GET JR NZ,PDOT ; DOT: - LD A,2EH ; LOAD A DOT + LD A,2EH ; LOAD A DOT PDOT: CALL COUT ; PRINT IT - INC HL ; + INC HL ; LD A,D ; CP H ; JR NZ,UPDH1 ; @@ -431,7 +427,7 @@ MOVEMEM1: LD A,(HL) ; GET SOURCE VAUEE LD (DE),A ; WRITE TO TARGET LOC LD A,H ; CHECK MSB OF END ADR - CP B ; + CP B ; JR NZ,MOVEMEM1 ; NO MATCH, LOOP LD A,L ; CHECK LSB OF END ADR CP C ; @@ -466,7 +462,7 @@ FILLMEM1: LD A,C ; FILL VALUE TO A LD (HL),A ; WRITE FILL VALUE TO CUR ADR (HL) LD A,H ; CHECK MSB OF END ADR - CP D ; + CP D ; JR NZ,FILLMEM1 ; NO MATCH, LOOP LD A,L ; CHECK LSB OF END ADR CP E ; @@ -524,7 +520,7 @@ WORDPARM: ; ;__GETLN______________________________________________________________________ ; -; READ A LINE OF TEXT FROM THE SERIAL PORT, HANDLE , TERM ON +; READ A LINE OF TEXT FROM THE SERIAL PORT, HANDLE , TERM ON ; EXIT IF TOO MANY CHARS STORE RESULT IN HL. CHAR COUNT IN C. ;_____________________________________________________________________________ ; @@ -535,7 +531,7 @@ GETLNLOP: ; ENTRY LOOP CALL KIN ; GET A KEY CP CHR_CR ; IS ? - JR Z,GETLNDONE ; YES, EXIT + JR Z,GETLNDONE ; YES, EXIT CP CHR_BS ; IS ? JR Z,GETLNBS ; IF SO, HANDLE IT CP ' ' ; UNEXPECTED CONTROL CHAR? @@ -548,7 +544,7 @@ GETLNLOP: CALL COUT ; OUTPUT KEY TO SCREEN LD (HL),A ; STORE CHAR IN BUFFER INC HL ; INC POINTER - INC C ; INC CHAR COUNTER + INC C ; INC CHAR COUNTER JR GETLNLOP ; GET NEXT CHAR GETLNOVF: ; OVERFLOW @@ -683,37 +679,40 @@ NIBL: LD A,(HL) ; GET K B. DATA INC HL ; INC KB POINTER CP 40H ; TEST FOR ALPHA - JR NC,ALPH ; + JR NC,ALPH AND 0FH ; GET THE BITS - RET ; + RET ALPH: AND 0FH ; GET THE BITS ADD A,09H ; MAKE IT HEX A-F - RET ; + RET ; ;__HEXINS_____________________________________________________________________ ; -; GET ONE BYTE OF HEX DATA FROM SERIAL PORT, RETURN IN A +; GET ONE BYTE OF HEX DATA FROM SERIAL PORT, CHECKSUM IN E, RETURN IN A ;_____________________________________________________________________________ ; HEXINS: - PUSH BC ; SAVE BC REGS CALL NIBLS ; DO A NIBBLE - RLC A ; MOVE FIRST BYTE UPPER NIBBLE - RLC A ; - RLC A ; - RLC A ; - LD B,A ; SAVE ROTATED BYTE + RLCA ; MOVE FIRST BYTE UPPER NIBBLE + RLCA ; + RLCA ; + RLCA ; + LD B,A ; SAVE ROTATED NIBBLE CALL NIBLS ; DO NEXT NIBBLE - ADD A,B ; COMBINE NIBBLES IN ACC - POP BC ; RESTORE BC - RET ; DONE + OR B ; COMBINE NIBBLES IN ACC TO BYTE + LD B,A ; SAVE BYTE + ADD A,E ; ADD TO CHECKSUM + LD E,A ; SAVE CHECKSUM + LD A,B ; RECOVER BYTE + RET ; DONE NIBLS: - CALL KIN ; GET K B. DATA - CP 40H ; TEST FOR ALPHA - JR NC,ALPH ; - AND 0FH ; GET THE BITS - RET ; + CALL CIN ; GET K B. DATA + SUB '0' + CP 10 ; TEST FOR ALPHA + RET C ; IF A<10 JUST RETURN + SUB 7 ; ELSE SUBTRACT 'A'-'0' (17) AND ADD 10 + RET ; ;__PHL________________________________________________________________________ ; @@ -737,7 +736,7 @@ PRTSTRH: LD HL,TXT_MINIHELP JP PRTSTR ; -#IF (PLATFORM == PLT_UNA) +#IF (BIOS == BIOS_UNA) ; ;__COUT_______________________________________________________________________ ; @@ -806,7 +805,7 @@ CST: POP DE POP BC RET -; +; #ELSE ; ;__COUT_______________________________________________________________________ @@ -823,9 +822,9 @@ COUT: ; ; OUTPUT CHARACTER TO CONSOLE VIA HBIOS LD E,A ; OUTPUT CHAR TO E - LD C,CIODEV_CONSOLE ; CONSOLE UNIT TO C + LD C,CIO_CONSOLE ; CONSOLE UNIT TO C LD B,BF_CIOOUT ; HBIOS FUNC: OUTPUT CHAR - RST 08 ; HBIOS OUTPUTS CHARACTDR + RST 08 ; HBIOS OUTPUTS CHARACTER ; ; RESTORE ALL REGISTERS POP HL @@ -846,9 +845,9 @@ CIN: PUSH HL ; ; INPUT CHARACTER FROM CONSOLE VIA HBIOS - LD C,CIODEV_CONSOLE ; CONSOLE UNIT TO C + LD C,CIO_CONSOLE ; CONSOLE UNIT TO C LD B,BF_CIOIN ; HBIOS FUNC: INPUT CHAR - RST 08 ; HBIOS READS CHARACTDR + RST 08 ; HBIOS READS CHARACTER LD A,E ; MOVE CHARACTER TO A FOR RETURN ; ; RESTORE REGISTERS (AF IS OUTPUT) @@ -869,7 +868,7 @@ CST: PUSH HL ; ; GET CONSOLE INPUT STATUS VIA HBIOS - LD C,CIODEV_CONSOLE ; CONSOLE UNIT TO C + LD C,CIO_CONSOLE ; CONSOLE UNIT TO C LD B,BF_CIOIST ; HBIOS FUNC: INPUT STATUS RST 08 ; HBIOS RETURNS STATUS IN A ; @@ -894,10 +893,12 @@ KEYBUF: .FILL BUFLEN,0 ;_____________________________________________________________________________ ; TXT_PROMPT .TEXT "\r\n>$" -TXT_READY .TEXT "\r\nMonitor Ready$" +TXT_READY .TEXT "\r\n\r\nMonitor Ready$" TXT_COMMAND .TEXT "\r\nUnknown Command$" TXT_ERR .TEXT "\r\nSyntax Error$" TXT_CKSUMERR .TEXT "\r\nChecksum Error$" +TXT_RECORDERR .TEXT "\r\nRecord Type Error$" +TXT_LOADED .TEXT "\r\nLoaded$" TXT_BADNUM .TEXT " *Invalid Hex Byte Value*$" TXT_MINIHELP .TEXT " (H for Help)$" TXT_HELP .TEXT "\r\nMonitor Commands (all values in hex):" @@ -929,11 +930,12 @@ KY_PW .EQU KY_BK ; USE [BW] FOR [PW] (PORT WRITE) DSKY_ENTRY: LD SP,MON_STACK ; SET THE STACK POINTER EI ; INTS OK NOW + LD HL,DSKY_ENTRY ; RESTART ADDRESS CALL INITIALIZE ; ;__FRONT_PANEL_STARTUP________________________________________________________ ; -; START UP THE SYSTEM WITH THE FRONT PANEL INTERFACE +; START UP THE SYSTEM WITH THE FRONT PANEL INTERFACE ;_____________________________________________________________________________ ; CALL DSKY_INIT ; INIT 8255 @@ -965,7 +967,7 @@ FRONTPANELLOOP1: JR FRONTPANELLOOP ; LOOP EXIT: - RET + RET ; ;__DOBOOT_____________________________________________________________________ ; @@ -984,7 +986,7 @@ DOBOOT: ; POS 01234567 ;_____________________________________________________________________________ ; -DOPORTREAD: +DOPORTREAD: CALL GETPORT ; GET PORT INTO A PORTREADLOOP: LD C,A ; STORE PORT IN "C" @@ -1006,7 +1008,7 @@ PORTREADGETKEY: ; POS 01234567 ;_____________________________________________________________________________ ; -DOPORTWRITE: +DOPORTWRITE: CALL GETPORT ; GET PORT INTO A PORTWRITELOOP: LD L,A ; SAVE PORT NUM @@ -1067,11 +1069,11 @@ EXAMINELOOP: EXAMINEGETKEY: CALL KB_GET ; GET KEY FROM KB CP KY_EN ; [EN] PRESSED, INC ADDRESS AND LOOP - JR Z,EXAMINEFW ; + JR Z,EXAMINEFW ; JR EXAMINEGETKEY ; NO VALID KEY, LOOP EXAMINEFW: INC HL ; HL++ - JR EXAMINELOOP ; + JR EXAMINELOOP ; ; ;__DODEPOSIT__________________________________________________________________ ; @@ -1100,11 +1102,11 @@ DEPOSITLOOP: DEPOSITGETKEY: CALL KB_GET ; GET KEY FROM KB CP KY_EN ; [EN] PRESSED, INC ADDRESS AND LOOP - JR Z,DEPOSITFW ; + JR Z,DEPOSITFW ; JR DEPOSITGETKEY ; NO VALID KEY, LOOP DEPOSITFW: INC HL ; - JR DEPOSITLOOP ; + JR DEPOSITLOOP ; ; ;__GETADDR____________________________________________________________________ ; @@ -1132,9 +1134,9 @@ GETVALW: LD (DISPLAYBUF+6),A ; LD (DISPLAYBUF+7),A ; GETVALW1: - CALL ENCDISPLAY ; + CALL ENCDISPLAY ; GETVALWLOOP: - CALL KB_GET ; + CALL KB_GET ; CP $10 ; JP M,GETVALWNUM ; NUMBER PRESSED, STORE IT CP KY_EN ; [EN] PRESSED, DONE @@ -1145,9 +1147,9 @@ GETVALWNUM: LD C,A ; LD A,(DISPLAYBUF+5) ; SHIFT BYTES IN DISPLAY BUF TO THE LEFT LD (DISPLAYBUF+4),A ; - LD A,(DISPLAYBUF+6) ; + LD A,(DISPLAYBUF+6) ; LD (DISPLAYBUF+5),A ; - LD A,(DISPLAYBUF+7) ; + LD A,(DISPLAYBUF+7) ; LD (DISPLAYBUF+6),A ; LD A,C ; DISPLAY KEYSTROKE IN RIGHT MOST DISPLAY (0) LD (DISPLAYBUF+7),A ; @@ -1159,7 +1161,7 @@ GETVALWDONE: SLA A ; SLA A ; SLA A ; - LD C,A ; STORE IT IN "C" + LD C,A ; STORE IT IN "C" LD A,(DISPLAYBUF+7) ; GET DIGIT IN DISPLAY 7 AND $0F OR C ; ADD IN NIBBLE STORED IN C @@ -1170,7 +1172,7 @@ GETVALWDONE: SLA A ; SLA A ; SLA A ; - LD C,A ; STORE IT IN "C" + LD C,A ; STORE IT IN "C" LD A,(DISPLAYBUF+5) ; GET DIGIT IN DISPLAY 5 AND $0F OR C ; ADD IN NIBBLE STORED IN "C" @@ -1201,9 +1203,9 @@ GETVALUE: LD (DISPLAYBUF+6),A ; LD (DISPLAYBUF+7),A ; GETVALUE1: - CALL ENCDISPLAY ; + CALL ENCDISPLAY ; GETVALUELOOP: - CALL KB_GET ; + CALL KB_GET ; CP $10 ; JP M,GETVALUENUM ; NUMBER PRESSED, STORE IT CP KY_EN ; [EN] PRESSED, DONE @@ -1212,7 +1214,7 @@ GETVALUELOOP: GETVALUENUM: OR $80 ; SET DP LD C,A ; - LD A,(DISPLAYBUF+7) ; + LD A,(DISPLAYBUF+7) ; LD (DISPLAYBUF+6),A ; LD A,C ; LD (DISPLAYBUF+7),A ; @@ -1224,7 +1226,7 @@ GETVALUEDONE: RLCA ; RLCA ; RLCA ; - LD C,A ; + LD C,A ; LD A,(DISPLAYBUF+7) ; AND $0F OR C ; @@ -1255,7 +1257,7 @@ PUTVALUE: ;__KB_GET_____________________________________________________________________ ; ; GET A SINGLE KEY AND DECODE -; +; ;_____________________________________________________________________________ ; KB_GET: @@ -1270,7 +1272,7 @@ KB_GET: ; NOT A DIGIT OR [EN], BAIL OUT TO MAIN LOOP TO HANDLE IT LD SP,MON_STACK ; CLEAR STACK JP FRONTPANELLOOP1 ; RESTART AT MAIN LOOP -KB_GET1: +KB_GET1: POP HL ; RESTORE HL POP DE POP BC @@ -1343,19 +1345,19 @@ PORT .DB $13,$14,$15,$16,$10,$10,$00,$00 ; "Port 00" (ENCODED) GOTO .DB $1A,$14,$10,$10,$00,$00,$00,$00 ; "Go 0000" (ENCODED) ; ;_HEX_7_SEG_DECODE_TABLE______________________________________________________ -; +; ; SET BIT 7 TO DISPLAY W/ DECIMAL POINT ;_____________________________________________________________________________ ; SEGDECODE: ; POS $00 $01 $02 $03 $04 $05 $06 $07 - ; GLYPH '0' '1' '2' '3' '4' '5' '6' '7' + ; GLYPH '0' '1' '2' '3' '4' '5' '6' '7' .DB $7B, $30, $6D, $75, $36, $57, $5F, $70 -; +; ; POS $08 $09 $0A $0B $0C $0D $0E $0F ; GLYPH '8' '9' 'A' 'B' 'C' 'D' 'E' 'F' .DB $7F, $77, $7E, $1F, $4B, $3D, $4F, $4E -; +; ; POS $10 $11 $12 $13 $14 $15 $16 $17 $18 $19 $1A ; GLYPH ' ' '-' '.' 'P' 'o' 'r' 't' 'A' 'd' 'r' 'G' .DB $00, $04, $00, $6E, $1D, $0C, $0F, $7E, $3D, $0C, $5B @@ -1378,5 +1380,13 @@ MON_STACK .EQU $ .ECHO "DBGMON space remaining: " .ECHO SLACK .ECHO " bytes.\n" - +; +; DBGMON CURRENTLY OCCUPIES $F000-$FDFF BECAUSE THE +; HBIOS PROXY OCCUPIES $FE00-$FFFF. HOWEVER THE DBGMON +; IMAGE MUST OCCUPY A FULL $1000 BYTES IN THE ROM. +; BELOW WE JUST PAD OUT THE IMAGE BY $200 SO IT +; OCCUPIES THE FULL $1000 BYTES IN ROM. +; + .FILL $200,$00 +; .END diff --git a/Source/HBIOS/dsky.asm b/Source/HBIOS/dsky.asm index 96288ff9..26b9e92a 100644 --- a/Source/HBIOS/dsky.asm +++ b/Source/HBIOS/dsky.asm @@ -262,11 +262,11 @@ DSKY_HEXOUT1: DSKY_SHOWHEX: LD A,$D0 ; 7218 -> (DATA COMING, HEXA DECODE) JR DSKY_SHOW - +; DSKY_SHOWSEG: LD A,$F0 ; 7218 -> (DATA COMING, NO DECODE) JR DSKY_SHOW - +; DSKY_SHOW: PUSH AF ; SAVE 7218 CONTROL BITS LD A,82H ; SETUP PPI @@ -290,15 +290,37 @@ DSKY_STROBE: ; DATA STROBE DSKY_STROBE0: OUT (PPIC),A ; OUT TO PORTC CALL DLY2 ; DELAY -DSKY_COFF +DSKY_COFF: LD A,40H | 30H ; QUIESCE OUT (PPIC),A ; OUT TO PORTC ; CALL DSKY_DELAY ; WAIT RET ; +; CODES FOR NUMERICS +; HIGH BIT ALWAYS SET TO SUPPRESS DECIMAL POINT +; CLEAR HIGH BIT TO SHOW DECIMAL POINT +; +DSKY_NUMS: + .DB $FB ; 0 + .DB $B0 ; 1 + .DB $ED ; 2 + .DB $F5 ; 3 + .DB $B6 ; 4 + .DB $D7 ; 5 + .DB $DF ; 6 + .DB $F0 ; 7 + .DB $FF ; 8 + .DB $F7 ; 9 + .DB $FE ; A + .DB $9F ; B + .DB $CB ; C + .DB $BD ; D + .DB $CF ; E + .DB $CE ; F +; ; SEG DISPLAY WORKING STORAGE ; -DSKY_BUF: .FILL 8,0 +DSKY_BUF .FILL 8,0 DSKY_BUFLEN .EQU $ - DSKY_BUF DSKY_HEXBUF .FILL 4,0 DSKY_HEXBUFLEN .EQU $ - DSKY_HEXBUF diff --git a/Source/HBIOS/dsrtc.asm b/Source/HBIOS/dsrtc.asm index 690b7ea1..5bcd596f 100644 --- a/Source/HBIOS/dsrtc.asm +++ b/Source/HBIOS/dsrtc.asm @@ -63,37 +63,106 @@ ; ; CONSTANTS ; +; RTC SBC SBC-004 MFPIC N8 N8-CSIO SC +; ----- ------- ------- ------- ------- ------- ------- +; D7 WR RTC_OUT RTC_OUT -- RTC_OUT RTC_OUT RTC_OUT, I2C_SDA +; D6 WR RTC_CLK RTC_CLK -- RTC_CLK RTC_CLK RTC_CLK +; D5 WR /RTC_WE /RTC_WE -- /RTC_WE /RTC_WE /RTC_WE +; D4 WR RTC_CE RTC_CE -- RTC_CE RTC_CE RTC_CE +; D3 WR NC SPK /RTC_CE NC NC /SPI_CS2 +; D2 WR NC CLKHI RTC_CLK SPI_CS SPI_CS /SPI_CS1 +; D1 WR -- -- RTC_WE SPI_CLK NC FS +; D0 WR -- -- RTC_OUT SPI_DI NC I2C_SCL +; +; D7 RD -- -- -- -- -- I2C_SDA +; D6 RD CFG CFG -- SPI_DO CFG -- +; D5 RD -- -- -- -- -- -- +; D4 RD -- -- -- -- -- -- +; D3 RD -- -- -- -- -- -- +; D2 RD -- -- -- -- -- -- +; D1 RD -- -- -- -- -- -- +; D0 RD RTC_IN RTC_IN RTC_IN RTC_IN RTC_IN RTC_IN +; #IF (DSRTCMODE == DSRTCMODE_STD) ; -DSRTC_BASE .EQU RTC ; RTC PORT ON ALL SBC SERIES Z80 PLATFORMS +DSRTC_IO .EQU RTCIO ; RTC PORT +; +DSRTC_DATA .EQU %10000000 ; BIT 7 IS RTC DATA OUT +DSRTC_CLK .EQU %01000000 ; BIT 6 IS RTC CLOCK (CLK) +DSRTC_RD .EQU %00100000 ; BIT 5 IS DATA DIRECTION (/WE) +DSRTC_CE .EQU %00010000 ; BIT 4 IS CHIP ENABLE (CE) +; +DSRTC_MASK .EQU %11110000 ; MASK FOR BITS WE OWN IN RTC LATCH PORT +DSRTC_IDLE .EQU %00100000 ; QUIESCENT STATE +; +RTCDEF .SET DSRTC_IDLE ; FOR HBIOS MAINLINE +; +#DEFINE DSRTC_OPRVAL RTCVAL ; -DSRTC_DATA .EQU %10000000 ; BIT 7 CONTROLS RTC DATA (I/O) LINE -DSRTC_CLK .EQU %01000000 ; BIT 6 CONTROLS RTC CLOCK LINE, 1 = HIGH -DSRTC_RD .EQU %00100000 ; BIT 5 CONTROLS DATA DIRECTION, 1 = READ -DSRTC_CE .EQU %00010000 ; BIT 4 CONTROLS RTC CE LINE, 1 = HIGH (ENABLED) +; VALUES FOR DIFFERENT BATTERY OR SUPERCAPACITOR CHARGE RATES ; -DSRTC_RESET .EQU %00000000 ; ALL LOW +DS1d2k .EQU %10100101 ; 1 DIODE 2K RESISTOR (DEFAULT) +DS1d4k .EQU %10100110 ; 1 DIODE 4K RESISTOR +DS1d8k .EQU %10100111 ; 1 DOIDE 8K RESISTOR +DS2d2k .EQU %10101001 ; 2 DIODES 2K RESISTOR +DS2d4k .EQU %10101010 ; 2 DIODES 4K RESISTOR +DS2d8k .EQU %10101011 ; 2 DIODES 8K RESISTOR ; #ENDIF ; #IF (DSRTCMODE == DSRTCMODE_MFPIC) ; -DSRTC_BASE .EQU $43 ; RTC PORT ON MF/PIC +DSRTC_IO .EQU $43 ; RTC PORT ON MF/PIC ; -DSRTC_DATA .EQU %00000001 ; BIT 0 CONTROLS RTC DATA (I/O) LINE -DSRTC_CLK .EQU %00000100 ; BIT 2 CONTROLS RTC CLOCK LINE, 1 = HIGH -DSRTC_WR .EQU %00000010 ; BIT 1 CONTROLS DATA DIRECTION, 1 = WRITE -DSRTC_CE .EQU %00001000 ; BIT 3 CONTROLS RTC CE LINE, 0 = ENABLED +DSRTC_DATA .EQU %00000001 ; BIT 0 IS RTC DATA OUT +DSRTC_CLK .EQU %00000100 ; BIT 2 IS RTC CLOCK (CLK) +DSRTC_WR .EQU %00000010 ; BIT 1 IS DATA DIRECTION (WE) +DSRTC_CE .EQU %00001000 ; BIT 3 CHIP ENABLE (/CE) ; -DSRTC_RESET .EQU %00001000 ; ALL LOW, BUT CE = 1 +DSRTC_MASK .EQU %00001111 ; MASK FOR BITS WE OWN IN RTC LATCH PORT +DSRTC_IDLE .EQU %00001000 ; QUIESCENT STATE +; +#DEFINE DSRTC_OPRVAL DSRTC_RTCVAL ; #ENDIF ; DSRTC_BUFSIZ .EQU 7 ; 7 BYTE BUFFER (YYMMDDHHMMSSWW) ; +; RTC DEVICE PRE-INITIALIZATION ENTRY +; +DSRTC_PREINIT: +; + ; SET RELEVANT BITS IN RTC LATCH SHADOW REGISTER + ; TO THEIR QUIESENT STATE + LD A,(DSRTC_OPRVAL) ; GET CURRENT SHADOW REG VAL + AND ~DSRTC_MASK ; CLEAR OUR BITS + OR DSRTC_IDLE ; SET OUR IDLE BITS + LD (DSRTC_OPRVAL),A ; SAVE IT +; + CALL DSRTC_DETECT ; HARDWARE DETECTION + LD (DSRTC_STAT),A ; SAVE RESULT + RET NZ ; ABORT IF ERROR +; + ; CHECK FOR CLOCK HALTED + CALL DSRTC_TSTCLK + JR Z,DSRTC_PREINIT1 + ;PRTS(" INIT CLOCK $") + LD HL,DSRTC_TIMDEF + CALL DSRTC_TIM2CLK + LD HL,DSRTC_BUF + CALL DSRTC_WRCLK +; +DSRTC_PREINIT1: + XOR A ; SIGNAL SUCCESS + RET ; DONE +; ; RTC DEVICE INITIALIZATION ENTRY ; DSRTC_INIT: + LD A,(RTC_DISPACT) ; RTC DISPATCHER ALREADY SET? + OR A ; SET FLAGS + RET NZ ; IF ALREADY ACTIVE, ABORT +; CALL NEWLINE ; FORMATTING PRTS("DSRTC: MODE=$") ; @@ -104,14 +173,18 @@ DSRTC_INIT: PRTS("MFPIC$") #ENDIF ; - ; CHECK FOR CLOCK HALTED - CALL DSRTC_TSTCLK - JR Z,DSRTC_INIT1 - PRTS(" INIT CLOCK $") - LD HL,DSRTC_TIMDEF - CALL DSRTC_TIM2CLK - LD HL,DSRTC_BUF - CALL DSRTC_WRCLK + ; PRINT RTC LATCH PORT ADDRESS + PRTS(" IO=0x$") ; LABEL FOR IO ADDRESS + LD A,DSRTC_IO ; GET IO ADDRESS + CALL PRTHEXBYTE ; PRINT IT +; + ; CHECK PRESENCE STATUS + LD A,(DSRTC_STAT) ; GET DEVICE STATUS + OR A ; SET FLAGS + JR Z,DSRTC_INIT1 ; IF ZERO, ALL GOOD + PRTS(" NOT PRESENT$") ; NOT ZERO, H/W NOT PRESENT + OR $FF ; SIGNAL FAILURE + RET ; BAIL OUT ; DSRTC_INIT1: ; DISPLAY CURRENT TIME @@ -122,36 +195,26 @@ DSRTC_INIT1: CALL DSRTC_CLK2TIM LD HL,DSRTC_TIMBUF CALL PRTDT - -#IF DSRTCCHG ; FORCE_RTC_CHARGE_ENABLE - LD C,$8E ; ACCESS WRITE PROT REG - CALL DSRTC_CMD ; - LD A,$00 ; WRITE PROTECT OFF - CALL DSRTC_PUT ; - CALL DSRTC_END ; FINISH CMD - - LD C,$90 ; ACCESS CHARGE REGISTER - CALL DSRTC_CMD ; - LD A,$A5 ; STD CHARGE VALUES - CALL DSRTC_PUT ; - CALL DSRTC_END ; FINISH REG WRITE - - LD C,$8E ; ACCESS WRITE PROT REG - CALL DSRTC_CMD ; - LD A,$80 ; WRITE PROTECT ON - CALL DSRTC_PUT ; - CALL DSRTC_END ; FINISH CMD +; +#IF DSRTCCHG ; FORCE_RTC_CHARGE_ENABLE + LD C,$90 ; ACCESS CHARGE REGISTER + LD E,DS1d2k ; STD CHARGE VALUES + CALL DSRTC_WRBYTWP #ENDIF - - PRTS(" CHARGE=$") - CALL DSRTC_TSTCHG - JR NZ,NOCHG1 +; + PRTS(" CHARGE=$") ; DISPLAY + CALL DSRTC_TSTCHG ; CHARGING + JR NZ,NOCHG1 ; STATUS PRTS("ON$") - JR NOCHG2 + JR NOCHG2 NOCHG1: PRTS("OFF$") NOCHG2: - XOR A ; SIGNAL SUCCESS +; + LD BC,DSRTC_DISPATCH + CALL RTC_SETDISP +; + XOR A ; SIGNAL SUCCESS RET ; ; RTC DEVICE FUNCTION DISPATCH ENTRY @@ -176,8 +239,6 @@ DSRTC_DISPATCH: ; ; NVRAM FUNCTIONS ARE NOT AVAILABLE IN SIMULATOR ; -DSRTC_GETBYT: -DSRTC_SETBYT: DSRTC_GETBLK: DSRTC_SETBLK: CALL PANIC @@ -189,6 +250,9 @@ DSRTC_SETBLK: ; 24 HOUR TIME FORMAT IS ASSUMED ; DSRTC_GETTIM: + LD A,(DSRTC_STAT) ; GET DEVICE STATUS + OR A ; SET FLAGS + RET NZ ; BAIL OUT ON ERROR ; PUSH HL ; SAVE ADR OF OUTPUT BUF ; @@ -225,6 +289,9 @@ DSRTC_GETTIM: ; 24 HOUR TIME FORMAT IS ASSUMED ; DSRTC_SETTIM: + LD A,(DSRTC_STAT) ; GET DEVICE STATUS + OR A ; SET FLAGS + RET NZ ; BAIL OUT ON ERROR ; ; COPY INCOMING TIME DATA TO OUR TIME BUFFER LD A,(HB_INVBNK) ; COPY FROM CURRENT USER BANK @@ -251,6 +318,38 @@ DSRTC_SETTIM: XOR A ; SIGNAL SUCCESS RET ; AND RETURN ; +; RTC GET NVRAM BYTE +; C: INDEX +; E: VALUE (OUTPUT) +; +DSRTC_GETBYT: + LD A,(DSRTC_STAT) ; GET DEVICE STATUS + OR A ; SET FLAGS + RET NZ ; BAIL OUT ON ERROR + LD A,C ; INDEX + SLA A ; SHIFT TO INDEX BITS + ADD A,$C1 ; CMD OFFSET + LD C,A ; SAVE READ CMD BYTE + CALL DSRTC_RDBYT ; DO IT + XOR A ; SIGNAL SUCCESS + RET ; DONE +; +; RTC SET NVRAM BYTE +; C: INDEX +; E: VALUE +; +DSRTC_SETBYT: + LD A,(DSRTC_STAT) ; GET DEVICE STATUS + OR A ; SET FLAGS + RET NZ ; BAIL OUT ON ERROR + LD A,C ; INDEX + SLA A ; SHIFT TO INDEX BITS + ADD A,$C0 ; CMD OFFSET + LD C,A ; SAVE WRITE CMD BYTE + CALL DSRTC_WRBYTWP ; DO IT + XOR A ; SIGNAL SUCCESS + RET ; DONE +; ; CONVERT DATA IN CLOCK BUFFER TO TIME BUFFER AT HL ; DSRTC_CLK2TIM: @@ -303,14 +402,41 @@ DSRTC_TIM2CLK: ; TEST CLOCK FOR CHARGE DATA ; DSRTC_TSTCHG: - LD C,$91 ; CHARGE RESISTOR & DIODE VALUES - CALL DSRTC_CMD ; SEND THE COMMAND - CALL DSRTC_GET ; READ THE REGISTER - CALL DSRTC_END ; FINISH IT - AND %11110000 ; CHECK FOR - CP %10100000 ; ENABLED FLAG + LD C,$91 ; CHARGE RESISTOR & DIODE VALUES + CALL DSRTC_RDBYT ; GET VALUE + LD A,E ; VALUE TO A + AND %11110000 ; CHECK FOR + CP %10100000 ; ... ENABLED FLAG RET ; +; DETECT RTC HARDWARE PRESENCE +; +DSRTC_DETECT: + LD C,31 ; NVRAM INDEX 31 + CALL DSRTC_GETBYT ; GET VALUE + LD A,E ; TO ACCUM + LD (DSRTC_TEMP),A ; SAVE IT + XOR $FF ; FLIP ALL BITS + LD E,A ; TO E + LD C,31 ; NVRAM INDEX 31 + CALL DSRTC_SETBYT ; WRITE IT + LD C,31 ; NVRAM INDEX 31 + CALL DSRTC_GETBYT ; GET VALUE + LD A,(DSRTC_TEMP) ; GET SAVED VALUE + XOR $FF ; FLIP ALL BITS + CP E ; COMPARE WITH VALUE READ + LD A,0 ; ASSUME OK + JR Z,DSRTC_DETECT1 ; IF MATCH, GO AHEAD + LD A,$FF ; ELSE STATUS IS ERROR +DSRTC_DETECT1: + PUSH AF ; SAVE STATUS + LD A,(DSRTC_TEMP) ; GET SAVED VALUE + LD C,31 ; NVRAM INDEX 31 + CALL DSRTC_SETBYT ; SAVE IT + POP AF ; RECOVER STATUS + OR A ; SET FLAGS + RET ; DONE +; ; TEST CLOCK FOR VALID DATA ; READ CLOCK HALT BIT AND RETURN ZF BASED ON BIT VALUE ; 0 = RUNNING @@ -318,22 +444,70 @@ DSRTC_TSTCHG: ; DSRTC_TSTCLK: LD C,$81 ; SECONDS REGISTER HAS CLOCK HALT FLAG - CALL DSRTC_CMD ; SEND THE COMMAND - CALL DSRTC_GET ; READ THE REGISTER - CALL DSRTC_END ; FINISH IT + CALL DSRTC_RDBYT ; GET REGISTER VALUE + LD A,E ; VALUE TO A AND %10000000 ; HIGH ORDER BIT IS CLOCK HALT RET ; +; READ RAW BYTE +; C=READ CMD BYTE +; E=VALUE (OUTPUT) +; +DSRTC_RDBYT: + LD E,C + CALL DSRTC_CMD + CALL DSRTC_GET + CALL DSRTC_END + RET +; +; WRITE RAW BYTE +; C=WRITE CMD BYTE +; E=VALUE +; +DSRTC_WRBYT: + PUSH DE ; SAVE VALUE TO WRITE + LD E,C ; CMD TO E + CALL DSRTC_CMD + POP DE ; RESTORE VALUE + CALL DSRTC_PUT + CALL DSRTC_END + RET +; +; WRITE RAW BYTE W/ WRITE PROTECT BRACKETING +; C=WRITE CMD BYTE +; E=VALUE +; +DSRTC_WRBYTWP: + LD D,C ; WRITE CMD TO D + PUSH DE ; SAVE PARMS +; + ; TURN OFF WRITE PROTECT + LD C,$8E ; CMD + LD E,0 ; WRITE PROTECT OFF + CALL DSRTC_WRBYT ; DO IT +; + ; WRITE THE VALUE + POP DE ; RESTORE INPUTS + LD C,D ; WRITE CMD BACK TO C + CALL DSRTC_WRBYT ; DO IT +; + ; TURN WRITE PROTECT BACK ON + LD C,$8E ; WRITE CMD TO D + LD E,$80 ; WRITE PROTECT ON + CALL DSRTC_WRBYT ; DO IT +; + RET +; ; BURST READ CLOCK DATA INTO BUFFER AT HL ; DSRTC_RDCLK: - LD C,$BF ; COMMAND = $BF TO BURST READ CLOCK + LD E,$BF ; COMMAND = $BF TO BURST READ CLOCK CALL DSRTC_CMD ; SEND COMMAND TO RTC LD B,DSRTC_BUFSIZ ; B IS LOOP COUNTER DSRTC_RDCLK1: PUSH BC ; PRESERVE BC CALL DSRTC_GET ; GET NEXT BYTE - LD (HL),A ; SAVE IN BUFFER + LD (HL),E ; SAVE IN BUFFER INC HL ; INC BUF POINTER POP BC ; RESTORE BC DJNZ DSRTC_RDCLK1 ; LOOP IF NOT DONE @@ -342,34 +516,35 @@ DSRTC_RDCLK1: ; BURST WRITE CLOCK DATA FROM BUFFER AT HL ; DSRTC_WRCLK: - LD C,$8E ; COMMAND = $8E TO WRITE CONTROL REGISTER + LD E,$8E ; COMMAND = $8E TO WRITE CONTROL REGISTER CALL DSRTC_CMD ; SEND COMMAND - XOR A ; $00 = UNPROTECT + LD E,$00 ; $00 = UNPROTECT CALL DSRTC_PUT ; SEND VALUE TO CONTROL REGISTER CALL DSRTC_END ; FINISH IT ; - LD C,$BE ; COMMAND = $BE TO BURST WRITE CLOCK + LD E,$BE ; COMMAND = $BE TO BURST WRITE CLOCK CALL DSRTC_CMD ; SEND COMMAND TO RTC LD B,DSRTC_BUFSIZ ; B IS LOOP COUNTER DSRTC_WRCLK1: PUSH BC ; PRESERVE BC - LD A,(HL) ; GET NEXT BYTE TO WRITE + LD E,(HL) ; GET NEXT BYTE TO WRITE CALL DSRTC_PUT ; PUT NEXT BYTE INC HL ; INC BUF POINTER POP BC ; RESTORE BC DJNZ DSRTC_WRCLK1 ; LOOP IF NOT DONE - LD A,$80 ; ADD CONTROL REG BYTE, $80 = PROTECT ON + LD E,$80 ; ADD CONTROL REG BYTE, $80 = PROTECT ON CALL DSRTC_PUT ; WRITE REQUIRED 8TH BYTE JP DSRTC_END ; FINISH IT ; -#IF (DSRTCMODE == DSRTCMODE_STD) -; -; SEND COMMAND IN C TO RTC +; SEND COMMAND IN E TO RTC ; ALL RTC SEQUENCES MUST CALL THIS FIRST TO SEND THE RTC COMMAND. -; THE COMMAND IS SENT VIA A PUT. CE AND CLK ARE LEFT HIGH! THIS +; THE COMMAND IS SENT VIA A PUT. CE AND CLK ARE LEFT ASSERTED! THIS ; IS INTENTIONAL BECAUSE WHEN THE CLOCK IS LOWERED, THE FIRST BIT ; WILL BE PRESENTED TO READ (IN THE CASE OF A READ CMD). ; +; N.B. REGISTER A CONTAINS WORKING VALUE OF LATCH PORT AND MUST NOT +; BE MODIFIED BETWEEN CALLS TO DSRTC_CMD, DSRTC_PUT, AND DSRTC_GET. +; ; 0) ASSUME ALL LINES UNDEFINED AT ENTRY ; 1) DEASSERT ALL LINES (CE, RD, CLOCK, & DATA) ; 2) WAIT 1US @@ -378,19 +553,22 @@ DSRTC_WRCLK1: ; 5) PUT COMMAND ; DSRTC_CMD: - XOR A ; ALL LINES LOW TO RESET - OUT (DSRTC_BASE),A ; WRITE TO RTC PORT + LD A,(DSRTC_OPRVAL) ; INIT A WITH QUIESCENT STATE + OUT (DSRTC_IO),A ; WRITE TO PORT CALL DLY2 ; DELAY 2 * 27 T-STATES - XOR DSRTC_CE ; NOW SET CE HIGH - OUT (DSRTC_BASE),A ; WRITE TO RTC PORT +#IF (DSRTCMODE == DSRTCMODE_MFPIC) + AND ~DSRTC_CE ; ASSERT CE (LOW) +#ELSE + OR DSRTC_CE ; ASSERT CE (HIGH) +#ENDIF + OUT (DSRTC_IO),A ; WRITE TO RTC PORT CALL DLY2 ; DELAY 2 * 27 T-STATES - LD A,C ; LOAD COMMAND CALL DSRTC_PUT ; WRITE IT RET ; -; WRITE BYTE IN A TO THE RTC -; WRITE BYTE IN A TO THE RTC. CE IS IMPLICITY ASSERTED AT -; THE START. CE AND CLK ARE LEFT HIGH AT THE END IN CASE +; WRITE BYTE IN E TO THE RTC +; WRITE BYTE IN E TO THE RTC. CE IS IMPLICITY ASSERTED AT +; THE START. CE AND CLK ARE LEFT ASSERTED AT THE END IN CASE ; NEXT ACTION IS A READ. ; ; 0) ASSUME ENTRY WITH CE HI, OTHERS UNDEFINED @@ -404,135 +582,36 @@ DSRTC_CMD: ; DSRTC_PUT: LD B,8 ; LOOP FOR 8 BITS - LD C,A ; SAVE THE WORKING VALUE -DSRTC_PUT1: - LD A,DSRTC_CE ; SET CLOCK LOW - OUT (DSRTC_BASE),A ; DO IT - CALL DLY1 ; DELAY 27 T-STATES - LD A,C ; RECOVER WORKING VALUE - RRCA ; ROTATE NEXT BIT TO SEND INTO BIT 7 - LD C,A ; SAVE WORKING VALUE - AND %10000000 ; ISOLATE THE DATA BIT - OR DSRTC_CE ; KEEP CE HIGH - OUT (DSRTC_BASE),A ; ASSERT DATA BIT ON BUS - OR DSRTC_CLK ; SET CLOCK HI - OUT (DSRTC_BASE),A ; DO IT - CALL DLY1 ; DELAY 27 T-STATES - DJNZ DSRTC_PUT1 ; LOOP IF NOT DONE - RET -; -; READ BYTE FROM RTC, RETURN VALUE IN A -; READ THE NEXT BYTE FROM THE RTC INTO A. CE IS IMPLICITLY -; ASSERTED AT THE START. CE AND CLK ARE LEFT HIGH AT -; THE END. CLOCK *MUST* BE LEFT HIGH FROM DSRTC_CMD! -; -; 0) ASSUME ENTRY WITH CE HI, OTHERS UNDEFINED -; 1) SET RD HI AND CLOCK LOW -; 3) WAIT 250NS (CLOCK PUTS DATA BIT ON BUS) -; 4) READ DATA BIT -; 5) SET CLOCK HI -; 6) WAIT 250NS -; 7) LOOP FOR 8 DATA BITS -; 8) EXIT WITH CE,CLK,RD HI -; -DSRTC_GET: - LD C,0 ; INITIALIZE WORKING VALUE TO 0 - LD B,8 ; LOOP FOR 8 BITS -DSRTC_GET1: - LD A,DSRTC_CE | DSRTC_RD ; SET CLK LO - OUT (DSRTC_BASE),A ; WRITE TO RTC PORT - CALL DLY2 ; DELAY 2 * 27 T-STATES - IN A,(DSRTC_BASE) ; READ THE RTC PORT - AND %00000001 ; ISOLATE THE DATA BIT - OR C ; COMBINE WITH WORKING VALUE - RRCA ; ROTATE FOR NEXT BIT - LD C,A ; SAVE WORKING VALUE - LD A,DSRTC_CE | DSRTC_CLK | DSRTC_RD ; CLOCK BACK TO HI - OUT (DSRTC_BASE),A ; WRITE TO RTC PORT - CALL DLY1 ; DELAY 27 T-STATES - DJNZ DSRTC_GET1 ; LOOP IF NOT DONE (13) - LD A,C ; GET RESULT INTO A - RET -; -; COMPLETE A COMMAND SEQUENCE -; FINISHES UP A COMMAND SEQUENCE. -; DOES NOT DESTROY ANY REGISTERS. -; -; 1) SET ALL LINES LO -; -DSRTC_END: - PUSH AF ; SAVE AF - XOR A ; ALL LINES OFF TO CLEAN UP - OUT (DSRTC_BASE),A ; WRITE TO RTC PORT - POP AF ; RESTORE AF - RET -; -#ENDIF -; #IF (DSRTCMODE == DSRTCMODE_MFPIC) -; -; -; SEND COMMAND IN C TO RTC -; ALL RTC SEQUENCES MUST CALL THIS FIRST TO SEND THE RTC COMMAND. -; THE COMMAND IS SENT VIA A PUT. CE AND CLK ARE LEFT ACTIVE! THIS -; IS INTENTIONAL BECAUSE WHEN THE CLOCK IS LOWERED, THE FIRST BIT -; WILL BE PRESENTED TO READ (IN THE CASE OF A READ CMD). -; -; 0) ASSUME ALL LINES UNDEFINED AT ENTRY -; 1) DEASSERT ALL LINES (CE, RD, CLOCK, & DATA) -; 2) WAIT 1US -; 3) SET CE HI -; 4) WAIT 1US -; 5) PUT COMMAND -; -DSRTC_CMD: - ;XOR A ; ALL LINES LOW TO RESET - LD A,DSRTC_RESET ; QUIESCENT STATE - OUT (DSRTC_BASE),A ; WRITE TO RTC PORT - CALL DLY2 ; DELAY 2 * 27 T-STATES - XOR DSRTC_CE ; NOW ASSERT CE - OUT (DSRTC_BASE),A ; WRITE TO RTC PORT - CALL DLY2 ; DELAY 2 * 27 T-STATES - LD A,C ; LOAD COMMAND - CALL DSRTC_PUT ; WRITE IT - RET -; -; WRITE BYTE IN A TO THE RTC -; WRITE BYTE IN A TO THE RTC. CE IS IMPLICITY ASSERTED AT -; THE START. CE AND CLK ARE LEFT ASSERTED AT THE END IN CASE -; NEXT ACTION IS A READ. -; -; 0) ASSUME ENTRY WITH CE ASSERTED, OTHERS UNDEFINED -; 1) CLOCK -> LOW -; 2) WAIT 250NS -; 3) SET DATA ACCORDING TO BIT VALUE -; 4) CLOCK -> HIGH -; 5) WAIT 250NS (CLOCK READS DATA BIT FROM BUS) -; 6) LOOP FOR 8 DATA BITS -; 7) EXIT WITH CE AND CLOCK ASSERTED -; -DSRTC_PUT: - LD B,8 ; LOOP FOR 8 BITS - LD C,A ; SAVE THE WORKING VALUE - LD A,DSRTC_WR | DSRTC_CLK ; MODE=WRITE, CLOCK ON, CE ACTIVE (0) + OR DSRTC_WR ; SET WRITE MODE +#ELSE + AND ~DSRTC_RD ; SET WRITE MODE +#ENDIF DSRTC_PUT1: - XOR DSRTC_CLK ; FLIP CLOCK OFF - OUT (DSRTC_BASE),A ; DO IT + AND ~DSRTC_CLK ; SET CLOCK LOW + OUT (DSRTC_IO),A ; DO IT CALL DLY1 ; DELAY 27 T-STATES + +#IF (DSRTCMODE == DSRTCMODE_MFPIC) RRA ; PREP ACCUM TO GET DATA BIT IN CARRY - RR C ; ROTATE NEXT BIT TO SEND INTO CARRY + RR E ; ROTATE NEXT BIT TO SEND INTO CARRY RLA ; ROTATE BITS BACK TO CORRECT POSTIIONS - OUT (DSRTC_BASE),A ; ASSERT DATA BIT ON BUS - XOR DSRTC_CLK ; FLIP CLOCK ON - OUT (DSRTC_BASE),A ; DO IT, DATA BIT SENT ON RISING EDGE +#ELSE + RLA ; PREP ACCUM TO GET DATA BIT IN CARRY + RR E ; ROTATE NEXT BIT TO SEND INTO CARRY + RRA ; ROTATE BITS BACK TO CORRECT POSTIIONS +#ENDIF + OUT (DSRTC_IO),A ; ASSERT DATA BIT ON BUS + OR DSRTC_CLK ; SET CLOCK HI + OUT (DSRTC_IO),A ; DO IT CALL DLY1 ; DELAY 27 T-STATES DJNZ DSRTC_PUT1 ; LOOP IF NOT DONE RET ; -; READ BYTE FROM RTC, RETURN VALUE IN A -; READ THE NEXT BYTE FROM THE RTC INTO A. CE IS IMPLICITLY -; ASSERTED AT THE START. CE AND CLK ARE LEFT HIGH AT -; THE END. CLOCK *MUST* BE LEFT HIGH FROM DSRTC_CMD! +; READ BYTE FROM RTC, RETURN VALUE IN E +; READ THE NEXT BYTE FROM THE RTC INTO E. CE IS IMPLICITLY +; ASSERTED AT THE START. CE AND CLK ARE LEFT ASSERTED AT +; THE END. CLOCK *MUST* BE LEFT ASSERTED FROM DSRTC_CMD! ; ; 0) ASSUME ENTRY WITH CE HI, OTHERS UNDEFINED ; 1) SET RD HI AND CLOCK LOW @@ -544,52 +623,57 @@ DSRTC_PUT1: ; 8) EXIT WITH CE,CLK,RD HI ; DSRTC_GET: - LD C,0 ; INITIALIZE WORKING VALUE TO 0 + LD E,0 ; INITIALIZE WORKING VALUE TO 0 LD B,8 ; LOOP FOR 8 BITS - LD A,DSRTC_CLK ; MODE=READ, CLOCK ON, CE ACTIVE (0) +#IF (DSRTCMODE == DSRTCMODE_MFPIC) + AND ~DSRTC_WR ; SET READ MODE +#ELSE + OR DSRTC_RD ; SET READ MODE +#ENDIF DSRTC_GET1: - XOR DSRTC_CLK ; FLIP CLOCK OFF - OUT (DSRTC_BASE),A ; DO IT - CALL DLY2 ; DELAY 2 * 27 T-STATES - IN A,(DSRTC_BASE) ; READ THE RTC PORT + AND ~DSRTC_CLK ; SET CLK LO + OUT (DSRTC_IO),A ; WRITE TO RTC PORT + CALL DLY1 ; DELAY 2 * 27 T-STATES + PUSH AF ; SAVE PORT VALUE + IN A,(DSRTC_IO) ; READ THE RTC PORT RRA ; DATA BIT TO CARRY - RR C ; SHIFT INTO WORKING VALUE - LD A,DSRTC_CLK ; CLOCK ON - OUT (DSRTC_BASE),A ; WRITE TO RTC PORT + RR E ; SHIFT INTO WORKING VALUE + POP AF ; RESTORE PORT VALUE + OR DSRTC_CLK ; CLOCK BACK TO HI + OUT (DSRTC_IO),A ; WRITE TO RTC PORT CALL DLY1 ; DELAY 27 T-STATES - DJNZ DSRTC_GET1 ; LOOP IF NOT DONE - LD A,C ; GET RESULT INTO A + DJNZ DSRTC_GET1 ; LOOP IF NOT DONE (13) RET ; ; COMPLETE A COMMAND SEQUENCE ; FINISHES UP A COMMAND SEQUENCE. ; DOES NOT DESTROY ANY REGISTERS. ; -; 1) BACK TO QUIESCENT STATE +; 1) SET ALL LINES BACK TO QUIESCENT STATE ; DSRTC_END: - PUSH AF ; SAVE AF - ;XOR A ; ALL LINES OFF TO CLEAN UP - LD A,DSRTC_RESET ; QUIESCENT STATE - OUT (DSRTC_BASE),A ; WRITE TO RTC PORT - POP AF ; RESTORE AF - RET -; -#ENDIF + LD A,(DSRTC_OPRVAL) ; INIT A WITH QUIESCENT STATE + OUT (DSRTC_IO),A ; WRITE TO PORT + RET ; RETURN ; ; WORKING VARIABLES ; +DSRTC_STAT .DB 0 ; DEVICE STATUS (0=OK) +DSRTC_TEMP .DB 0 ; TEMP VALUE STORAGE +; +DSRTC_RTCVAL .DB DSRTC_IDLE ; LOCAL LATCH SHADOW FOR MFPIC +; ; DSRTC_BUF IS USED FOR BURST READ/WRITE OF CLOCK DATA TO DS-1302 ; FIELDS BELOW MATCH ORDER OF DS-1302 FIELDS (BCD) ; DSRTC_BUF: -DSRTC_SEC: .DB 0 ; SECOND -DSRTC_MIN: .DB 0 ; MINUTE -DSRTC_HR: .DB 0 ; HOUR -DSRTC_DT: .DB 0 ; DATE -DSRTC_MON: .DB 0 ; MONTH -DSRTC_DAY: .DB 0 ; DAY OF WEEK -DSRTC_YR: .DB 0 ; YEAR +DSRTC_SEC .DB 0 ; SECOND +DSRTC_MIN .DB 0 ; MINUTE +DSRTC_HR .DB 0 ; HOUR +DSRTC_DT .DB 0 ; DATE +DSRTC_MON .DB 0 ; MONTH +DSRTC_DAY .DB 0 ; DAY OF WEEK +DSRTC_YR .DB 0 ; YEAR ; ; DSRTC_TIMBUF IS TEMP BUF USED TO STORE TIME TEMPORARILY TO DISPLAY ; IT. diff --git a/Source/HBIOS/eastaegg.asm b/Source/HBIOS/eastaegg.asm index 24744f11..35c4c982 100644 --- a/Source/HBIOS/eastaegg.asm +++ b/Source/HBIOS/eastaegg.asm @@ -28,7 +28,7 @@ scale .equ 256 ; Do NOT change this - the ; this scaling factor! :-) divergent .equ scale * 4 - ld sp,0fdffh + ld sp,HBX_LOC ld hl, welcome ; Print a welcome message call _puts @@ -173,16 +173,29 @@ inner_loop_end call _putcrlf ; Print a CR/LF pair mandel_end ld hl, finished ; Print finished-message call _puts ; GET CONSOLE INPUT STATUS VIA HBIOS -waitch LD C,CIODEV_CONSOLE ; CONSOLE UNIT TO C - LD B,BF_CIOIST ; HBIOS FUNC: INPUT STATUS - RST 08 ; HBIOS RETURNS STATUS IN A - ; RESTORE REGISTERS (AF IS OUTPUT) - JR Z,waitch - ; Return to the loader - LD A,BID_BOOT ; BOOT BANK - LD HL,0 ; ADDRESS ZERO - CALL HB_BNKCALL ; DOES NOT RETURN - HALT +waitch +#IF (BIOS == BIOS_WBW) + LD C,CIO_CONSOLE ; CONSOLE UNIT TO C + LD B,BF_CIOIN ; HBIOS FUNC: INPUT CHAR + RST 08 ; DO IT + + ; RETURN TO THE LOADER + LD A,BID_BOOT ; BOOT BANK + LD HL,0 ; ADDRESS ZERO + CALL HB_BNKCALL ; DOES NOT RETURN + HALT +#ENDIF +#IF (BIOS == BIOS_UNA) + LD B,0 ; CONSOLE UNIT TO B + LD C,BF_CIOIN ; UBIOS FUNC: INPUT CHAR + CALL $FFFD ; DO IT + + ; RETURN TO THE LOADER + LD BC,$01FB ; UNA FUNC = SET BANK + LD DE,0 ; ROM BANK 0 + CALL $FFFD ; DO IT + JP 0 ; JUMP TO RESTART ADDRESS +#ENDIF _putcrlf ld hl, crlf _puts push af @@ -195,22 +208,41 @@ puts0 ld a,(hl) puts1 pop af ret -_putc PUSH AF - PUSH BC - PUSH DE - PUSH HL - LD E,A ; OUTPUT CHAR TO E - LD C,CIODEV_CONSOLE; CONSOLE UNIT TO C - LD B,BF_CIOOUT ; HBIOS FUNC: OUTPUT CHAR - RST 08 ; HBIOS OUTPUTS CHARACTDR - POP HL - POP DE - POP BC - POP AF - RET +_putc +#IF (BIOS == BIOS_WBW) + PUSH AF + PUSH BC + PUSH DE + PUSH HL + LD E,A ; OUTPUT CHAR TO E + LD C,CIO_CONSOLE ; CONSOLE UNIT TO C + LD B,BF_CIOOUT ; HBIOS FUNC: OUTPUT CHAR + RST 08 ; HBIOS OUTPUTS CHARACTDR + POP HL + POP DE + POP BC + POP AF + RET +#ENDIF +#IF (BIOS == BIOS_UNA) + PUSH AF + PUSH BC + PUSH DE + PUSH HL + LD E,A ; OUTPUT CHAR TO E + LD B,0 ; CONSOLE UNIT TO B + LD C,BF_CIOOUT ; UBIOS FUNC: OUTPUT CHAR + CALL $FFFD ; UBIOS OUTPUTS CHARACTDR + POP HL + POP DE + POP BC + POP AF + RET +#ENDIF -welcome .db "Generating a Mandelbrot set" - .db cr, lf, eos + +welcome .db "Generating a Mandelbrot set..." + .db cr, lf, cr, lf, eos finished .db "Computation finished." crlf .db cr, lf, eos diff --git a/Source/HBIOS/fd.asm b/Source/HBIOS/fd.asm index a200c443..df2673be 100644 --- a/Source/HBIOS/fd.asm +++ b/Source/HBIOS/fd.asm @@ -46,13 +46,21 @@ FDC_MSR .EQU $50 ; 8272 MAIN STATUS REGISTER FDC_DATA .EQU $51 ; 8272 DATA PORT FDC_DOR .EQU $58 ; DIGITAL OUTPUT REGISTER (LATCH) #ENDIF -#IF (FDMODE = FDMODE_RCWDC) +#IF (FDMODE == FDMODE_RCWDC) FDC_MSR .EQU $50 ; 8272 MAIN STATUS REGISTER FDC_DATA .EQU $51 ; 8272 DATA PORT FDC_DOR .EQU $58 ; DIGITAL OUTPUT REGISTER FDC_DCR .EQU $48 ; CONFIGURATION CONTROL REGISTER FDC_TC .EQU $58 ; TERMINAL COUNT (W/ DACK) #ENDIF +#IF (FDMODE == FDMODE_DYNO) +FDC_BASE .EQU $84 +FDC_MSR .EQU FDC_BASE + $00 ; 8272 MAIN STATUS REGISTER +FDC_DATA .EQU FDC_BASE + $01 ; 8272 DATA PORT +FDC_DOR .EQU FDC_BASE + $02 ; DIGITAL OUTPUT REGISTER +FDC_DCR .EQU FDC_BASE + $03 ; CONFIGURATION CONTROL REGISTER +FDC_TC .EQU FDC_BASE + $02 ; TERMINAL COUNT (W/ DACK) +#ENDIF ; ; DISK OPERATIONS ; @@ -106,18 +114,18 @@ FD_CFGTBL: .DB 0 ; DEVICE STATUS .DB FDMEDIA ; MEDIA TYPE .DB $FF ; CURRENT TRACK - .DB 0 ; HOST TRACK + .DW 0 ; HOST TRACK .DB 0 ; HOST SECTOR - .DW 0 ; HOST HEAD + .DB 0 ; HOST HEAD #IF (FD_DEVCNT >= 2) ; DEVICE 1, PRIMARY SLAVE .DB 1 ; DRIVER DEVICE NUMBER .DB 0 ; DEVICE STATUS .DB FDMEDIA ; MEDIA TYPE .DB $FF ; CURRENT TRACK - .DB 0 ; HOST TRACK + .DW 0 ; HOST TRACK .DB 0 ; HOST SECTOR - .DW 0 ; HOST HEAD + .DB 0 ; HOST HEAD #ENDIF ; #IF ($ - FD_CFGTBL) != (FD_DEVCNT * FD_CFGSIZ) @@ -391,7 +399,7 @@ DOR_INIT .EQU 11100000B ; INITIAL DEFAULT LATCH VALUE ; ; *** DIDE/N8/ZETA V2 *** ; -#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC)) +#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC) | (FDMODE == FDMODE_DYNO)) DOR_INIT .EQU 00001100B ; SOFT RESET INACTIVE, DMA ENABLED DOR_BR250 .EQU DOR_INIT DOR_BR500 .EQU DOR_INIT @@ -615,7 +623,7 @@ FD_GEOM: LD A,(HL) ; GET TRACKS INC HL ; POINT TO HEADS LD D,(HL) ; GET HEADS - ;SET 7,D ; SET LBA CAPABILITY BIT (FUTURE) + SET 7,D ; SET LBA CAPABILITY BIT INC HL ; POINT TO SECTORS LD E,(HL) ; GET SECTORS LD L,A ; L := TRACKS @@ -811,6 +819,36 @@ FD_DRIVERESET: ; ; FD_SEEK: + ; DE:HL CONTAINS EITHER LBA OR CHS + BIT 7,D ; TEST LBA BIT + JR Z,FD_SEEK9 ; IF NOT LBA, JUST SAVE INCOMING VALUE + ; NEED TO CONVERT LBA IN DE:HL TO CHS + ; NOTE: FLOPPY LBA WILL NEVER EXCEED 16 BITS, SO WE IGNORE DE ENTIRELY + PUSH HL ; SAVE HL + CALL FD_GEOM ; E := SPT, D := HDS + POP HL ; RESTORE HL + RET NZ ; BAIL OUT ON ERROR + RES 7,D ; MAKE SURE LBA BIT IS CLEARED + LD (FD_CURGEOM),DE ; SAVE AS FD_CURSPT & FD_CURHDS + LD A,(FD_CURSPT) ; A := SECTORS PER TRACK + LD D,0 ; DE := SPT + LD E,A + CALL DIV16 ; DIVIDE, REMAINDER (SECTOR #) IN HL + PUSH HL ; SAVE SECTOR # + PUSH BC ; CYLINDERS AND HEADS BACK TO HL + POP HL + LD A,(FD_CURHDS) ; A := HEADS PER CYLINDER + LD D,0 ; DE : = HEADS PER CYLINDER + LD E,A + CALL DIV16 ; DIVIDE, BC := TRACK, REMAINDER (HEAD #) IN HL + PUSH HL ; SAVE HEAD # + PUSH BC ; COPY TRACK # TO HL + POP HL + POP BC ; RECOVER HEAD # + LD D,C ; HEAD # TO D + POP BC ; RECOVER SECTOR # + LD E,C ; SECTOR # TO E +FD_SEEK9: ; NOT LBA, JUST SAVE THE CHS VALUE IN CFG ENTRY PUSH HL ; SAVE INCOMING HL TO (SP) LD A,FD_HST ; A := HST OFFSET IN CFG ENTRY CALL LDHLIYA ; HL := HST VALUE ADR @@ -821,11 +859,13 @@ FD_SEEK: RET ; FD_READ: + CALL HB_DSKREAD ; HOOK HBIOS DISK READ SUPERVISOR LD (FD_DSKBUF),HL ; SAVE DISK BUFFER ADDRESS LD A,DOP_READ JR FD_RW ; FD_WRITE: + CALL HB_DSKWRITE ; HOOK HBIOS DISK WRITE SUPERVISOR LD (FD_DSKBUF),HL ; SAVE DISK BUFFER ADDRESS LD A,DOP_WRITE JR FD_RW @@ -1195,7 +1235,7 @@ FC_SETDOR ; ; SET FST_DCR ; -#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC)) +#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC) | (FDMODE == FDMODE_DYNO)) ; FC_SETDCR LD (FST_DCR),A @@ -1227,7 +1267,7 @@ FC_RESETFDC: #IF ((FDMODE == FDMODE_ZETA) | (FDMODE == FDMODE_DIO3) | (FDMODE == FDMODE_RCSMC)) RES 7,A #ENDIF -#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC)) +#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC) | (FDMODE == FDMODE_DYNO)) LD A,0 #ENDIF CALL FC_SETDOR @@ -1242,7 +1282,7 @@ FC_RESETFDC: ; PULSE TERMCT TO TERMINATE ANY ACTIVE EXECUTION PHASE ; FC_PULSETC: -#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC)) +#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC) | (FDMODE == FDMODE_DYNO)) IN A,(FDC_TC) #ELSE LD A,(FST_DOR) @@ -1256,9 +1296,13 @@ FC_PULSETC: ; SET FST_DOR FOR MOTOR CONTROL ON ; FC_MOTORON: - LD BC,300H -; LD BC,10H - LD (FCD_IDLECNT),BC +; LD BC,300H +; LD BC,50H +; LD (FCD_IDLECNT),BC + + LD A,(CB_CPUMHZ) + RLCA + LD (FCD_IDLECNT),A #IF (FDTRACE >= 3) LD DE,FDSTR_MOTON @@ -1295,7 +1339,7 @@ FC_MOTORON1: CP C ; COMPARE TO NEW MOTOR BITS RET Z ; SKIP DELAY, MOTOR WAS ALREADY ON #ENDIF -#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC)) +#IF ((FDMODE == FDMODE_DIDE) | (FDMODE == FDMODE_N8) | (FDMODE == FDMODE_ZETA2) | (FDMODE == FDMODE_RCWDC) | (FDMODE == FDMODE_DYNO)) ; SETUP DCR FOR DIDE HARDWARE LD A,(FCD_DCR) ; GET NEW DCR VALUE CALL FC_SETDCR ; AND IMPLEMENT IT @@ -1391,7 +1435,7 @@ FOP_CMD4: ; START OF STATUS LOOP, WAIT FOR FDC TO BE READY FOR BYTE CP 080H ; LOOKING FOR RQM=1, DIO=0 (FDC READY FOR A BYTE) JR Z,FOP_CMD6 ; GOOD, GO TO SEND BYTE CP 0C0H ; HMMMM... RQM=1 & DIO=1, FDC WANTS TO SEND US DATA, UNEXPECTED - JR Z,FOP_RES ; GO IMMEDIATELY TO RESULTS??? + JP Z,FOP_RES ; GO IMMEDIATELY TO RESULTS??? DJNZ FOP_CMD4 ; LOOP TILL COUNTER EXHAUSTED JP FOP_TOSNDCMD ; COUNTER EXHAUSTED, TIMEOUT / EXIT @@ -1407,28 +1451,148 @@ FOP_CMD6: ; SEND NEXT BYTE FOP_X1: LD A,(FCP_CMD) - LD HL,FOP_RES - PUSH HL - CP CFD_READ - JP Z,FXR_READ + JR Z,FXR_READ CP CFD_WRITE - JP Z,FXR_WRITE + JR Z,FXR_WRITE CP CFD_READID - JP Z,FXR_NULL - RET ; RET ACTUALLY JUST JUMPS RIGHT TO FOP_RES + JR Z,FXR_NULL + JP FOP_RES +;; +;; EXECUTION ROUTINES +;; ; -; RESULTS PHASE +; NULL EXECUTION, NO DATA TO READ/WRITE (USED BY SPECIFY, READID, ETC.) ; -; 1) CHECK FST_RC AND BAIL OUT IF SET??? -; 2) COMPARE MSR CHECKING TO FXR_NULL (WHICH IS RIGHT?) -; 3) WHEN DUMPING FRB, USE THE RECORDED LENGTH??? +; DO NOTHING, BUT WAIT FOR EXEC B IT TO CLEAR FDC READY. +; LOOP NEEDS TO ALLOW FOR 2 FULL ROTATIONS OF THE DISK +; WHICH IS 400ms AT 300RPM +; +FXR_NULL: + LD BC,$7000 ; LOOP COUNTER, $7000 * 16us = ~485ms +FXR_NULL1: + CALL DELAY ; FDC MAY TAKE UP TO 12us TO UPDATE MSR + IN A,(FDC_MSR) ; GET MSR + AND 0E0H ; ISOLATE RQM/DIO/NDM + CP 0C0H ; WE WANT RQM=1,DIO=1,NDM=0 (READY TO READ A BYTE W/ EXEC INACTIVE) + JP Z,FOP_RES ; EXEC DONE, EXIT CLEAN W/O PULSING TC + ;JP Z,FXR_NULL2 ; *DEBUG* + DEC BC ; DECREMENT COUNTER (16 BIT) + LD A,B ; CHECK FOR ZERO + OR C ; " + JR NZ,FXR_NULL1 ; NOT ZERO YET, KEEP CHECKING + JP FOP_TOEXEC ; TIMEOUT EXIT +;FXR_NULL2: ; *DEBUG* +; CALL PRTHEXBYTE +; CALL PRTHEXWORD +; JP FOP_RES +; +; READ DATA +; +FXR_READ: + HB_DI ; TIME CRITICAL , INTERRUPTS WILL CAUSE I/O ERRS + LD HL,(FD_DSKBUF) ; POINT TO SECTOR BUFFER START + LD DE,(FCD_SECSZ) + ; TIMEOUT COUNTER IS CPU MHZ / 4 (MAKING SURE IT IS AT LEAST 1) +; LD A,(CPUMHZ + 3) / 4 + LD A,(CB_CPUMHZ) ; GET CPU SPEED IN MHZ + ADD A,3 ; ROUND UP + SRL A ; SHIFT RIGHT TWICE + ;SRL A ; ... TO DIVIDE BY 4 + ;INC A ; MAKE SURE RESULT IS AT LEAST 1 + LD (FCD_TO),A ; INIT TIMEOUT COUNTER +FXRR1 LD C,0 ; OUTER LOOP TIMEOUT COUNTER +FXRR2: LD B,0 ; SETUP FOR 256 ITERATIONS +FXRR3: IN A,(FDC_MSR) ; GET MSR + CP 0F0H ; WE WANT RQM=1,DIO=1,NDM=1,BUSY=1 (READY TO RECEIVE A BYTE W/ EXEC ACTIVE) + JR Z,FXRR4 ; GOT IT, DO BYTE READ + DJNZ FXRR3 ; NOT READY, LOOP IF COUNTER NOT ZERO + JR FXRR5 ; COUNTER ZERO, GO TO OUTER LOOP LOGIC + +FXRR4: IN A,(FDC_DATA) ; GET PENDING BYTE + LD (HL),A ; STORE IT IN BUFFER + INC HL ; INCREMENT THE BUFFER POINTER + DEC DE ; DECREMENT BYTE COUNT + LD A,D + OR E + JR NZ,FXRR2 ; IF NOT ZERO, REPEAT LOOP + JR FXR_END ; CLEAN EXIT + +FXRR5: ; OUTER LOOP, REALLY ONLY HAPPENS WHEN WAITING FOR FIRST BYTE OR ABORTED + AND 0E0H ; ISOLATE RQM/DIO/NDM + CP 0C0H ; IF RQM=1, DIO=1, NDM=0 (EXECUTION ABORTED) + JR Z,FXR_ABORT ; BAIL OUT TO ERR ROUTINE, FIX: GO TO SPECIFIC ROUTINE FOR THIS??? + DEC C + JR NZ,FXRR2 ; IF NOT ZERO, LOOP SOME MORE + LD A,(FCD_TO) + DEC A + LD (FCD_TO),A + JR NZ,FXRR1 + JR FXR_TO ; OTHERWISE, TIMEOUT ERROR +; +; WRITE DATA +; +FXR_WRITE: + HB_DI ; TIME CRITICAL , INTERRUPTS WILL CAUSE I/O ERRS + LD HL,(FD_DSKBUF) ; POINT TO SECTOR BUFFER START + LD DE,(FCD_SECSZ) + ; TIMEOUT COUNTER IS CPU MHZ / 4 (MAKING SURE IT IS AT LEAST 1) +; LD A,(CPUMHZ + 3) / 4 + LD A,(CB_CPUMHZ) ; GET CPU SPEED IN MHZ + ADD A,3 ; ROUND UP + SRL A ; SHIFT RIGHT TWICE + ;SRL A ; ... TO DIVIDE BY 4 + ;INC A ; MAKE SURE RESULT IS AT LEAST 1 + LD (FCD_TO),A +FXRW1 LD C,0 ; OUTER LOOP TIMEOUT COUNTER +FXRW2: LD B,0 ; SETUP FOR 256 ITERATIONS +FXRW3: IN A,(FDC_MSR) ; GET MSR + CP 0B0H ; WE WANT RQM=1,DIO=0,NDM=1,BUSY=1 (READY TO SEND A BYTE W/ EXEC ACTIVE) + JR Z,FXRW4 ; GOT IT, DO BYTE WRITE + DJNZ FXRW3 ; NOT READY, LOOP IF COUNTER NOT ZERO + JR FXRW5 ; COUNTER ZERO, GO TO OUTER LOOP LOGIC +FXRW4: LD A,(HL) ; GET NEXT BYTE TO WRITE + OUT (FDC_DATA),A ; WRITE IT + INC HL ; INCREMENT THE BUFFER POINTER + DEC DE ; DECREMENT LOOP COUNTER + LD A,D + OR E + JR NZ,FXRW2 ; IF NOT ZERO, REPEAT LOOP + JR FXR_END ; CLEAN EXIT +FXRW5: ; OUTER LOOP, REALLY ONLY HAPPENS WHEN WAITING FOR FIRST BYTE OR ABORTED + AND 0E0H ; ISOLATE RQM/DIO/NDM + CP 0C0H ; IF RQM=1, DIO=1, NDM=0 (EXECUTION ABORTED) + JR Z,FXR_ABORT ; BAIL OUT TO ERR ROUTINE + DEC C + JR NZ,FXRW2 ; IF NOT ZERO, LOOP SOME MORE + LD A,(FCD_TO) + DEC A + LD (FCD_TO),A + JR NZ,FXRW1 + JR FXR_TO ; OTHERWISE, TIMEOUT ERROR +; +; COMMON COMPLETION CODE FOR ALL EXECUTION ROUTINES +; +FXR_TO: ; TIMEOUT + HB_EI ; INTERRUPTS OK AGAIN + JP FOP_TOEXEC ; EXEC TIMEOUT +; +FXR_ABORT: ; EXECUTION ABORTED + HB_EI ; INTERRUPTS OK AGAIN + JR FOP_RES ; GET RSEULTS, NO NEED TO PULSE TC +; +FXR_END: ; EXECUTION COMPLETED NORMALLY + CALL FC_PULSETC ; PULSE TC TO END EXECUTION + HB_EI ; INTERRUPTS OK AGAIN + JR FOP_RES ; GET RSEULTS +; +; RESULTS PHASE ; FOP_RES: LD HL,FRB ; POINT TO RECEIVE BUFFER FOP_RES0: - LD B,0 ; B IS LOOP COUNTER + LD BC,$7000 ; LOOP COUNTER, $7000 * 16us = ~458ms FOP_RES1: CALL DELAY ; FDC MAY TAKE UP TO 12us TO UPDATE MSR @@ -1438,7 +1602,12 @@ FOP_RES1: JR Z,FOP_RES2 ; GOOD, GO TO RECEIVE BYTE CP 080H ; CHECK FOR RQM=1, DIO=0 (NOTHING LEFT) JR Z,FOP_EVAL ; IF NOTHING LEFT, ALL DONE, GO TO EOD/EXIT - DJNZ FOP_RES1 ; LOOP TILL COUNTER EXHAUSTED + DEC BC ; DECREMENT COUNTER (16 BIT) + LD A,B ; CHECK FOR ZERO + OR C ; "" + JR NZ,FOP_RES1 ; LOOP TILL COUNTER EXHAUSTED + ;IN A,(FDC_MSR) ; *DEBUG* + ;CALL PRTHEXBYTE ; *DEBUG* JR FOP_TOGETRES ; OTHERWISE TIMEOUT ERROR FOP_RES2: ; PROCESS NEXT PENDING BYTE @@ -1577,133 +1746,6 @@ FOP_EXIT: CALL FC_PRTRESULTS #ENDIF JP FD_RETRC -; -; EXECUTION ROUTINES -; -FXR_NOP: - RET -; -; NULL EXECUTION, NO DATA TO READ/WRITE (USED BY READID) -; -; DO NOTHING, BUT WAIT FOR FDC READY. LOOP NEEDS TO ALLOW FOR -; 2 FULL ROTATIONS OF THE DISK WHICH IS 400ms AT 300RPM -; -FXR_NULL: - LD BC,$4000 ; LOOP COUNTER, $4000 * 25us = 410ms -FXR_NULL1: - CALL DELAY ; FDC MAY TAKE UP TO 12us TO UPDATE MSR - IN A,(FDC_MSR) ; GET MSR - AND 0E0H ; ISOLATE RQM/DIO/NDM - CP 0C0H ; WE WANT RQM=1,DIO=1,NDM=0 (READY TO READ A BYTE W/ EXEC INACTIVE) - RET Z ; GOT IT, EXIT CLEAN W/O PULSING TC - DEC BC ; DECREMENT COUNTER (16 BIT) - LD A,B ; CHECK FOR ZERO - OR C ; " - JR NZ,FXR_NULL1 ; NOT ZERO YET, KEEP CHECKING - JP FXR_TO ; OTHERWISE, TIMEOUT ERROR - RET -; -; READ DATA -; -FXR_READ: - HB_DI ; TIME CRITICAL , INTERRUPTS WILL CAUSE I/O ERRS - LD HL,(FD_DSKBUF) ; POINT TO SECTOR BUFFER START - LD DE,(FCD_SECSZ) - ; TIMEOUT COUNTER IS CPU MHZ / 4 (MAKING SURE IT IS AT LEAST 1) -; LD A,(CPUMHZ + 3) / 4 - LD A,(CB_CPUMHZ) ; GET CPU SPEED IN MHZ - ADD A,3 ; ROUND UP - SRL A ; SHIFT RIGHT TWICE - SRL A ; ... TO DIVIDE BY 4 - ;INC A ; MAKE SURE RESULT IS AT LEAST 1 - LD (FCD_TO),A ; INIT TIMEOUT COUNTER -FXRR1 LD C,0 ; OUTER LOOP TIMEOUT COUNTER -FXRR2: LD B,0 ; SETUP FOR 256 ITERATIONS -FXRR3: IN A,(FDC_MSR) ; GET MSR - CP 0F0H ; WE WANT RQM=1,DIO=1,NDM=1,BUSY=1 (READY TO RECEIVE A BYTE W/ EXEC ACTIVE) - JR Z,FXRR4 ; GOT IT, DO BYTE READ - DJNZ FXRR3 ; NOT READY, LOOP IF COUNTER NOT ZERO - JR FXRR5 ; COUNTER ZERO, GO TO OUTER LOOP LOGIC - -FXRR4: IN A,(FDC_DATA) ; GET PENDING BYTE - LD (HL),A ; STORE IT IN BUFFER - INC HL ; INCREMENT THE BUFFER POINTER - DEC DE ; DECREMENT BYTE COUNT - LD A,D - OR E - JR NZ,FXRR2 ; IF NOT ZERO, REPEAT LOOP - JR FXR_END ; CLEAN EXIT - -FXRR5: ; OUTER LOOP, REALLY ONLY HAPPENS WHEN WAITING FOR FIRST BYTE OR ABORTED - CP 0C0H ; IF RQM=1, DIO=1, NDM=0 (EXECUTION ABORTED) - JR Z,FXR_ABORT ; BAIL OUT TO ERR ROUTINE, FIX: GO TO SPECIFIC ROUTINE FOR THIS??? - DEC C - JR NZ,FXRR2 ; IF NOT ZERO, LOOP SOME MORE - LD A,(FCD_TO) - DEC A - LD (FCD_TO),A - JR NZ,FXRR1 - JR FXR_TO ; OTHERWISE, TIMEOUT ERROR -; -; WRITE DATA -; -FXR_WRITE: - HB_DI ; TIME CRITICAL , INTERRUPTS WILL CAUSE I/O ERRS - LD HL,(FD_DSKBUF) ; POINT TO SECTOR BUFFER START - LD DE,(FCD_SECSZ) - ; TIMEOUT COUNTER IS CPU MHZ / 4 (MAKING SURE IT IS AT LEAST 1) -; LD A,(CPUMHZ + 3) / 4 - LD A,(CB_CPUMHZ) ; GET CPU SPEED IN MHZ - ADD A,3 ; ROUND UP - SRL A ; SHIFT RIGHT TWICE - SRL A ; ... TO DIVIDE BY 4 - ;INC A ; MAKE SURE RESULT IS AT LEAST 1 - LD (FCD_TO),A -FXRW1 LD C,0 ; OUTER LOOP TIMEOUT COUNTER -FXRW2: LD B,0 ; SETUP FOR 256 ITERATIONS -FXRW3: IN A,(FDC_MSR) ; GET MSR - CP 0B0H ; WE WANT RQM=1,DIO=0,NDM=1,BUSY=1 (READY TO SEND A BYTE W/ EXEC ACTIVE) - JR Z,FXRW4 ; GOT IT, DO BYTE WRITE - DJNZ FXRW3 ; NOT READY, LOOP IF COUNTER NOT ZERO - JR FXRW5 ; COUNTER ZERO, GO TO OUTER LOOP LOGIC -FXRW4: LD A,(HL) ; GET NEXT BYTE TO WRITE - OUT (FDC_DATA),A ; WRITE IT - INC HL ; INCREMENT THE BUFFER POINTER - DEC DE ; DECREMENT LOOP COUNTER - LD A,D - OR E - JR NZ,FXRW2 ; IF NOT ZERO, REPEAT LOOP - JR FXR_END ; CLEAN EXIT -FXRW5: ; OUTER LOOP, REALLY ONLY HAPPENS WHEN WAITING FOR FIRST BYTE OR ABORTED - CP 0C0H ; IF RQM=1, DIO=1, NDM=0 (EXECUTION ABORTED) - JR Z,FXR_ABORT ; BAIL OUT TO ERR ROUTINE - DEC C - JR NZ,FXRW2 ; IF NOT ZERO, LOOP SOME MORE - LD A,(FCD_TO) - DEC A - LD (FCD_TO),A - JR NZ,FXRW1 - JR FXR_TO ; OTHERWISE, TIMEOUT ERROR -; -FXR_TO: - LD A,FRC_TOEXEC - JR FXR_ERR - -FXR_ABORT: - LD A,FRC_ABORT - JR FXR_ERR -; -; COMMON COMPLETION CODE FOR ALL EXECUTION ROUTINES -; -FXR_ERR: - HB_EI ; INTERRUPTS OK AGAIN - LD (FST_RC),A - RET - -FXR_END: - HB_EI ; INTERRUPTS OK AGAIN - CALL FC_PULSETC - RET #IF (FDTRACE > 0) ; diff --git a/Source/HBIOS/font_hi.asm b/Source/HBIOS/font_hi.asm deleted file mode 100644 index d4750aae..00000000 --- a/Source/HBIOS/font_hi.asm +++ /dev/null @@ -1,257 +0,0 @@ -FONT_HI: - .DB $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$7E,$81,$A5,$81,$81,$BD,$99,$81,$81,$7E,$00,$00,$00,$00 - .DB $00,$00,$7C,$FE,$FE,$D6,$FE,$FE,$BA,$C6,$FE,$7C,$00,$00,$00,$00 - .DB $00,$00,$00,$6C,$EE,$FE,$FE,$FE,$FE,$7C,$38,$10,$00,$00,$00,$00 - .DB $00,$00,$00,$10,$38,$7C,$FE,$7C,$38,$10,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$10,$38,$38,$10,$6C,$EE,$6C,$10,$38,$00,$00,$00,$00 - .DB $00,$00,$10,$38,$7C,$7C,$FE,$FE,$FE,$6C,$10,$38,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$18,$3C,$3C,$3C,$18,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$E7,$C3,$C3,$C3,$E7,$FF,$FF,$FF,$FF,$FF,$FF - .DB $00,$00,$00,$00,$18,$3C,$66,$66,$66,$3C,$18,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$E7,$C3,$99,$99,$99,$C3,$E7,$FF,$FF,$FF,$FF,$FF - .DB $00,$00,$1E,$0E,$1E,$36,$78,$CC,$CC,$CC,$CC,$78,$00,$00,$00,$00 - .DB $00,$00,$3C,$66,$66,$66,$3C,$18,$7E,$18,$18,$18,$00,$00,$00,$00 - .DB $00,$00,$1E,$1A,$1E,$18,$18,$18,$18,$78,$F8,$70,$00,$00,$00,$00 - .DB $00,$00,$3E,$36,$3E,$36,$36,$76,$F6,$66,$0E,$1E,$0C,$00,$00,$00 - .DB $00,$00,$18,$DB,$7E,$3C,$66,$66,$3C,$7E,$DB,$18,$00,$00,$00,$00 - .DB $00,$00,$00,$80,$E0,$F0,$FC,$FE,$FC,$F0,$E0,$80,$00,$00,$00,$00 - .DB $00,$00,$00,$02,$0E,$3E,$7E,$FE,$7E,$3E,$0E,$02,$00,$00,$00,$00 - .DB $00,$00,$18,$3C,$7E,$18,$18,$18,$18,$7E,$3C,$18,$00,$00,$00,$00 - .DB $00,$00,$66,$66,$66,$66,$66,$66,$66,$00,$66,$66,$00,$00,$00,$00 - .DB $00,$00,$7F,$DB,$DB,$DB,$DB,$7B,$1B,$1B,$1B,$1B,$00,$00,$00,$00 - .DB $00,$00,$7C,$C6,$C6,$60,$7C,$F6,$DE,$7C,$0C,$C6,$C6,$7C,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$00,$FE,$FE,$FE,$FE,$00,$00,$00,$00 - .DB $00,$00,$18,$3C,$7E,$18,$18,$18,$7E,$3C,$18,$7E,$00,$00,$00,$00 - .DB $00,$00,$18,$3C,$7E,$18,$18,$18,$18,$18,$18,$18,$00,$00,$00,$00 - .DB $00,$00,$18,$18,$18,$18,$18,$18,$18,$7E,$3C,$18,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$0C,$0E,$FF,$0E,$0C,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$30,$70,$FE,$70,$30,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$C0,$C0,$C0,$FE,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$24,$66,$FF,$66,$24,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$10,$38,$38,$38,$7C,$7C,$FE,$FE,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$FE,$FE,$7C,$7C,$7C,$38,$38,$10,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$18,$3C,$3C,$3C,$3C,$18,$18,$00,$18,$18,$00,$00,$00,$00 - .DB $00,$36,$36,$36,$36,$14,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$6C,$6C,$6C,$FE,$6C,$6C,$FE,$6C,$6C,$6C,$00,$00,$00,$00 - .DB $00,$00,$18,$18,$7C,$C6,$C0,$78,$3C,$06,$C6,$7C,$18,$18,$00,$00 - .DB $00,$00,$00,$00,$00,$62,$66,$0C,$18,$30,$66,$C6,$00,$00,$00,$00 - .DB $00,$00,$38,$6C,$38,$30,$76,$7E,$CC,$CC,$CC,$76,$00,$00,$00,$00 - .DB $00,$0C,$0C,$0C,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$0C,$18,$30,$30,$30,$30,$30,$30,$18,$0C,$00,$00,$00,$00 - .DB $00,$00,$30,$18,$0C,$0C,$0C,$0C,$0C,$0C,$18,$30,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$6C,$38,$FE,$38,$6C,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$18,$18,$7E,$18,$18,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$00,$00,$0C,$0C,$0C,$18,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$FE,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$18,$18,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$02,$06,$0C,$18,$30,$60,$C0,$80,$00,$00,$00,$00 - .DB $00,$00,$7C,$C6,$C6,$CE,$DE,$F6,$E6,$C6,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$18,$78,$18,$18,$18,$18,$18,$18,$18,$7E,$00,$00,$00,$00 - .DB $00,$00,$7C,$C6,$C6,$06,$0C,$18,$30,$60,$C6,$FE,$00,$00,$00,$00 - .DB $00,$00,$7C,$C6,$06,$06,$3C,$06,$06,$06,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$0C,$1C,$3C,$6C,$CC,$CC,$FE,$0C,$0C,$1E,$00,$00,$00,$00 - .DB $00,$00,$FE,$C0,$C0,$C0,$FC,$06,$06,$06,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$7C,$C6,$C0,$C0,$FC,$C6,$C6,$C6,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$FE,$C6,$06,$0C,$18,$30,$30,$30,$30,$30,$00,$00,$00,$00 - .DB $00,$00,$7C,$C6,$C6,$C6,$7C,$C6,$C6,$C6,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$7C,$C6,$C6,$C6,$C6,$7E,$06,$06,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$0C,$0C,$00,$00,$0C,$0C,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$0C,$0C,$00,$00,$0C,$0C,$0C,$18,$00,$00,$00 - .DB $00,$00,$00,$0C,$18,$30,$60,$C0,$60,$30,$18,$0C,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$FE,$00,$FE,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$60,$30,$18,$0C,$06,$0C,$18,$30,$60,$00,$00,$00,$00 - .DB $00,$00,$7C,$C6,$C6,$0C,$18,$18,$18,$00,$18,$18,$00,$00,$00,$00 - .DB $00,$00,$7C,$C6,$C6,$C6,$DE,$DE,$DE,$DC,$C0,$7E,$00,$00,$00,$00 - .DB $00,$00,$38,$6C,$C6,$C6,$C6,$FE,$C6,$C6,$C6,$C6,$00,$00,$00,$00 - .DB $00,$00,$FC,$66,$66,$66,$7C,$66,$66,$66,$66,$FC,$00,$00,$00,$00 - .DB $00,$00,$3C,$66,$C2,$C0,$C0,$C0,$C0,$C2,$66,$3C,$00,$00,$00,$00 - .DB $00,$00,$F8,$6C,$66,$66,$66,$66,$66,$66,$6C,$F8,$00,$00,$00,$00 - .DB $00,$00,$FE,$66,$60,$64,$7C,$64,$60,$60,$66,$FE,$00,$00,$00,$00 - .DB $00,$00,$FE,$66,$60,$64,$7C,$64,$60,$60,$60,$F0,$00,$00,$00,$00 - .DB $00,$00,$7C,$C6,$C6,$C0,$C0,$C0,$CE,$C6,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$C6,$C6,$C6,$C6,$FE,$C6,$C6,$C6,$C6,$C6,$00,$00,$00,$00 - .DB $00,$00,$3C,$18,$18,$18,$18,$18,$18,$18,$18,$3C,$00,$00,$00,$00 - .DB $00,$00,$3C,$18,$18,$18,$18,$18,$18,$D8,$D8,$70,$00,$00,$00,$00 - .DB $00,$00,$C6,$C6,$CC,$D8,$F0,$F0,$D8,$CC,$C6,$C6,$00,$00,$00,$00 - .DB $00,$00,$F0,$60,$60,$60,$60,$60,$60,$62,$66,$FE,$00,$00,$00,$00 - .DB $00,$00,$C6,$C6,$EE,$EE,$FE,$D6,$D6,$D6,$C6,$C6,$00,$00,$00,$00 - .DB $00,$00,$C6,$C6,$E6,$E6,$F6,$DE,$CE,$CE,$C6,$C6,$00,$00,$00,$00 - .DB $00,$00,$7C,$C6,$C6,$C6,$C6,$C6,$C6,$C6,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$FC,$66,$66,$66,$66,$7C,$60,$60,$60,$F0,$00,$00,$00,$00 - .DB $00,$00,$7C,$C6,$C6,$C6,$C6,$C6,$C6,$D6,$D6,$7C,$06,$00,$00,$00 - .DB $00,$00,$FC,$66,$66,$66,$7C,$78,$6C,$66,$66,$E6,$00,$00,$00,$00 - .DB $00,$00,$7C,$C6,$C0,$C0,$70,$1C,$06,$06,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$7E,$5A,$18,$18,$18,$18,$18,$18,$18,$3C,$00,$00,$00,$00 - .DB $00,$00,$C6,$C6,$C6,$C6,$C6,$C6,$C6,$C6,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$C6,$C6,$C6,$C6,$C6,$C6,$C6,$6C,$38,$10,$00,$00,$00,$00 - .DB $00,$00,$C6,$C6,$C6,$D6,$D6,$D6,$FE,$EE,$C6,$C6,$00,$00,$00,$00 - .DB $00,$00,$C6,$C6,$C6,$6C,$38,$38,$6C,$C6,$C6,$C6,$00,$00,$00,$00 - .DB $00,$00,$66,$66,$66,$66,$66,$3C,$18,$18,$18,$3C,$00,$00,$00,$00 - .DB $00,$00,$FE,$C6,$86,$0C,$18,$30,$60,$C2,$C6,$FE,$00,$00,$00,$00 - .DB $00,$00,$7C,$60,$60,$60,$60,$60,$60,$60,$60,$7C,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$80,$C0,$60,$30,$18,$0C,$06,$02,$00,$00,$00,$00 - .DB $00,$00,$7C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$7C,$00,$00,$00,$00 - .DB $00,$10,$38,$6C,$C6,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$FF,$00,$00 - .DB $00,$18,$18,$18,$0C,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$78,$0C,$7C,$CC,$CC,$DC,$76,$00,$00,$00,$00 - .DB $00,$00,$E0,$60,$60,$7C,$66,$66,$66,$66,$66,$FC,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$7C,$C6,$C0,$C0,$C0,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$1C,$0C,$0C,$7C,$CC,$CC,$CC,$CC,$CC,$7E,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$7C,$C6,$C6,$FE,$C0,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$1C,$36,$30,$30,$FC,$30,$30,$30,$30,$78,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$76,$CE,$C6,$C6,$CE,$76,$06,$C6,$7C,$00,$00 - .DB $00,$00,$E0,$60,$60,$7C,$66,$66,$66,$66,$66,$E6,$00,$00,$00,$00 - .DB $00,$00,$18,$18,$00,$38,$18,$18,$18,$18,$18,$3C,$00,$00,$00,$00 - .DB $00,$00,$0C,$0C,$00,$1C,$0C,$0C,$0C,$0C,$0C,$CC,$CC,$78,$00,$00 - .DB $00,$00,$E0,$60,$60,$66,$66,$6C,$78,$6C,$66,$E6,$00,$00,$00,$00 - .DB $00,$00,$18,$18,$18,$18,$18,$18,$18,$18,$18,$1C,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$6C,$FE,$D6,$D6,$C6,$C6,$C6,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$DC,$66,$66,$66,$66,$66,$66,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$7C,$C6,$C6,$C6,$C6,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$DC,$66,$66,$66,$66,$7C,$60,$60,$F0,$00,$00 - .DB $00,$00,$00,$00,$00,$76,$CC,$CC,$CC,$CC,$7C,$0C,$0C,$1E,$00,$00 - .DB $00,$00,$00,$00,$00,$DC,$66,$60,$60,$60,$60,$F0,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$7C,$C6,$C0,$7C,$06,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$30,$30,$30,$FC,$30,$30,$30,$30,$36,$1C,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$CC,$CC,$CC,$CC,$CC,$CC,$76,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$C6,$C6,$C6,$C6,$6C,$38,$10,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$C6,$C6,$D6,$D6,$D6,$FE,$6C,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$C6,$C6,$6C,$38,$6C,$C6,$C6,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$C6,$C6,$C6,$C6,$CE,$76,$06,$C6,$7C,$00,$00 - .DB $00,$00,$00,$00,$00,$FE,$86,$0C,$18,$30,$62,$FE,$00,$00,$00,$00 - .DB $00,$00,$0E,$18,$18,$18,$70,$18,$18,$18,$18,$0E,$00,$00,$00,$00 - .DB $00,$00,$18,$18,$18,$18,$00,$18,$18,$18,$18,$18,$00,$00,$00,$00 - .DB $00,$00,$70,$18,$18,$18,$0E,$18,$18,$18,$18,$70,$00,$00,$00,$00 - .DB $00,$00,$76,$DC,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$10,$38,$38,$6C,$6C,$FE,$00,$00,$00,$00,$00 - .DB $00,$00,$3C,$66,$C0,$C0,$C0,$C6,$66,$3C,$18,$0C,$CC,$38,$00,$00 - .DB $00,$00,$C6,$00,$00,$C6,$C6,$C6,$C6,$C6,$CE,$76,$00,$00,$00,$00 - .DB $00,$0C,$18,$30,$00,$7C,$C6,$C6,$FE,$C0,$C6,$7C,$00,$00,$00,$00 - .DB $00,$30,$78,$CC,$00,$78,$0C,$7C,$CC,$CC,$DC,$76,$00,$00,$00,$00 - .DB $00,$00,$CC,$00,$00,$78,$0C,$7C,$CC,$CC,$DC,$76,$00,$00,$00,$00 - .DB $00,$60,$30,$18,$00,$78,$0C,$7C,$CC,$CC,$DC,$76,$00,$00,$00,$00 - .DB $00,$38,$6C,$38,$00,$78,$0C,$7C,$CC,$CC,$DC,$76,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$7C,$C6,$C0,$C0,$C6,$7C,$18,$0C,$6C,$38,$00,$00 - .DB $00,$30,$78,$CC,$00,$7C,$C6,$C6,$FE,$C0,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$CC,$00,$00,$7C,$C6,$C6,$FE,$C0,$C6,$7C,$00,$00,$00,$00 - .DB $00,$30,$18,$0C,$00,$7C,$C6,$C6,$FE,$C0,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$66,$00,$00,$38,$18,$18,$18,$18,$18,$3C,$00,$00,$00,$00 - .DB $00,$18,$3C,$66,$00,$38,$18,$18,$18,$18,$18,$3C,$00,$00,$00,$00 - .DB $00,$30,$18,$0C,$00,$38,$18,$18,$18,$18,$18,$3C,$00,$00,$00,$00 - .DB $00,$C6,$00,$38,$6C,$C6,$C6,$C6,$FE,$C6,$C6,$C6,$00,$00,$00,$00 - .DB $38,$6C,$38,$00,$38,$6C,$C6,$C6,$FE,$C6,$C6,$C6,$00,$00,$00,$00 - .DB $0C,$18,$30,$00,$FE,$60,$60,$7C,$60,$60,$60,$FE,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$66,$DB,$1B,$7F,$D8,$D8,$DF,$76,$00,$00,$00,$00 - .DB $00,$00,$7E,$D8,$D8,$D8,$D8,$FE,$D8,$D8,$D8,$DE,$00,$00,$00,$00 - .DB $00,$30,$78,$CC,$00,$7C,$C6,$C6,$C6,$C6,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$C6,$00,$00,$7C,$C6,$C6,$C6,$C6,$C6,$7C,$00,$00,$00,$00 - .DB $00,$30,$18,$0C,$00,$7C,$C6,$C6,$C6,$C6,$C6,$7C,$00,$00,$00,$00 - .DB $00,$30,$78,$CC,$00,$C6,$C6,$C6,$C6,$C6,$CE,$76,$00,$00,$00,$00 - .DB $00,$60,$30,$18,$00,$C6,$C6,$C6,$C6,$C6,$CE,$76,$00,$00,$00,$00 - .DB $00,$18,$00,$3C,$18,$18,$18,$18,$18,$18,$18,$3C,$00,$00,$00,$00 - .DB $00,$C6,$00,$7C,$C6,$C6,$C6,$C6,$C6,$C6,$C6,$7C,$00,$00,$00,$00 - .DB $00,$C6,$00,$C6,$C6,$C6,$C6,$C6,$C6,$C6,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$18,$18,$7C,$C6,$C0,$C0,$C6,$7C,$18,$18,$00,$00,$00,$00 - .DB $00,$38,$6C,$60,$60,$F0,$60,$60,$60,$66,$F6,$6C,$00,$00,$00,$00 - .DB $00,$66,$66,$66,$66,$3C,$18,$7E,$18,$3C,$18,$18,$00,$00,$00,$00 - .DB $00,$00,$3E,$63,$63,$30,$1C,$06,$63,$63,$3E,$00,$1C,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$3E,$63,$38,$0E,$63,$3E,$00,$1C,$00,$00,$00 - .DB $00,$0C,$18,$30,$00,$78,$0C,$7C,$CC,$CC,$DC,$76,$00,$00,$00,$00 - .DB $00,$0C,$18,$30,$00,$38,$18,$18,$18,$18,$18,$3C,$00,$00,$00,$00 - .DB $00,$0C,$18,$30,$00,$7C,$C6,$C6,$C6,$C6,$C6,$7C,$00,$00,$00,$00 - .DB $00,$18,$30,$60,$00,$CC,$CC,$CC,$CC,$CC,$DC,$76,$00,$00,$00,$00 - .DB $00,$00,$76,$DC,$00,$DC,$66,$66,$66,$66,$66,$66,$00,$00,$00,$00 - .DB $00,$76,$DC,$00,$C6,$C6,$E6,$F6,$DE,$CE,$C6,$C6,$00,$00,$00,$00 - .DB $00,$21,$1E,$00,$1E,$33,$60,$60,$67,$63,$33,$1D,$00,$00,$00,$00 - .DB $00,$42,$3C,$00,$3B,$66,$66,$66,$3E,$06,$66,$3C,$00,$00,$00,$00 - .DB $00,$00,$30,$30,$00,$30,$30,$30,$60,$C6,$C6,$7C,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$7E,$60,$60,$60,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$7E,$06,$06,$06,$00,$00,$00,$00,$00 - .DB $00,$60,$60,$62,$66,$6C,$18,$30,$60,$DC,$36,$0C,$18,$3E,$00,$00 - .DB $00,$60,$60,$62,$66,$6C,$18,$36,$6E,$DE,$36,$7E,$06,$06,$00,$00 - .DB $00,$00,$18,$18,$00,$18,$18,$3C,$3C,$3C,$3C,$18,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$36,$6C,$D8,$6C,$36,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$D8,$6C,$36,$6C,$D8,$00,$00,$00,$00,$00,$00 - .DB $11,$44,$11,$44,$11,$44,$11,$44,$11,$44,$11,$44,$11,$44,$11,$44 - .DB $AA,$55,$AA,$55,$AA,$55,$AA,$55,$AA,$55,$AA,$55,$AA,$55,$AA,$55 - .DB $DD,$77,$DD,$77,$DD,$77,$DD,$77,$DD,$77,$DD,$77,$DD,$77,$DD,$77 - .DB $18,$18,$18,$18,$18,$18,$18,$18,$18,$18,$18,$18,$18,$18,$18,$18 - .DB $18,$18,$18,$18,$18,$18,$18,$F8,$18,$18,$18,$18,$18,$18,$18,$18 - .DB $18,$18,$18,$18,$18,$F8,$18,$F8,$18,$18,$18,$18,$18,$18,$18,$18 - .DB $36,$36,$36,$36,$36,$36,$36,$F6,$36,$36,$36,$36,$36,$36,$36,$36 - .DB $00,$00,$00,$00,$00,$00,$00,$FE,$36,$36,$36,$36,$36,$36,$36,$36 - .DB $00,$00,$00,$00,$00,$F8,$18,$F8,$18,$18,$18,$18,$18,$18,$18,$18 - .DB $36,$36,$36,$36,$36,$F6,$06,$F6,$36,$36,$36,$36,$36,$36,$36,$36 - .DB $36,$36,$36,$36,$36,$36,$36,$36,$36,$36,$36,$36,$36,$36,$36,$36 - .DB $00,$00,$00,$00,$00,$FE,$06,$F6,$36,$36,$36,$36,$36,$36,$36,$36 - .DB $36,$36,$36,$36,$36,$F6,$06,$FE,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $36,$36,$36,$36,$36,$36,$36,$FE,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$18,$18,$F8,$18,$F8,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$F8,$18,$18,$18,$18,$18,$18,$18,$18 - .DB $18,$18,$18,$18,$18,$18,$18,$1F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$18,$18,$18,$18,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$FF,$18,$18,$18,$18,$18,$18,$18,$18 - .DB $18,$18,$18,$18,$18,$18,$18,$1F,$18,$18,$18,$18,$18,$18,$18,$18 - .DB $00,$00,$00,$00,$00,$00,$00,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$18,$18,$18,$18,$FF,$18,$18,$18,$18,$18,$18,$18,$18 - .DB $18,$18,$18,$18,$18,$1F,$18,$1F,$18,$18,$18,$18,$18,$18,$18,$18 - .DB $36,$36,$36,$36,$36,$36,$36,$37,$36,$36,$36,$36,$36,$36,$36,$36 - .DB $36,$36,$36,$36,$36,$37,$30,$3F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$3F,$30,$37,$36,$36,$36,$36,$36,$36,$36,$36 - .DB $36,$36,$36,$36,$36,$F7,$00,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$FF,$00,$F7,$36,$36,$36,$36,$36,$36,$36,$36 - .DB $36,$36,$36,$36,$36,$37,$30,$37,$36,$36,$36,$36,$36,$36,$36,$36 - .DB $00,$00,$00,$00,$00,$FF,$00,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $36,$36,$36,$36,$36,$F7,$00,$F7,$36,$36,$36,$36,$36,$36,$36,$36 - .DB $18,$18,$18,$18,$18,$FF,$00,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $36,$36,$36,$36,$36,$36,$36,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$FF,$00,$FF,$18,$18,$18,$18,$18,$18,$18,$18 - .DB $00,$00,$00,$00,$00,$00,$00,$FF,$36,$36,$36,$36,$36,$36,$36,$36 - .DB $36,$36,$36,$36,$36,$36,$36,$3F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$18,$18,$1F,$18,$1F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$1F,$18,$1F,$18,$18,$18,$18,$18,$18,$18,$18 - .DB $00,$00,$00,$00,$00,$00,$00,$3F,$36,$36,$36,$36,$36,$36,$36,$36 - .DB $36,$36,$36,$36,$36,$36,$36,$FF,$36,$36,$36,$36,$36,$36,$36,$36 - .DB $18,$18,$18,$18,$18,$FF,$18,$FF,$18,$18,$18,$18,$18,$18,$18,$18 - .DB $18,$18,$18,$18,$18,$18,$18,$F8,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$1F,$18,$18,$18,$18,$18,$18,$18,$18 - .DB $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF - .DB $00,$00,$00,$00,$00,$00,$00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF - .DB $F0,$F0,$F0,$F0,$F0,$F0,$F0,$F0,$F0,$F0,$F0,$F0,$F0,$F0,$F0,$F0 - .DB $0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F - .DB $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$76,$DC,$D8,$D8,$D8,$D8,$DC,$76,$00,$00,$00,$00 - .DB $00,$00,$78,$CC,$CC,$D8,$FC,$C6,$C6,$C6,$C6,$CC,$00,$00,$00,$00 - .DB $00,$00,$FE,$66,$62,$60,$60,$60,$60,$60,$60,$60,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$FE,$6C,$6C,$6C,$6C,$6C,$6C,$00,$00,$00,$00 - .DB $00,$00,$FE,$C6,$62,$30,$18,$18,$30,$62,$C6,$FE,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$7E,$D8,$CC,$CC,$CC,$D8,$70,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$66,$66,$66,$66,$66,$7C,$60,$C0,$80,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$76,$DC,$18,$18,$18,$18,$18,$00,$00,$00,$00 - .DB $00,$00,$FE,$38,$38,$6C,$C6,$C6,$6C,$38,$38,$FE,$00,$00,$00,$00 - .DB $00,$00,$00,$38,$6C,$C6,$C6,$FE,$C6,$C6,$6C,$38,$00,$00,$00,$00 - .DB $00,$00,$38,$6C,$C6,$C6,$C6,$C6,$6C,$6C,$6C,$EE,$00,$00,$00,$00 - .DB $00,$00,$3E,$60,$60,$3C,$66,$C6,$C6,$C6,$CC,$78,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$7E,$DB,$DB,$DB,$7E,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$02,$06,$7C,$CE,$DE,$F6,$F6,$7C,$60,$C0,$00,$00,$00,$00 - .DB $00,$00,$00,$1C,$30,$60,$60,$7C,$60,$60,$30,$1C,$00,$00,$00,$00 - .DB $00,$00,$7C,$C6,$C6,$C6,$C6,$C6,$C6,$C6,$C6,$C6,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$FE,$00,$00,$FE,$00,$00,$FE,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$18,$18,$7E,$18,$18,$00,$00,$7E,$00,$00,$00,$00 - .DB $00,$00,$30,$18,$0C,$06,$0C,$18,$30,$00,$00,$7E,$00,$00,$00,$00 - .DB $00,$00,$0C,$18,$30,$60,$30,$18,$0C,$00,$00,$7E,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$0C,$1E,$1A,$18,$18,$18,$18,$18,$18,$18,$18,$18 - .DB $18,$18,$18,$18,$18,$18,$18,$18,$18,$18,$58,$78,$30,$00,$00,$00 - .DB $00,$00,$00,$00,$18,$18,$00,$7E,$00,$18,$18,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$76,$DC,$00,$76,$DC,$00,$00,$00,$00,$00 - .DB $00,$00,$78,$CC,$CC,$78,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$18,$18,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$00,$18,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$1F,$18,$18,$18,$18,$18,$D8,$D8,$78,$38,$18,$00,$00,$00 - .DB $00,$00,$D8,$6C,$6C,$6C,$6C,$6C,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$70,$D8,$18,$30,$60,$F8,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$7E,$7E,$7E,$7E,$7E,$7E,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 diff --git a/Source/HBIOS/font_lo.asm b/Source/HBIOS/font_lo.asm deleted file mode 100644 index cf865267..00000000 --- a/Source/HBIOS/font_lo.asm +++ /dev/null @@ -1,515 +0,0 @@ -FONT_LO: - .DB $18,$18,$18,$FF,$FF,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C0,$C0,$30,$30,$C0,$C0,$30,$30,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$18,$18,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $33,$33,$CC,$CC,$33,$33,$CC,$CC,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $33,$99,$CC,$66,$33,$99,$CC,$66,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F0,$F0,$F0,$F0,$F0,$F0,$F0,$F0,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C0,$C0,$C0,$C0,$C0,$C0,$C0,$C0,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $CC,$CC,$33,$33,$CC,$CC,$33,$33,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $03,$03,$03,$03,$03,$03,$03,$03,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$CC,$CC,$33,$33,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $CC,$99,$33,$66,$CC,$99,$33,$66,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $03,$03,$03,$03,$03,$03,$03,$03,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$1F,$1F,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$0F,$0F,$0F,$0F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$1F,$1F,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$F8,$F8,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$1F,$1F,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$FF,$FF,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$F8,$F8,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C0,$C0,$C0,$C0,$C0,$C0,$C0,$C0,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E0,$E0,$E0,$E0,$E0,$E0,$E0,$E0,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $07,$07,$07,$07,$07,$07,$07,$07,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $01,$03,$06,$6C,$78,$70,$60,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$18,$00,$00,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$66,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$66,$FF,$66,$FF,$66,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$3E,$60,$3C,$06,$7C,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $62,$66,$0C,$18,$30,$66,$46,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$3C,$38,$67,$66,$3F,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $06,$0C,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $0C,$18,$30,$30,$30,$18,$0C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $30,$18,$0C,$0C,$0C,$18,$30,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$66,$3C,$FF,$3C,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$18,$18,$7E,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$18,$18,$30,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$03,$06,$0C,$18,$30,$60,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$6E,$76,$66,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$38,$18,$18,$18,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$06,$0C,$30,$60,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$06,$1C,$06,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $06,$0E,$1E,$66,$7F,$06,$06,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7E,$60,$7C,$06,$06,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$60,$7C,$66,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7E,$66,$0C,$18,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$66,$3C,$66,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$66,$3E,$06,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$18,$00,$00,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$18,$00,$00,$18,$18,$30,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $0E,$18,$30,$60,$30,$18,$0E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$7E,$00,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $70,$18,$0C,$06,$0C,$18,$70,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$06,$0C,$18,$00,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$6E,$6E,$60,$62,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$3C,$66,$7E,$66,$66,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7C,$66,$66,$7C,$66,$66,$7C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$60,$60,$60,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $78,$6C,$66,$66,$66,$6C,$78,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7E,$60,$60,$78,$60,$60,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7E,$60,$60,$78,$60,$60,$60,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$60,$6E,$66,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$66,$66,$7E,$66,$66,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$18,$18,$18,$18,$18,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $1E,$0C,$0C,$0C,$0C,$6C,$38,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$6C,$78,$70,$78,$6C,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $60,$60,$60,$60,$60,$60,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $63,$77,$7F,$6B,$63,$63,$63,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$76,$7E,$7E,$6E,$66,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$66,$66,$66,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7C,$66,$66,$7C,$60,$60,$60,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$66,$66,$66,$3C,$0E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7C,$66,$66,$7C,$78,$6C,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$60,$3C,$06,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7E,$18,$18,$18,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$66,$66,$66,$66,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$66,$66,$66,$66,$3C,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $63,$63,$63,$6B,$7F,$77,$63,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$66,$3C,$18,$3C,$66,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$66,$66,$3C,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7E,$06,$0C,$18,$30,$60,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$30,$30,$30,$30,$30,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $40,$60,$30,$18,$0C,$06,$02,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$0C,$0C,$0C,$0C,$0C,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$18,$24,$42,$42,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$30,$18,$0C,$04,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$3C,$06,$3E,$66,$3E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$60,$60,$7C,$66,$66,$7C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$3C,$60,$60,$60,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$06,$06,$3E,$66,$66,$3E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$3C,$66,$7E,$60,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$0E,$18,$3E,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$3E,$66,$66,$3E,$06,$7C,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$60,$60,$7C,$66,$66,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$18,$00,$38,$18,$18,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$06,$00,$06,$06,$06,$06,$3C,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$60,$60,$6C,$78,$6C,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$38,$18,$18,$18,$18,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$66,$7F,$7F,$6B,$63,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$7C,$66,$66,$66,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$3C,$66,$66,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$7C,$66,$66,$7C,$60,$60,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$3E,$66,$66,$3E,$06,$06,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$7C,$66,$60,$60,$60,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$3E,$60,$3C,$06,$7C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$18,$7E,$18,$18,$18,$0E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$66,$66,$66,$66,$3E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$66,$66,$66,$3C,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$63,$6B,$7F,$3E,$36,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$66,$3C,$18,$3C,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$66,$66,$66,$3E,$0C,$78,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$7E,$0C,$18,$30,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$0C,$10,$10,$20,$10,$10,$0C,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$18,$18,$18,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$18,$04,$04,$02,$04,$04,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$32,$4C,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$81,$81,$81,$81,$81,$81,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F0,$F0,$F0,$F0,$0F,$0F,$0F,$0F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$91,$91,$9F,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$C3,$F9,$C1,$99,$C1,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$9F,$9F,$83,$99,$99,$83,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$C3,$9F,$9F,$9F,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$F9,$F9,$C1,$99,$99,$C1,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$C3,$99,$81,$9F,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$F1,$E7,$C1,$E7,$E7,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$C1,$99,$99,$C1,$F9,$83,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$9F,$9F,$83,$99,$99,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$E7,$FF,$C7,$E7,$E7,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$F9,$FF,$F9,$F9,$F9,$F9,$C3,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$9F,$9F,$93,$87,$93,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$C7,$E7,$E7,$E7,$E7,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$99,$80,$80,$94,$9C,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$83,$99,$99,$99,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$C3,$99,$99,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$83,$99,$99,$83,$9F,$9F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$C1,$99,$99,$C1,$F9,$F9,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$83,$99,$9F,$9F,$9F,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$C1,$9F,$C3,$F9,$83,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$E7,$81,$E7,$E7,$E7,$F1,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$99,$99,$99,$99,$C1,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$99,$99,$99,$C3,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$9C,$94,$80,$C1,$C9,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$99,$C3,$E7,$C3,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$99,$99,$99,$C1,$F3,$87,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$81,$F3,$E7,$CF,$81,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$CF,$CF,$CF,$CF,$CF,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F3,$ED,$CF,$83,$CF,$9D,$03,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$F3,$F3,$F3,$F3,$F3,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$E7,$C3,$81,$E7,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$EF,$CF,$80,$80,$CF,$EF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$E7,$FF,$FF,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$99,$99,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$99,$00,$99,$00,$99,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$C1,$9F,$C3,$F9,$83,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $9D,$99,$F3,$E7,$CF,$99,$B9,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$C3,$C7,$98,$99,$C0,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F9,$F3,$E7,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F3,$E7,$CF,$CF,$CF,$E7,$F3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $CF,$E7,$F3,$F3,$F3,$E7,$CF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$99,$C3,$00,$C3,$99,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$E7,$E7,$81,$E7,$E7,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$E7,$E7,$CF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$81,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$E7,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FC,$F9,$F3,$E7,$CF,$9F,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$91,$89,$99,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$C7,$E7,$E7,$E7,$81,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$F9,$F3,$CF,$9F,$81,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$F9,$E3,$F9,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F9,$F1,$E1,$99,$80,$F9,$F9,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $81,$9F,$83,$F9,$F9,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$9F,$83,$99,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $81,$99,$F3,$E7,$E7,$E7,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$99,$C3,$99,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$99,$C1,$F9,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$E7,$FF,$FF,$E7,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$E7,$FF,$FF,$E7,$E7,$CF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F1,$E7,$CF,$9F,$CF,$E7,$F1,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$81,$FF,$81,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $8F,$E7,$F3,$F9,$F3,$E7,$8F,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$F9,$F3,$E7,$FF,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$00,$00,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$C3,$99,$81,$99,$99,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $83,$99,$99,$83,$99,$99,$83,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$9F,$9F,$9F,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $87,$93,$99,$99,$99,$93,$87,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $81,$9F,$9F,$87,$9F,$9F,$81,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $81,$9F,$9F,$87,$9F,$9F,$9F,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$9F,$91,$99,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$99,$99,$81,$99,$99,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$E7,$E7,$E7,$E7,$E7,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E1,$F3,$F3,$F3,$F3,$93,$C7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$93,$87,$8F,$87,$93,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $9F,$9F,$9F,$9F,$9F,$9F,$81,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $9C,$88,$80,$94,$9C,$9C,$9C,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$89,$81,$81,$91,$99,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$99,$99,$99,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $83,$99,$99,$83,$9F,$9F,$9F,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$99,$99,$99,$C3,$F1,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $83,$99,$99,$83,$87,$93,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$9F,$C3,$F9,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $81,$E7,$E7,$E7,$E7,$E7,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$99,$99,$99,$99,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$99,$99,$99,$99,$C3,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $9C,$9C,$9C,$94,$80,$88,$9C,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$99,$C3,$E7,$C3,$99,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$99,$99,$C3,$E7,$E7,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $81,$F9,$F3,$E7,$CF,$9F,$81,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$00,$00,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3F,$3F,$CF,$CF,$3F,$3F,$CF,$CF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$E7,$E7,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $CC,$CC,$33,$33,$CC,$CC,$33,$33,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $CC,$66,$33,$99,$CC,$66,$33,$99,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3F,$3F,$3F,$3F,$3F,$3F,$3F,$3F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $33,$33,$CC,$CC,$33,$33,$CC,$CC,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FC,$FC,$FC,$FC,$FC,$FC,$FC,$FC,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$33,$33,$CC,$CC,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $33,$66,$CC,$99,$33,$66,$CC,$99,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FC,$FC,$FC,$FC,$FC,$FC,$FC,$FC,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$E0,$E0,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$F0,$F0,$F0,$F0,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$E0,$E0,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$07,$07,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$E0,$E0,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$00,$00,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$00,$00,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$07,$07,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3F,$3F,$3F,$3F,$3F,$3F,$3F,$3F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $1F,$1F,$1F,$1F,$1F,$1F,$1F,$1F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F8,$F8,$F8,$F8,$F8,$F8,$F8,$F8,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FE,$FC,$F9,$93,$87,$8F,$9F,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$0F,$0F,$0F,$0F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F0,$F0,$F0,$F0,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$07,$07,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $0F,$0F,$0F,$0F,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 -#IF 0 - .DB $0F,$0F,$0F,$0F,$F0,$F0,$F0,$F0,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$6E,$6E,$60,$62,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$3C,$66,$7E,$66,$66,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7C,$66,$66,$7C,$66,$66,$7C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$60,$60,$60,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $78,$6C,$66,$66,$66,$6C,$78,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7E,$60,$60,$78,$60,$60,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7E,$60,$60,$78,$60,$60,$60,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$60,$6E,$66,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$66,$66,$7E,$66,$66,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$18,$18,$18,$18,$18,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $1E,$0C,$0C,$0C,$0C,$6C,$38,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$6C,$78,$70,$78,$6C,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $60,$60,$60,$60,$60,$60,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $63,$77,$7F,$6B,$63,$63,$63,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$76,$7E,$7E,$6E,$66,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$66,$66,$66,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7C,$66,$66,$7C,$60,$60,$60,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$66,$66,$66,$3C,$0E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7C,$66,$66,$7C,$78,$6C,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$60,$3C,$06,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7E,$18,$18,$18,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$66,$66,$66,$66,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$66,$66,$66,$66,$3C,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $63,$63,$63,$6B,$7F,$77,$63,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$66,$3C,$18,$3C,$66,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$66,$66,$3C,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7E,$06,$0C,$18,$30,$60,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$30,$30,$30,$30,$30,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $0C,$12,$30,$7C,$30,$62,$FC,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$0C,$0C,$0C,$0C,$0C,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$18,$3C,$7E,$18,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$10,$30,$7F,$7F,$30,$10,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$18,$00,$00,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$66,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $66,$66,$FF,$66,$FF,$66,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$3E,$60,$3C,$06,$7C,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $62,$66,$0C,$18,$30,$66,$46,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$3C,$38,$67,$66,$3F,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $06,$0C,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $0C,$18,$30,$30,$30,$18,$0C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $30,$18,$0C,$0C,$0C,$18,$30,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$66,$3C,$FF,$3C,$66,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$18,$18,$7E,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$18,$18,$30,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$03,$06,$0C,$18,$30,$60,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$6E,$76,$66,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$38,$18,$18,$18,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$06,$0C,$30,$60,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$06,$1C,$06,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $06,$0E,$1E,$66,$7F,$06,$06,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7E,$60,$7C,$06,$06,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$60,$7C,$66,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $7E,$66,$0C,$18,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$66,$3C,$66,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$66,$3E,$06,$66,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$18,$00,$00,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$18,$00,$00,$18,$18,$30,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $0E,$18,$30,$60,$30,$18,$0E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$7E,$00,$7E,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $70,$18,$0C,$06,$0C,$18,$70,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$66,$06,$0C,$18,$00,$18,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $08,$1C,$3E,$7F,$7F,$1C,$3E,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$18,$18,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $30,$30,$30,$30,$30,$30,$30,$30,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $0C,$0C,$0C,$0C,$0C,$0C,$0C,$0C,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$E0,$F0,$38,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$1C,$0F,$07,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$38,$F0,$E0,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C0,$C0,$C0,$C0,$C0,$C0,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C0,$E0,$70,$38,$1C,$0E,$07,$03,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $03,$07,$0E,$1C,$38,$70,$E0,$C0,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$C0,$C0,$C0,$C0,$C0,$C0,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$03,$03,$03,$03,$03,$03,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$3C,$7E,$7E,$7E,$7E,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $36,$7F,$7F,$7F,$3E,$1C,$08,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $60,$60,$60,$60,$60,$60,$60,$60,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$07,$0F,$1C,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$E7,$7E,$3C,$3C,$7E,$E7,$C3,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$3C,$7E,$66,$66,$7E,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$66,$66,$18,$18,$3C,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $06,$06,$06,$06,$06,$06,$06,$06,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $08,$1C,$3E,$7F,$3E,$1C,$08,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$FF,$FF,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C0,$C0,$30,$30,$C0,$C0,$30,$30,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$18,$18,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$03,$3E,$76,$36,$36,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$7F,$3F,$1F,$0F,$07,$03,$01,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F0,$F0,$F0,$F0,$F0,$F0,$F0,$F0,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$00,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C0,$C0,$C0,$C0,$C0,$C0,$C0,$C0,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $CC,$CC,$33,$33,$CC,$CC,$33,$33,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $03,$03,$03,$03,$03,$03,$03,$03,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$CC,$CC,$33,$33,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FE,$FC,$F8,$F0,$E0,$C0,$80,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $03,$03,$03,$03,$03,$03,$03,$03,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$1F,$1F,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$0F,$0F,$0F,$0F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$1F,$1F,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$F8,$F8,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$00,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$1F,$1F,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$FF,$FF,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$F8,$F8,$18,$18,$18,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C0,$C0,$C0,$C0,$C0,$C0,$C0,$C0,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E0,$E0,$E0,$E0,$E0,$E0,$E0,$E0,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $07,$07,$07,$07,$07,$07,$07,$07,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$00,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $03,$03,$03,$03,$03,$03,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$00,$F0,$F0,$F0,$F0,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $0F,$0F,$0F,$0F,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $18,$18,$18,$F8,$F8,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F0,$F0,$F0,$F0,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F0,$F0,$F0,$F0,$0F,$0F,$0F,$0F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$91,$91,$9F,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$C3,$99,$81,$99,$99,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $83,$99,$99,$83,$99,$99,$83,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$9F,$9F,$9F,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $87,$93,$99,$99,$99,$93,$87,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $81,$9F,$9F,$87,$9F,$9F,$81,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $81,$9F,$9F,$87,$9F,$9F,$9F,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$9F,$91,$99,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$99,$99,$81,$99,$99,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$E7,$E7,$E7,$E7,$E7,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E1,$F3,$F3,$F3,$F3,$93,$C7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$93,$87,$8F,$87,$93,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $9F,$9F,$9F,$9F,$9F,$9F,$81,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $9C,$88,$80,$94,$9C,$9C,$9C,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$89,$81,$81,$91,$99,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$99,$99,$99,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $83,$99,$99,$83,$9F,$9F,$9F,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$99,$99,$99,$C3,$F1,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $83,$99,$99,$83,$87,$93,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$9F,$C3,$F9,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $81,$E7,$E7,$E7,$E7,$E7,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$99,$99,$99,$99,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$99,$99,$99,$99,$C3,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $9C,$9C,$9C,$94,$80,$88,$9C,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$99,$C3,$E7,$C3,$99,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$99,$99,$C3,$E7,$E7,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $81,$F9,$F3,$E7,$CF,$9F,$81,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$CF,$CF,$CF,$CF,$CF,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F3,$ED,$CF,$83,$CF,$9D,$03,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$F3,$F3,$F3,$F3,$F3,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$E7,$C3,$81,$E7,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$EF,$CF,$80,$80,$CF,$EF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$E7,$FF,$FF,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$99,$99,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $99,$99,$00,$99,$00,$99,$99,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$C1,$9F,$C3,$F9,$83,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $9D,$99,$F3,$E7,$CF,$99,$B9,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$C3,$C7,$98,$99,$C0,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F9,$F3,$E7,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F3,$E7,$CF,$CF,$CF,$E7,$F3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $CF,$E7,$F3,$F3,$F3,$E7,$CF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$99,$C3,$00,$C3,$99,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$E7,$E7,$81,$E7,$E7,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$E7,$E7,$CF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$81,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$E7,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FC,$F9,$F3,$E7,$CF,$9F,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$91,$89,$99,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$C7,$E7,$E7,$E7,$81,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$F9,$F3,$CF,$9F,$81,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$F9,$E3,$F9,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F9,$F1,$E1,$99,$80,$F9,$F9,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $81,$9F,$83,$F9,$F9,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$9F,$83,$99,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $81,$99,$F3,$E7,$E7,$E7,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$99,$C3,$99,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$99,$C1,$F9,$99,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$E7,$FF,$FF,$E7,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$E7,$FF,$FF,$E7,$E7,$CF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F1,$E7,$CF,$9F,$CF,$E7,$F1,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$81,$FF,$81,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $8F,$E7,$F3,$F9,$F3,$E7,$8F,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C3,$99,$F9,$F3,$E7,$FF,$E7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$00,$00,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F7,$E3,$C1,$80,$80,$E3,$C1,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$E7,$E7,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$00,$00,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$00,$00,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$00,$00,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$00,$00,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $CF,$CF,$CF,$CF,$CF,$CF,$CF,$CF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F3,$F3,$F3,$F3,$F3,$F3,$F3,$F3,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$1F,$0F,$C7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E3,$F0,$F8,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$C7,$0F,$1F,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3F,$3F,$3F,$3F,$3F,$3F,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3F,$1F,$8F,$C7,$E3,$F1,$F8,$FC,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FC,$F8,$F1,$E3,$C7,$8F,$1F,$3F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$3F,$3F,$3F,$3F,$3F,$3F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$FC,$FC,$FC,$FC,$FC,$FC,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$C3,$81,$81,$81,$81,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$00,$00,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $C9,$80,$80,$80,$C1,$E3,$F7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $9F,$9F,$9F,$9F,$9F,$9F,$9F,$9F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$F8,$F0,$E3,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3C,$18,$81,$C3,$C3,$81,$18,$3C,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$C3,$81,$99,$99,$81,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$99,$99,$E7,$E7,$C3,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F9,$F9,$F9,$F9,$F9,$F9,$F9,$F9,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F7,$E3,$C1,$80,$C1,$E3,$F7,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$00,$00,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3F,$3F,$CF,$CF,$3F,$3F,$CF,$CF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$E7,$E7,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FC,$C1,$89,$C9,$C9,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$80,$C0,$E0,$F0,$F8,$FC,$FE,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $0F,$0F,$0F,$0F,$0F,$0F,$0F,$0F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3F,$3F,$3F,$3F,$3F,$3F,$3F,$3F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $33,$33,$CC,$CC,$33,$33,$CC,$CC,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FC,$FC,$FC,$FC,$FC,$FC,$FC,$FC,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$33,$33,$CC,$CC,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$01,$03,$07,$0F,$1F,$3F,$7F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FC,$FC,$FC,$FC,$FC,$FC,$FC,$FC,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$E0,$E0,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$F0,$F0,$F0,$F0,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$E0,$E0,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$07,$07,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$E0,$E0,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$00,$00,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$00,$00,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$07,$07,$E7,$E7,$E7,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $3F,$3F,$3F,$3F,$3F,$3F,$3F,$3F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $1F,$1F,$1F,$1F,$1F,$1F,$1F,$1F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F8,$F8,$F8,$F8,$F8,$F8,$F8,$F8,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$FF,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $00,$00,$00,$FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FC,$FC,$FC,$FC,$FC,$FC,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $FF,$FF,$FF,$FF,$0F,$0F,$0F,$0F,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $F0,$F0,$F0,$F0,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $E7,$E7,$E7,$07,$07,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 - .DB $0F,$0F,$0F,$0F,$FF,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00 -#ENDIF \ No newline at end of file diff --git a/Source/HBIOS/font_tms.asm b/Source/HBIOS/font_tms.asm deleted file mode 100644 index c5f3da74..00000000 --- a/Source/HBIOS/font_tms.asm +++ /dev/null @@ -1,260 +0,0 @@ -; tms_font.inc - automatically generated by n8vidtst -FONT_TMS: - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x00(0) - .DB 030h,030h,030h,03ch,03ch,000h,000h,000h ; 0x01(1) - .DB 030h,030h,030h,0f0h,0f0h,000h,000h,000h ; 0x02(2) - .DB 000h,000h,000h,03ch,03ch,030h,030h,030h ; 0x03(3) - .DB 000h,000h,000h,0f0h,0f0h,030h,030h,030h ; 0x04(4) - .DB 030h,030h,030h,030h,030h,030h,030h,030h ; 0x05(5) - .DB 000h,000h,000h,0ffh,0ffh,000h,000h,000h ; 0x06(6) - .DB 000h,000h,020h,070h,0f8h,070h,020h,000h ; 0x07(7) - .DB 000h,0f8h,0d8h,088h,000h,088h,0d8h,0f8h ; 0x08(8) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x09(9) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x0a(10) - .DB 000h,000h,060h,090h,060h,020h,018h,018h ; 0x0b(11) - .DB 000h,000h,020h,070h,020h,020h,050h,020h ; 0x0c(12) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x0d(13) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x0e(14) - .DB 000h,0a8h,050h,050h,0d8h,050h,050h,0a8h ; 0x0f(15) - .DB 030h,030h,030h,0fch,0fch,030h,030h,030h ; 0x10(16) - .DB 000h,010h,030h,070h,0f0h,070h,030h,010h ; 0x11(17) - .DB 000h,020h,070h,020h,020h,020h,070h,020h ; 0x12(18) - .DB 000h,000h,050h,000h,050h,050h,050h,050h ; 0x13(19) - .DB 000h,000h,028h,028h,068h,0a8h,0a8h,078h ; 0x14(20) - .DB 000h,000h,000h,0fch,0fch,030h,030h,030h ; 0x15(21) - .DB 030h,030h,030h,0fch,0fch,000h,000h,000h ; 0x16(22) - .DB 030h,030h,030h,0f0h,0f0h,030h,030h,030h ; 0x17(23) - .DB 020h,020h,020h,020h,020h,020h,070h,020h ; 0x18(24) - .DB 030h,030h,030h,03ch,03ch,030h,030h,030h ; 0x19(25) - .DB 000h,000h,010h,018h,0fch,018h,010h,000h ; 0x1a(26) - .DB 000h,000h,020h,060h,0fch,060h,020h,000h ; 0x1b(27) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x1c(28) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x1d(29) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x1e(30) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x1f(31) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x20(32) - .DB 000h,000h,030h,000h,030h,078h,078h,030h ; 0x21(33) - .DB 000h,000h,000h,000h,000h,050h,0d8h,0d8h ; 0x22(34) - .DB 048h,048h,0fch,048h,048h,0fch,048h,048h ; 0x23(35) - .DB 000h,020h,0f8h,024h,078h,0a0h,078h,020h ; 0x24(36) - .DB 000h,000h,098h,058h,020h,0d0h,0c8h,000h ; 0x25(37) - .DB 000h,078h,090h,060h,050h,048h,048h,030h ; 0x26(38) - .DB 000h,000h,000h,000h,000h,040h,030h,030h ; 0x27(39) - .DB 000h,010h,020h,020h,020h,020h,020h,010h ; 0x28(40) - .DB 000h,020h,010h,010h,010h,010h,010h,020h ; 0x29(41) - .DB 000h,020h,0a8h,070h,0f8h,070h,0a8h,020h ; 0x2a(42) - .DB 000h,000h,020h,020h,0f8h,020h,020h,000h ; 0x2b(43) - .DB 040h,030h,030h,000h,000h,000h,000h,000h ; 0x2c(44) - .DB 000h,000h,000h,000h,070h,000h,000h,000h ; 0x2d(45) - .DB 000h,030h,030h,000h,000h,000h,000h,000h ; 0x2e(46) - .DB 000h,000h,080h,040h,020h,010h,008h,004h ; 0x2f(47) - .DB 000h,070h,0c8h,0a8h,0a8h,0a8h,098h,070h ; 0x30(48) - .DB 000h,0f8h,020h,020h,020h,020h,060h,020h ; 0x31(49) - .DB 000h,0f8h,080h,060h,010h,008h,088h,070h ; 0x32(50) - .DB 000h,070h,088h,008h,070h,008h,088h,070h ; 0x33(51) - .DB 000h,010h,010h,010h,0f8h,090h,050h,030h ; 0x34(52) - .DB 000h,070h,088h,008h,0f0h,080h,080h,0f8h ; 0x35(53) - .DB 000h,070h,088h,088h,0f0h,080h,040h,038h ; 0x36(54) - .DB 000h,020h,020h,020h,020h,010h,008h,0f8h ; 0x37(55) - .DB 000h,070h,088h,088h,070h,088h,088h,070h ; 0x38(56) - .DB 000h,070h,088h,008h,078h,088h,088h,070h ; 0x39(57) - .DB 000h,000h,030h,030h,000h,030h,030h,000h ; 0x3a(58) - .DB 000h,060h,030h,030h,000h,030h,030h,000h ; 0x3b(59) - .DB 000h,010h,020h,040h,080h,040h,020h,010h ; 0x3c(60) - .DB 000h,000h,000h,0fch,000h,0fch,000h,000h ; 0x3d(61) - .DB 000h,040h,020h,010h,008h,010h,020h,040h ; 0x3e(62) - .DB 000h,020h,000h,020h,030h,008h,088h,070h ; 0x3f(63) - .DB 000h,070h,080h,080h,0b0h,0b8h,088h,070h ; 0x40(64) - .DB 000h,088h,088h,088h,0f8h,088h,050h,020h ; 0x41(65) - .DB 000h,0f0h,048h,048h,070h,048h,048h,0f0h ; 0x42(66) - .DB 000h,070h,088h,080h,080h,080h,088h,070h ; 0x43(67) - .DB 000h,0f0h,048h,048h,048h,048h,048h,0f0h ; 0x44(68) - .DB 000h,0f8h,088h,080h,0e0h,080h,088h,0f8h ; 0x45(69) - .DB 000h,080h,080h,080h,0f0h,080h,088h,0f8h ; 0x46(70) - .DB 000h,070h,088h,088h,0b8h,080h,088h,070h ; 0x47(71) - .DB 000h,088h,088h,088h,0f8h,088h,088h,088h ; 0x48(72) - .DB 000h,0f8h,020h,020h,020h,020h,020h,0f8h ; 0x49(73) - .DB 000h,070h,088h,008h,008h,008h,008h,01ch ; 0x4a(74) - .DB 000h,088h,090h,0a0h,0c0h,0a0h,090h,088h ; 0x4b(75) - .DB 000h,0f8h,088h,080h,080h,080h,080h,080h ; 0x4c(76) - .DB 000h,088h,088h,088h,088h,0a8h,0d8h,088h ; 0x4d(77) - .DB 000h,088h,098h,0a8h,0a8h,0a8h,0c8h,088h ; 0x4e(78) - .DB 000h,070h,088h,088h,088h,088h,088h,070h ; 0x4f(79) - .DB 000h,080h,080h,080h,0f0h,088h,088h,0f0h ; 0x50(80) - .DB 004h,078h,098h,0a8h,088h,088h,088h,070h ; 0x51(81) - .DB 000h,088h,090h,0a0h,0f0h,088h,088h,0f0h ; 0x52(82) - .DB 000h,070h,088h,010h,020h,040h,088h,070h ; 0x53(83) - .DB 000h,020h,020h,020h,020h,020h,020h,0f8h ; 0x54(84) - .DB 000h,070h,088h,088h,088h,088h,088h,088h ; 0x55(85) - .DB 000h,020h,050h,050h,050h,088h,088h,088h ; 0x56(86) - .DB 000h,050h,0a8h,0a8h,0a8h,088h,088h,088h ; 0x57(87) - .DB 000h,088h,088h,050h,020h,050h,088h,088h ; 0x58(88) - .DB 000h,020h,020h,020h,020h,050h,088h,088h ; 0x59(89) - .DB 000h,0f8h,088h,040h,020h,010h,088h,0f8h ; 0x5a(90) - .DB 000h,078h,040h,040h,040h,040h,040h,078h ; 0x5b(91) - .DB 000h,000h,008h,010h,020h,040h,080h,000h ; 0x5c(92) - .DB 000h,078h,008h,008h,008h,008h,008h,078h ; 0x5d(93) - .DB 000h,000h,000h,000h,000h,088h,050h,020h ; 0x5e(94) - .DB 000h,0fch,000h,000h,000h,000h,000h,000h ; 0x5f(95) - .DB 000h,000h,000h,000h,000h,010h,060h,060h ; 0x60(96) - .DB 000h,070h,088h,078h,008h,0f0h,000h,000h ; 0x61(97) - .DB 000h,0f0h,088h,088h,0f0h,080h,080h,080h ; 0x62(98) - .DB 000h,070h,080h,080h,070h,000h,000h,000h ; 0x63(99) - .DB 000h,078h,088h,088h,078h,008h,008h,008h ; 0x64(100) - .DB 000h,070h,080h,0f8h,088h,070h,000h,000h ; 0x65(101) - .DB 000h,040h,040h,040h,0e0h,048h,030h,000h ; 0x66(102) - .DB 070h,008h,038h,048h,048h,038h,000h,000h ; 0x67(103) - .DB 000h,088h,088h,0c8h,0b0h,080h,080h,000h ; 0x68(104) - .DB 000h,070h,020h,060h,000h,020h,000h,000h ; 0x69(105) - .DB 030h,048h,008h,008h,018h,000h,008h,000h ; 0x6a(106) - .DB 000h,090h,0a0h,0c0h,0a0h,090h,080h,080h ; 0x6b(107) - .DB 000h,020h,020h,020h,020h,020h,020h,020h ; 0x6c(108) - .DB 000h,088h,0a8h,0a8h,050h,000h,000h,000h ; 0x6d(109) - .DB 000h,088h,088h,0c8h,0b0h,000h,000h,000h ; 0x6e(110) - .DB 000h,070h,088h,088h,070h,000h,000h,000h ; 0x6f(111) - .DB 040h,040h,070h,048h,048h,070h,000h,000h ; 0x70(112) - .DB 008h,008h,038h,048h,048h,038h,060h,000h ; 0x71(113) - .DB 000h,080h,080h,0c8h,0b0h,000h,000h,000h ; 0x72(114) - .DB 000h,060h,010h,060h,080h,060h,000h,000h ; 0x73(115) - .DB 000h,020h,040h,040h,0e0h,040h,000h,000h ; 0x74(116) - .DB 000h,068h,090h,090h,090h,000h,000h,000h ; 0x75(117) - .DB 000h,020h,050h,088h,088h,000h,000h,000h ; 0x76(118) - .DB 000h,050h,0a8h,0a8h,088h,000h,000h,000h ; 0x77(119) - .DB 000h,048h,030h,030h,048h,000h,000h,000h ; 0x78(120) - .DB 000h,080h,040h,020h,050h,088h,000h,000h ; 0x79(121) - .DB 000h,0f8h,040h,020h,010h,0f8h,000h,000h ; 0x7a(122) - .DB 000h,010h,020h,020h,040h,020h,020h,010h ; 0x7b(123) - .DB 000h,020h,020h,020h,000h,020h,020h,020h ; 0x7c(124) - .DB 000h,040h,020h,020h,010h,020h,020h,040h ; 0x7d(125) - .DB 000h,000h,000h,000h,000h,000h,090h,06ch ; 0x7e(126) - .DB 000h,070h,050h,050h,050h,050h,050h,070h ; 0x7f(127) - .DB 0a8h,0a8h,0a8h,0ach,0a0h,0bch,080h,0fch ; 0x80(128) - .DB 000h,000h,000h,0fch,000h,0fch,000h,0fch ; 0x81(129) - .DB 054h,054h,054h,0d4h,014h,0f4h,004h,0fch ; 0x82(130) - .DB 054h,054h,054h,054h,054h,054h,054h,054h ; 0x83(131) - .DB 0fch,004h,0f4h,014h,0d4h,054h,054h,054h ; 0x84(132) - .DB 0ffh,000h,0ffh,000h,0ffh,000h,000h,000h ; 0x85(133) - .DB 0fch,080h,0bch,0a0h,0ach,0a8h,0a8h,0a8h ; 0x86(134) - .DB 0a8h,0a8h,0a8h,0a8h,0a8h,0a8h,0a8h,0a8h ; 0x87(135) - .DB 0a8h,0a8h,0a8h,0ach,0a0h,0ach,0a8h,0a8h ; 0x88(136) - .DB 054h,054h,054h,0d4h,014h,0d4h,054h,054h ; 0x89(137) - .DB 000h,000h,000h,0fch,000h,0fch,000h,000h ; 0x8a(138) - .DB 080h,080h,080h,080h,080h,080h,080h,0fch ; 0x8b(139) - .DB 000h,000h,000h,000h,000h,000h,000h,0fch ; 0x8c(140) - .DB 004h,004h,004h,004h,004h,004h,004h,0fch ; 0x8d(141) - .DB 004h,004h,004h,004h,004h,004h,004h,004h ; 0x8e(142) - .DB 0fch,004h,004h,004h,004h,004h,004h,004h ; 0x8f(143) - .DB 0fch,000h,000h,000h,000h,000h,000h,000h ; 0x90(144) - .DB 0fch,080h,080h,080h,080h,080h,080h,080h ; 0x91(145) - .DB 080h,080h,080h,080h,080h,080h,080h,080h ; 0x92(146) - .DB 080h,080h,080h,080h,0fch,080h,080h,080h ; 0x93(147) - .DB 004h,004h,004h,004h,0fch,004h,004h,004h ; 0x94(148) - .DB 000h,000h,000h,000h,0fch,000h,000h,000h ; 0x95(149) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x96(150) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x97(151) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x98(152) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x99(153) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x9a(154) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x9b(155) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x9c(156) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x9d(157) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x9e(158) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0x9f(159) - .DB 000h,070h,088h,078h,008h,0f0h,040h,020h ; 0xa0(160) - .DB 000h,0f8h,020h,020h,060h,000h,020h,010h ; 0xa1(161) - .DB 000h,070h,088h,088h,070h,000h,020h,010h ; 0xa2(162) - .DB 000h,074h,088h,088h,088h,000h,020h,010h ; 0xa3(163) - .DB 000h,088h,088h,0c8h,0b0h,000h,020h,010h ; 0xa4(164) - .DB 000h,088h,098h,0a8h,0c8h,000h,020h,010h ; 0xa5(165) - .DB 000h,000h,0fch,000h,07ch,088h,088h,070h ; 0xa6(166) - .DB 000h,000h,0fch,000h,070h,088h,088h,070h ; 0xa7(167) - .DB 000h,070h,088h,040h,020h,020h,000h,020h ; 0xa8(168) - .DB 080h,080h,080h,0fch,000h,000h,000h,000h ; 0xa9(169) - .DB 004h,004h,004h,0fch,000h,000h,000h,000h ; 0xaa(170) - .DB 000h,000h,01ch,088h,054h,0a8h,090h,088h ; 0xab(171) - .DB 000h,000h,008h,0b8h,058h,0a8h,090h,088h ; 0xac(172) - .DB 000h,000h,030h,078h,078h,030h,000h,030h ; 0xad(173) - .DB 000h,014h,028h,050h,0a0h,050h,028h,014h ; 0xae(174) - .DB 000h,0a0h,050h,028h,014h,028h,050h,0a0h ; 0xaf(175) - .DB 0aah,054h,0aah,054h,0aah,054h,0aah,054h ; 0xb0(176) - .DB 054h,0aah,054h,0aah,054h,0aah,054h,0aah ; 0xb1(177) - .DB 06ch,0b6h,0dah,06ch,0b6h,0dah,06ch,0b6h ; 0xb2(178) - .DB 018h,018h,018h,018h,018h,018h,018h,018h ; 0xb3(179) - .DB 018h,018h,018h,0f8h,018h,018h,018h,018h ; 0xb4(180) - .DB 018h,018h,0f8h,018h,0f8h,018h,018h,018h ; 0xb5(181) - .DB 06ch,06ch,06ch,0ech,06ch,06ch,06ch,06ch ; 0xb6(182) - .DB 06ch,06ch,06ch,0fch,000h,000h,000h,000h ; 0xb7(183) - .DB 06ch,06ch,0ech,06ch,0fch,000h,000h,000h ; 0xb8(184) - .DB 06ch,06ch,06ch,0ech,00ch,0ech,06ch,06ch ; 0xb9(185) - .DB 06ch,06ch,06ch,06ch,06ch,06ch,06ch,06ch ; 0xba(186) - .DB 06ch,06ch,06ch,0eeh,00eh,0fch,000h,000h ; 0xbb(187) - .DB 000h,000h,000h,0fch,00ch,0ech,06ch,06ch ; 0xbc(188) - .DB 000h,000h,000h,000h,000h,0fch,064h,064h ; 0xbd(189) - .DB 000h,000h,0f0h,030h,030h,0f0h,030h,030h ; 0xbe(190) - .DB 030h,030h,030h,0f0h,000h,000h,000h,000h ; 0xbf(191) - .DB 000h,000h,000h,03ch,030h,030h,030h,030h ; 0xc0(192) - .DB 000h,000h,000h,0fch,030h,030h,030h,030h ; 0xc1(193) - .DB 030h,030h,030h,0fch,000h,000h,000h,000h ; 0xc2(194) - .DB 030h,030h,030h,03ch,030h,030h,030h,030h ; 0xc3(195) - .DB 000h,000h,000h,0fch,000h,000h,000h,000h ; 0xc4(196) - .DB 030h,030h,030h,0fch,030h,030h,030h,030h ; 0xc5(197) - .DB 030h,030h,030h,03ch,030h,03ch,030h,030h ; 0xc6(198) - .DB 0d8h,0d8h,0d8h,0d8h,0d8h,0dch,0d8h,0d8h ; 0xc7(199) - .DB 000h,000h,0fch,0c0h,0dch,0d8h,0d8h,0d8h ; 0xc8(200) - .DB 0d8h,0d8h,0dch,0c0h,0fch,000h,000h,000h ; 0xc9(201) - .DB 000h,0fch,000h,0c0h,0dch,0d8h,0d8h,0d8h ; 0xca(202) - .DB 000h,0fch,000h,000h,000h,000h,000h,000h ; 0xcb(203) - .DB 0d8h,0d8h,0dch,0c0h,0dch,0d8h,0d8h,0d8h ; 0xcc(204) - .DB 000h,000h,0fch,000h,0fch,000h,000h,000h ; 0xcd(205) - .DB 0d8h,0d8h,0dch,000h,0dch,0d8h,0d8h,0d8h ; 0xce(206) - .DB 000h,0fch,000h,000h,0fch,030h,030h,030h ; 0xcf(207) - .DB 000h,000h,000h,000h,0fch,0d8h,0d8h,0d8h ; 0xd0(208) - .DB 030h,030h,0fch,000h,000h,0fch,000h,000h ; 0xd1(209) - .DB 0d8h,0d8h,0fch,000h,000h,000h,000h,000h ; 0xd2(210) - .DB 000h,000h,000h,000h,0fch,0d8h,0d8h,0d8h ; 0xd3(211) - .DB 000h,03ch,030h,030h,03ch,030h,030h,030h ; 0xd4(212) - .DB 030h,030h,03eh,030h,03eh,000h,000h,000h ; 0xd5(213) - .DB 0d8h,0d8h,0fch,000h,000h,000h,000h,000h ; 0xd6(214) - .DB 0d8h,0d8h,0d8h,0dch,0d8h,0d8h,0d8h,0d8h ; 0xd7(215) - .DB 030h,030h,0fch,000h,000h,0fch,030h,030h ; 0xd8(216) - .DB 000h,000h,000h,000h,0f0h,030h,030h,030h ; 0xd9(217) - .DB 030h,030h,030h,030h,03ch,000h,000h,000h ; 0xda(218) - .DB 0fch,0fch,0fch,0fch,0fch,0fch,0fch,0fch ; 0xdb(219) - .DB 0c0h,0c0h,0fch,0fch,0fch,000h,000h,000h ; 0xdc(220) - .DB 0c0h,0c0h,0c0h,0c0h,0c0h,0c0h,0c0h,0c0h ; 0xdd(221) - .DB 00ch,00ch,00ch,00ch,00ch,00ch,00ch,00ch ; 0xde(222) - .DB 000h,0fch,0fch,0fch,00ch,00ch,00ch,00ch ; 0xdf(223) - .DB 000h,068h,090h,090h,068h,000h,000h,000h ; 0xe0(224) - .DB 040h,0f0h,088h,088h,0b0h,088h,088h,070h ; 0xe1(225) - .DB 000h,080h,080h,080h,080h,080h,090h,0f0h ; 0xe2(226) - .DB 000h,000h,048h,050h,050h,050h,050h,0f8h ; 0xe3(227) - .DB 000h,0f8h,088h,040h,020h,040h,088h,0f8h ; 0xe4(228) - .DB 000h,070h,088h,088h,07ch,000h,000h,000h ; 0xe5(229) - .DB 080h,070h,088h,088h,088h,000h,000h,000h ; 0xe6(230) - .DB 000h,01ch,010h,010h,098h,074h,000h,000h ; 0xe7(231) - .DB 000h,0fch,010h,038h,044h,038h,010h,0fch ; 0xe8(232) - .DB 000h,038h,0cch,0cch,0fch,0cch,0cch,030h ; 0xe9(233) - .DB 000h,084h,048h,048h,084h,084h,084h,078h ; 0xea(234) - .DB 000h,038h,044h,044h,044h,038h,040h,03ch ; 0xeb(235) - .DB 000h,000h,06ch,092h,092h,06ch,000h,000h ; 0xec(236) - .DB 000h,070h,068h,058h,038h,000h,000h,000h ; 0xed(237) - .DB 000h,038h,040h,080h,0f8h,080h,040h,038h ; 0xee(238) - .DB 000h,0cch,0cch,0cch,0cch,030h,000h,000h ; 0xef(239) - .DB 000h,0fch,000h,000h,0fch,000h,000h,0fch ; 0xf0(240) - .DB 000h,0feh,000h,010h,010h,07ch,010h,010h ; 0xf1(241) - .DB 000h,0feh,000h,020h,010h,008h,010h,020h ; 0xf2(242) - .DB 000h,0feh,000h,010h,020h,040h,020h,010h ; 0xf3(243) - .DB 030h,030h,030h,030h,030h,030h,034h,018h ; 0xf4(244) - .DB 070h,0b0h,030h,030h,030h,030h,030h,030h ; 0xf5(245) - .DB 000h,030h,030h,000h,0fch,000h,030h,030h ; 0xf6(246) - .DB 000h,000h,098h,064h,000h,098h,064h,000h ; 0xf7(247) - .DB 000h,000h,000h,000h,038h,044h,044h,038h ; 0xf8(248) - .DB 000h,000h,000h,000h,000h,038h,038h,000h ; 0xf9(249) - .DB 000h,000h,000h,000h,000h,038h,000h,000h ; 0xfa(250) - .DB 000h,008h,018h,028h,048h,008h,008h,00eh ; 0xfb(251) - .DB 000h,048h,048h,048h,0b0h,000h,000h,000h ; 0xfc(252) - .DB 000h,0f8h,080h,040h,03ch,0f0h,000h,000h ; 0xfd(253) - .DB 0fch,0fch,0fch,0fch,0fch,0fch,0fch,0fch ; 0xfe(254) - .DB 000h,000h,000h,000h,000h,000h,000h,000h ; 0xff(255) -; eof - tms_font.inc - diff --git a/Source/HBIOS/game.asm b/Source/HBIOS/game.asm new file mode 100644 index 00000000..8bbce38b --- /dev/null +++ b/Source/HBIOS/game.asm @@ -0,0 +1,1234 @@ +; 2048 +; +; Join the numbers and get to the 2048 tile. +; +; Commands: +; +; Use the arrow keys to move the tiles. +; When two tiles with the same number touch, they merge into one. +; +; w, s, a, d - Alternate keys (up, down, left, right) +; CTRL-E, CTRL-X, CTRL-S, CTRL-D - Wordstar-compatible control keys +; +; Compile: +; +; TASM -80 -b 2048.ASM 2048.COM +; +; Credits: +; +; Based on 2048 created by Gabriele Cirulli. +; Based on the console version for GNU/Linux by Maurits van der Schee +; Ported to Z80 and CP/M by Marco Maccaferri +; Ported to ROMWBW ROM by Phil Summers difficultylevelhigh@gmail from https://github.com/maccasoft/z80-apps/2048.asm + +CTRL_A .EQU 1 +CTRL_B .EQU 2 +CTRL_C .EQU 3 +CTRL_D .EQU 4 +CTRL_E .EQU 5 +CTRL_F .EQU 6 +CTRL_G .EQU 7 +CTRL_H .EQU 8 +CTRL_I .EQU 9 +CTRL_J .EQU 10 +CTRL_K .EQU 11 +CTRL_L .EQU 12 +CTRL_M .EQU 13 +CTRL_N .EQU 14 +CTRL_O .EQU 15 +CTRL_P .EQU 16 +CTRL_Q .EQU 17 +CTRL_R .EQU 18 +CTRL_S .EQU 19 +CTRL_T .EQU 20 +CTRL_U .EQU 21 +CTRL_V .EQU 22 +CTRL_W .EQU 23 +CTRL_X .EQU 24 +CTRL_Y .EQU 25 +CTRL_Z .EQU 26 + +BEL .EQU 07H +FF .EQU 0CH +CR .EQU 0DH +LF .EQU 0AH +EOS .EQU 24H + +;BDOS .EQU 0005H + +BOARD_X .EQU 25H +BOARD_Y .EQU 06H + +#include "std.asm" + + .ORG GAM_LOC + +S2048: LD A,R + LD (RAND16+1),A + LD A,R + LD (RAND16+2),A + + LD B,VAREND-BOARD + LD HL,BOARD + XOR A +CLRBOARD: LD (HL),A + INC HL + DJNZ CLRBOARD + + LD DE,INIT +; LD C,09H +; CALL BDOS + CALL PRTSTRDE + + CALL ADDRANDOM + CALL ADDRANDOM + + CALL DRAWBORDER + CALL DRAWBOARD + +LOOP2 LD A,BOARD_X-3 + LD (XPOS),A + LD A,BOARD_Y + ADD A,13H + DAA + LD (YPOS),A + CALL GOTOXY + LD DE,MSG1 +; LD C,09H +; CALL BDOS + CALL PRTSTRDE + +LOOP: +; LD C,06H +; LD E,0FFH +; CALL BDOS +; CP 0 +; JP Z,LOOP + CALL CIN + + CP 'a' + JP C,K3 + CP '{' + JP NC,K3 + AND 5FH + +K3 CP CTRL_C + JP Z,EXIT + + CP 1BH + JP Z,K1 + + CP CTRL_E + JP Z,TOP + CP CTRL_X + JP Z,BOTTOM + CP CTRL_D + JP Z,RIGHT + CP CTRL_S + JP Z,LEFT + + CP 'W' + JP Z,TOP + CP 'S' + JP Z,BOTTOM + CP 'D' + JP Z,RIGHT + CP 'A' + JP Z,LEFT + + CP '8' + JP Z,TOP + CP '2' + JP Z,BOTTOM + CP '6' + JP Z,RIGHT + CP '4' + JP Z,LEFT + + CP 'Q' + JP Z,QUIT + +; LD C,02H +; LD E,BEL +; CALL BDOS +; CALL COUTE + + JP LOOP + +K1: +; LD C,06H +; LD E,0FFH +; CALL BDOS +; CP 0 +; JP Z,K1 + CALL CIN + + CP '[' + JP NZ,LOOP + +K4: +; LD C,06H +; LD E,0FFH +; CALL BDOS +; CP 0 +; JP Z,K4 + CALL CIN + +K2 CP 'A' + JP Z,TOP + CP 'B' + JP Z,BOTTOM + CP 'C' + JP Z,RIGHT + CP 'D' + JP Z,LEFT + +; LD C,02H +; LD E,BEL +; CALL BDOS +; CALL COUTE + + JP LOOP + +LOOP1 LD A,(MOVED) + CP 0 + JP Z,LOOP + + CALL DRAWBOARD + + CALL ADDRANDOM + CALL DRAWBOARD + + CALL ISOVER + CP 1 + JP Z,GAMEOVER + + JP LOOP + +TOP: + CALL ROTATE + CALL ROTATE + CALL ROTATE + CALL MOVEMERGE + CALL ROTATE + JP LOOP1 + +BOTTOM: + CALL ROTATE + CALL MOVEMERGE + CALL ROTATE + CALL ROTATE + CALL ROTATE + JP LOOP1 + +RIGHT: + CALL ROTATE + CALL ROTATE + CALL MOVEMERGE + CALL ROTATE + CALL ROTATE + JP LOOP1 + +LEFT: + CALL MOVEMERGE + JP LOOP1 + +QUIT: + LD A,BOARD_X-3 + LD (XPOS),A + LD A,BOARD_Y + ADD A,13H + DAA + LD (YPOS),A + CALL GOTOXY + LD DE,MSG2 +; LD C,09H +; CALL BDOS + CALL PRTSTRDE + +Q1: +; LD C,06H +; LD E,0FFH +; CALL BDOS +; CP 0 +; JP Z,Q1 + CALL CIN + + CP 'y' + JP Z,EXIT + CP 'Y' + JP Z,EXIT + CP 'n' + JP Z,LOOP2 + CP 'N' + JP Z,LOOP2 + CP 'R' + JP S2048 + CP 'r' + JP S2048 + +; LD C,02H +; LD E,BEL +; CALL BDOS +; CALL COUTE + JP Q1 + +GAMEOVER: + LD A,BOARD_X-3 + LD (XPOS),A + LD A,BOARD_Y+13H + DAA + LD (YPOS),A + CALL GOTOXY + LD DE,MSG3 +; LD C,09H +; CALL BDOS + CALL PRTSTRDE +GAMEMORE: CALL CIN + CP 'y' + JP Z,S2048 + CP 'Y' + JP Z,S2048 + CP 'n' + JP Z,EXIT + CP 'N' + JP Z,EXIT + JR GAMEMORE +EXIT: + LD DE,TERM +; LD C,09H +; CALL BDOS + CALL PRTSTRDE + + LD A,00H + LD HL,0000H + JP 0FFF9H +; RET + +ISOVER: + LD A,1 + LD (RESULT),A + + LD B,16 + LD HL,BOARD +G1: LD A,(HL) + CP 13 + JP Z,GR + INC HL + DJNZ G1 + + CALL CANMERGE + CP 0 + JP Z,G2 + XOR A + LD (RESULT),A + +G2: CALL ROTATE + CALL CANMERGE + CP 0 + JP Z,G3 + XOR A + LD (RESULT),A + +G3: CALL ROTATE + CALL CANMERGE + CP 0 + JP Z,G4 + XOR A + LD (RESULT),A + +G4: CALL ROTATE + CALL CANMERGE + CP 0 + JP Z,G5 + XOR A + LD (RESULT),A + +G5: CALL ROTATE + +GR: LD A,(RESULT) + RET + +CANMERGE: + LD HL,BOARD + LD (BOARDPTR),HL + CALL TESTMERGE + CP 1 + RET Z + LD HL,BOARD+4 + LD (BOARDPTR),HL + CALL TESTMERGE + CP 1 + RET Z + LD HL,BOARD+8 + LD (BOARDPTR),HL + CALL TESTMERGE + CP 1 + RET Z + LD HL,BOARD+12 + LD (BOARDPTR),HL + CALL TESTMERGE + RET + +TESTMERGE: + LD DE,(BOARDPTR) + LD HL,(BOARDPTR) + INC HL + + LD B,3 +M8 LD A,(DE) + CP 0 + JP Z,M7 + LD C,(HL) + CP C + JP Z,M7 + INC HL + INC DE + DJNZ M8 + XOR A + RET +M7 LD A,1 + RET + +MOVEMERGE: + XOR A + LD (MOVED),A + + LD HL,BOARD + LD (BOARDPTR),HL + CALL MERGE + LD HL,BOARD+4 + LD (BOARDPTR),HL + CALL MERGE + LD HL,BOARD+8 + LD (BOARDPTR),HL + CALL MERGE + LD HL,BOARD+12 + LD (BOARDPTR),HL + CALL MERGE + + RET + +ADDRANDOM: + LD DE,0 + LD B,16 + LD HL,BOARD +N2 LD A,(HL) + CP 0 + JP NZ,N1 + INC E +N1 INC HL + DJNZ N2 + + LD A,E + CP 0 + RET Z + + PUSH DE + CALL RAND16 + POP DE + CALL DIV16 + + LD B,L +N4: LD C,16 + LD HL,BOARD +N5: LD A,(HL) + CP 0 + JP NZ,N3 + DEC B + JP Z,N6 +N3: INC HL + DEC C + JP NZ,N5 + JP N4 + +N6 PUSH HL + + CALL RAND16 + LD DE,10 + CALL DIV16 + LD DE,9 + CALL DIV16 + LD A,E + INC A + + POP HL + LD (HL),A + + RET + +MERGE: + CALL COLLAPSE + + ; merge tiles with same value + + LD DE,(BOARDPTR) + LD HL,(BOARDPTR) + INC HL + + LD B,3 +M6: LD A,(DE) + CP 0 + RET Z + LD C,(HL) + CP C + JP NZ,M5 + + INC A + LD (DE),A + CALL ADDPOINTS + XOR A + LD (HL),A + + LD A,(MOVED) + INC A + LD (MOVED),A + + PUSH DE + PUSH HL + CALL COLLAPSE + POP HL + POP DE + +M5: INC HL + INC DE + DJNZ M6 + + RET + +COLLAPSE: + ; collapse all tiles + + LD HL,(BOARDPTR) + + LD B,4 ; find first zero +M1: LD A,(HL) + CP 0 + JP Z,M2 + INC HL + DJNZ M1 + RET + +M2: LD E,L ; DE=fist zero position + LD D,H + +M4: LD A,(HL) ; move all non-zero tiles + CP 0 + JP Z,M3 + + LD (DE),A + INC DE + XOR A + LD (HL),A + + LD A,(MOVED) + INC A + LD (MOVED),A + +M3: INC HL + DJNZ M4 + + RET + +ROTATE: + ; outer ring + + LD B,3 + +R1: LD A,(BOARD+3) + LD C,A + + LD A,(BOARD+2) + LD (BOARD+3),A + LD A,(BOARD+1) + LD (BOARD+2),A + LD A,(BOARD+0) + LD (BOARD+1),A + + LD A,(BOARD+4) + LD (BOARD+0),A + LD A,(BOARD+8) + LD (BOARD+4),A + LD A,(BOARD+12) + LD (BOARD+8),A + + LD A,(BOARD+13) + LD (BOARD+12),A + LD A,(BOARD+14) + LD (BOARD+13),A + LD A,(BOARD+15) + LD (BOARD+14),A + + LD A,(BOARD+11) + LD (BOARD+15),A + LD A,(BOARD+7) + LD (BOARD+11),A + LD A,C + LD (BOARD+7),A + + DJNZ R1 + + ; inner ring + + LD A,(BOARD+6) + LD C,A + + LD A,(BOARD+5) + LD (BOARD+6),A + LD A,(BOARD+9) + LD (BOARD+5),A + LD A,(BOARD+10) + LD (BOARD+9),A + + LD A,C + LD (BOARD+10),A + + RET + +ADDPOINTS: + PUSH DE + PUSH HL + + LD E,A + LD D,0 + LD HL,POINTS + ADD HL,DE + ADD HL,DE + LD E,(HL) + INC HL + LD D,(HL) + + LD A,(SCORE+3) + ADD A,E + DAA + LD (SCORE+3),A + + LD A,(SCORE+2) + ADC A,D + DAA + LD (SCORE+2),A + + LD A,(SCORE+1) + ADC A,00H + DAA + LD (SCORE+1),A + + LD A,(SCORE) + ADC A,00H + DAA + LD (SCORE),A + + POP HL + POP DE + RET + +PRINTSCORE: + LD HL,SCORE + LD DE,SCORESTR+4 + LD B,4 + +P1 LD A,(HL) + RRA + RRA + RRA + RRA + AND 0FH + ADD A,30H + LD (DE),A + INC DE + + LD A,(HL) + AND 0FH + ADD A,30H + LD (DE),A + INC DE + + INC HL + DJNZ P1 + + LD HL,SCORESTR+4 + LD B,7 +P3 LD A,(HL) + CP 30H + JP NZ,P2 + LD A,20H + LD (HL),A + INC HL + DJNZ P3 + +P2 LD A,BOARD_X + ADD A,16H + DAA + LD (XPOS),A + LD A,BOARD_Y + SUB 02H + DAA + LD (YPOS),A + CALL GOTOXY + + LD DE,SCORESTR +; LD C,09H +; CALL BDOS + CALL PRTSTRDE + RET + +DRAWBORDER: + LD A,BOARD_X + LD (XPOS),A + LD A,BOARD_Y + SUB 02H + DAA + LD (YPOS),A + CALL GOTOXY + + LD DE,MSG4 +; LD C,09H +; CALL BDOS + CALL PRTSTRDE + + LD A,BOARD_X + SUB 01H + DAA + LD (XPOS),A + LD A,BOARD_Y + SUB 01H + DAA + LD (YPOS),A + CALL GOTOXY + + LD DE,TOPBORDER +; LD C,09H +; CALL BDOS + CALL PRTSTRDE + + LD B,12 +L4 PUSH BC + LD A,(YPOS) + ADD A,1 + DAA + LD (YPOS),A + CALL GOTOXY +; LD C,02H + LD E,0DBH +; CALL BDOS + CALL COUTE + LD DE,ADVANCE +; LD C,09H +; CALL BDOS + CALL PRTSTRDE +; LD C,02H + LD E,0DBH +; CALL BDOS + CALL COUTE + POP BC + DEC B + JP NZ,L4 + + LD A,(YPOS) + ADD A,1 + DAA + LD (YPOS),A + CALL GOTOXY + LD DE,BTMBORDER +; LD C,09H +; CALL BDOS + CALL PRTSTRDE + + RET + +DRAWBOARD: + CALL PRINTSCORE + + LD A,BOARD_X + LD (XPOS),A + LD A,BOARD_Y + SUB 01H + DAA + LD (YPOS),A + + LD HL,BOARD + LD B,4 +L1 PUSH BC + + PUSH HL + LD A,(YPOS) + ADD A,1 + DAA + LD (YPOS),A + CALL GOTOXY + POP HL + + PUSH HL + LD E,(HL) + CALL PRINTSPACE + POP HL + INC HL + PUSH HL + LD E,(HL) + CALL PRINTSPACE + POP HL + INC HL + PUSH HL + LD E,(HL) + CALL PRINTSPACE + POP HL + INC HL + PUSH HL + LD E,(HL) + CALL PRINTSPACE + CALL NEWLINE + POP HL + + DEC HL + DEC HL + DEC HL + + PUSH HL + LD A,(YPOS) + ADD A,1 + DAA + LD (YPOS),A + CALL GOTOXY + POP HL + + PUSH HL + LD E,(HL) + CALL PRINTLABEL + POP HL + INC HL + PUSH HL + LD E,(HL) + CALL PRINTLABEL + POP HL + INC HL + PUSH HL + LD E,(HL) + CALL PRINTLABEL + POP HL + INC HL + PUSH HL + LD E,(HL) + CALL PRINTLABEL + CALL NEWLINE + POP HL + + DEC HL + DEC HL + DEC HL + + PUSH HL + LD A,(YPOS) + ADD A,1 + DAA + LD (YPOS),A + CALL GOTOXY + POP HL + + PUSH HL + LD E,(HL) + CALL PRINTSPACE + POP HL + INC HL + PUSH HL + LD E,(HL) + CALL PRINTSPACE + POP HL + INC HL + PUSH HL + LD E,(HL) + CALL PRINTSPACE + POP HL + INC HL + PUSH HL + LD E,(HL) + CALL PRINTSPACE + CALL NEWLINE + POP HL + + INC HL + + POP BC + DEC B + JP NZ,L1 + + RET + +PRINTSPACE: + LD D,0 + + PUSH DE + LD HL,COLORPTR + ADD HL,DE + ADD HL,DE + LD E,(HL) + INC HL + LD D,(HL) +; LD C,09H +; CALL BDOS + CALL PRTSTRDE + POP DE + + LD DE,SPACER +; LD C,09H +; CALL BDOS + CALL PRTSTRDE + + RET + +PRINTLABEL: + LD D,0 + + PUSH DE + LD HL,COLORPTR + ADD HL,DE + ADD HL,DE + LD E,(HL) + INC HL + LD D,(HL) +; LD C,09H +; CALL BDOS + CALL PRTSTRDE + POP DE + + PUSH DE + LD HL,LABELPTR + ADD HL,DE + ADD HL,DE + LD E,(HL) + INC HL + LD D,(HL) +; LD C,09H +; CALL BDOS + CALL PRTSTRDE + POP DE + + RET + +GOTOXY: +; LD C,02H + LD E,1BH +; CALL BDOS + CALL COUTE +; LD C,02H + LD E,'[' +; CALL BDOS + CALL COUTE + + LD A,(YPOS) + CP 10H + JP C,L2 + + RRA + RRA + RRA + RRA + AND 0FH + ADD A,'0' + +; LD C,02H + LD E,A +; CALL BDOS + CALL COUTE + + LD A,(YPOS) + +L2 AND 0FH + ADD A,'0' + +; LD C,02H + LD E,A +; CALL BDOS + CALL COUTE + +; LD C,02H + LD E,3BH +; CALL BDOS + CALL COUTE + + LD A,(XPOS) + CP 10H + JP C,L3 + + RRA + RRA + RRA + RRA + AND 0FH + ADD A,'0' + +; LD C,02H + LD E,A +; CALL BDOS + CALL COUTE + + LD A,(XPOS) + +L3 AND 0FH + ADD A,'0' + +; LD C,02H + LD E,A +; CALL BDOS + CALL COUTE + +; LD C,02H + LD E,'H' +; CALL BDOS + CALL COUTE + + RET + +NEWLINE: + +; LD C,02H + LD E,CR +; CALL BDOS + CALL COUTE + +; LD C,02H + LD E,LF +; CALL BDOS + CALL COUTE + + RET + +RAND16 LD DE,0 + LD A,D + LD H,E + LD L,253 + OR A + SBC HL,DE + SBC A,0 + SBC HL,DE + LD D,0 + SBC A,D + LD E,A + SBC HL,DE + JR NC,RAND + INC HL +RAND LD (RAND16+1),HL + RET + +DIV16: + LD A,H ; HL = HL % DE + LD C,L + LD HL,0 + LD B,16 + +DL1 SCF + RL C + RLA + ADC HL,HL + SBC HL,DE + JR NC,$+4 + ADD HL,DE + DEC C + DJNZ DL1 + + LD D,A ; DE = HL / DE + LD E,C + RET + +SPACER .DB " ", '$' + +LABELPTR: .DW LABELS + .DW LABELS + 8 + .DW LABELS + 16 + .DW LABELS + 24 + .DW LABELS + 32 + .DW LABELS + 40 + .DW LABELS + 48 + .DW LABELS + 56 + .DW LABELS + 64 + .DW LABELS + 72 + .DW LABELS + 80 + .DW LABELS + 88 + .DW LABELS + 96 + .DW LABELS + 104 + +LABELS .DB " ", EOS + .DB " 2 ", EOS + .DB " 4 ", EOS + .DB " 8 ", EOS + .DB " 16 ", EOS + .DB " 32 ", EOS + .DB " 64 ", EOS + .DB " 128 ", EOS + .DB " 256 ", EOS + .DB " 512 ", EOS + .DB " 1024 ", EOS + .DB " 2048 ", EOS + .DB " 4096 ", EOS + .DB " 8192 ", EOS + +COLORPTR: .DW COLORS + .DW COLORS + 9 + .DW COLORS + 18 + .DW COLORS + 27 + .DW COLORS + 36 + .DW COLORS + 45 + .DW COLORS + 54 + .DW COLORS + 63 + .DW COLORS + 72 + .DW COLORS + 81 + .DW COLORS + 90 + .DW COLORS + 99 + .DW COLORS + 108 + .DW COLORS + 117 + +COLORS .DB 1BH, "[40;37m", EOS ; 0 + .DB 1BH, "[44;37m", EOS ; 2 + .DB 1BH, "[46;37m", EOS ; 4 + .DB 1BH, "[42;37m", EOS ; 8 + .DB 1BH, "[43;30m", EOS ; 16 + .DB 1BH, "[45;37m", EOS ; 32 + .DB 1BH, "[41;37m", EOS ; 64 + .DB 1BH, "[47;30m", EOS ; 128 + .DB 1BH, "[44;37m", EOS ; 256 + .DB 1BH, "[46;37m", EOS ; 512 + .DB 1BH, "[42;37m", EOS ; 1024 + .DB 1BH, "[43;30m", EOS ; 2048 + .DB 1BH, "[45;37m", EOS ; 4096 + .DB 1BH, "[41;37m", EOS ; 8192 + +POINTS .DW 0000H + .DW 0000H + .DW 0004H + .DW 0008H + .DW 0016H + .DW 0032H + .DW 0064H + .DW 0128H + .DW 0256H + .DW 0512H + .DW 1024H + .DW 2048H + .DW 4096H + .DW 8192H + +INIT: .DB 1BH, "[2J", EOS +TERM: .DB 1BH, "[?25h" +RESET: .DB 1BH, "[0m", EOS + +ADVANCE: .DB 1BH, "[28C", EOS + +TOPBORDER: + .DB 1BH, "[47;37m" + .DB 0DCH + .DB 0DCH, 0DCH, 0DCH, 0DCH, 0DCH, 0DCH, 0DCH + .DB 0DCH, 0DCH, 0DCH, 0DCH, 0DCH, 0DCH, 0DCH + .DB 0DCH, 0DCH, 0DCH, 0DCH, 0DCH, 0DCH, 0DCH + .DB 0DCH, 0DCH, 0DCH, 0DCH, 0DCH, 0DCH, 0DCH + .DB 0DCH, EOS + +BTMBORDER .DB 0DFH + .DB 0DFH, 0DFH, 0DFH, 0DFH, 0DFH, 0DFH, 0DFH + .DB 0DFH, 0DFH, 0DFH, 0DFH, 0DFH, 0DFH, 0DFH + .DB 0DFH, 0DFH, 0DFH, 0DFH, 0DFH, 0DFH, 0DFH + .DB 0DFH, 0DFH, 0DFH, 0DFH, 0DFH, 0DFH, 0DFH + .DB 0DFH, 1BH, "[0m", EOS + +MSG1: .DB 1BH, "[0m" + .DB "UDLR = wsad ^E^X^S^D 8246 : Q=QUIT" + + .DB EOS +MSG2: .DB 1BH, "[0m" + .DB " QUIT or RESTART (y/n/r) " + .DB EOS +MSG3: .DB 1BH, "[0m" + .DB " GAME OVER! PLAY AGAIN (y/n) " + .DB BEL, CR, LF, EOS +MSG4: .DB 1BH, "[0m" + .DB "2048 pts" + .DB EOS + +; HBIOS functions replacing BDOS functions +; +; +; PRINT A STRING AT ADDRESS SPECIFIED IN DE +; STRING MUST BE TERMINATED BY '$' +; +PRTSTRDE: + PUSH HL + LD H,D + LD L,E + CALL PRTSTR + POP HL + RET +; +; PRINT A STRING AT ADDRESS SPECIFIED IN HL +; STRING MUST BE TERMINATED BY '$' +; USAGE: +; LD HL,MYSTR +; CALL PRTSTR +; ... +; MYSTR: .DB "HELLO$" +; +PRTSTR: + LD A,(HL) + INC HL + CP '$' + RET Z + CALL COUT + JR PRTSTR +; +; OUTPUT CHARACTER IN A TO CONSOLE DEVICE +; +COUT: PUSH AF + PUSH BC + PUSH DE + LD B,01H + LD C,0 + LD E,A + RST 08 + POP DE + POP BC + POP AF + RET +; +; OUTPUT CHARACTER IN A TO CONSOLE DEVICE +; +COUTE: PUSH AF + LD A,E + CALL COUT + POP AF + RET +; +; WAIT FOR A CHARACTER FROM THE CONSOLE DEVICE AND RETURN IT IN A +; +CIN: PUSH BC + LD B,00H + LD C,00H + RST 08 + LD A,E + POP BC + RET + +; variables + +BOARD: + .DB 00, 00, 00, 00 + .DB 00, 00, 00, 00 + .DB 00, 00, 00, 00 + .DB 00, 00, 00, 00 + +XPOS: .DB 0 +YPOS: .DB 0 +BOARDPTR: .DW 0 +MOVED: .DB 0 +RESULT: .DB 0 +SCORE .DB 00H, 00H, 00H, 00H +VAREND .EQU $ +SCORESTR .DB 1BH, "[0m $" + +LASTBYTE .EQU $ + +SLACK .EQU (GAM_END - LASTBYTE) + .FILL SLACK,'e' +; + .ECHO "GAME space remaining: " + .ECHO SLACK + .ECHO " bytes.\n" + + .END + diff --git a/Source/HBIOS/hbios.asm b/Source/HBIOS/hbios.asm index a23f8e15..55ed757e 100644 --- a/Source/HBIOS/hbios.asm +++ b/Source/HBIOS/hbios.asm @@ -44,6 +44,27 @@ ; THAT AN OS IMAGES FILE IS APPENDED TO THE END OF THE IMAGE AND IS LAUNCHED ; AFTER HBIOS IS INSTALLED. ; +; INCLUDE FILE NESTING: +; +; hbios.asm +; - std.asm +; - ver.inc +; - hbios.inc +; - build.inc +; - config/_.asm +; - cfg_.asm +; - .asm +; - .asm +; - util.asm +; - time.asm +; - bcd.asm +; - decode.asm +; - encode.asm +; - [xio|mio].asm +; - dsky.asm +; - unlzsa2s.asm +; +; ; INCLUDE GENERIC STUFF ; #INCLUDE "std.asm" @@ -67,19 +88,24 @@ MODCNT .SET MODCNT + 1 ; ; ; -#IF ((PLATFORM == PLT_RC) | (PLATFORM == PLT_RC180)) -#DEFINE DIAGP $00 -#ENDIF -; -#IFDEF DIAGP +#IF (DIAGENABLE) #DEFINE DIAG(N) PUSH AF #DEFCONT \ LD A,N - #DEFCONT \ OUT (DIAGP),A + #DEFCONT \ OUT (DIAGPORT),A #DEFCONT \ POP AF #ELSE #DEFINE DIAG(N) \; #ENDIF ; +#IF (LEDENABLE) +#DEFINE LED(N) PUSH AF + #DEFCONT \ LD A,~N + #DEFCONT \ OUT (LEDPORT),A + #DEFCONT \ POP AF +#ELSE +#DEFINE LED(N) \; +#ENDIF +; ; ; #IF (INTMODE == 0) @@ -99,6 +125,21 @@ MODCNT .SET MODCNT + 1 ; ; ; +#IF (CTCENABLE) +CTCA .EQU CTCBASE + 0 ; CTC: CHANNEL A REGISTER ADR +CTCB .EQU CTCBASE + 1 ; CTC: CHANNEL B REGISTER ADR +CTCC .EQU CTCBASE + 2 ; CTC: CHANNEL C REGISTER ADR +CTCD .EQU CTCBASE + 3 ; CTC: CHANNEL D REGISTER ADR +#ENDIF +; +; THIS EQUATE IS UPDATED BY DRIVER INCLUDES THAT SHARE THE RTC LATCH. +; AS DRIVER IS INCLUDED, IT WILL USE .SET TO SET ANY BITS THEY OWN +; AND WANT TO SET AS DEFAULT. +; +RTCDEF .EQU 0 ; ALLOWS DRIVERS TO SET BITS +; +; +; #IFNDEF APPBOOT ; .ORG 0 @@ -124,7 +165,7 @@ MODCNT .SET MODCNT + 1 RET .FILL (038H - $),0FFH ; RST 38 / IM1 INT #IF (INTMODE == 1) - JP INT_IM1 ; JP TO INTERRUPT HANDLER IN HI MEM + JP INT_IM1 ; JP TO INTERRUPT HANDLER IN HI MEM #ELSE RETI ; RETURN W/ INTS DISABLED #ENDIF @@ -144,7 +185,7 @@ ROM_SIG: ; NAME .DB "ROMWBW v", BIOSVER, ", ", TIMESTAMP, 0 AUTH .DB "WBW",0 -DESC .DB "ROMWBW v", BIOSVER, ", Copyright (C) 2015, Wayne Warthen, GNU GPL v3", 0 +DESC .DB "ROMWBW v", BIOSVER, ", Copyright (C) 2020, Wayne Warthen, GNU GPL v3", 0 ; .FILL ($100 - $),$FF ; PAD REMAINDER OF PAGE ZERO ; @@ -215,7 +256,7 @@ CB_BIDROMDN .DB BID_ROMDN ; ; DEFINITIONS ; -HBX_BUFSIZ .EQU $40 ; INTERBANK COPY BUFFER SIZE +HBX_BUFSIZ .EQU $40 ; INTERBANK COPY BOUNCE BUFFER SIZE ; ; HBIOS IDENTIFICATION DATA BLOCK ; @@ -228,27 +269,55 @@ HBX_IDENT: ; HBIOS ENTRY FOR RST 08 PROCESSING ;================================================================================================== ; +; NOTE: THE SIZE OF HBX_TMPSTK (TYPICALLY 20 BYTES) IS INSUFFICIENT FOR +; FREERTOS IF AN INTERRUPT STRIKES WHILE THE TEMPORARY STACK IS ACTIVE. +; BELOW, HBX_BUF HAS BEEN USURPED TO PROVIDE A LARGER TEMP STACK TO +; ACCOMMODATE FREERTOS. HBX_BUF IS ONLY USED AS A BOUNCE BUFFER, SO IT'S +; USE WILL NEVER OVERLAP WITH BELOW. +; +; HBX_INVOKE IS NOT RE-ENTRANT! HB_INVBNK CAN BE USED GLOBALLY TO DETERMINE +; IF HBIOS IS ALREADY ACTIVE. HB_INVBNK WILL HAVE A VALUE != $FF WHEN HBIOS +; IS ACTIVE. ON RETURN, HB_INVBNK IS SET TO $FF TO INDICATE HBIOS IS NOT +; ACTIVE. +; HBX_INVOKE: + +#IF (HBIOS_MUTEX == TRUE) + PUSH HL ; SAVE HL + LD HL,HB_LOCK ; POINT TO LOCK + SRA (HL) ; TEST/ACQUIRE MUTEX LOCK + JR C,$-2 ; KEEP TRYING ON FAILURE + POP HL ; RESTORE HL +#ENDIF + LD (HBX_INVSP),SP ; SAVE ORIGINAL STACK FRAME + LD SP,HBX_BUF_END ; BORROW HBX_BUF FOR TEMP STACK + LD A,(HB_CURBNK) ; GET CURRENT BANK LD (HB_INVBNK),A ; SAVE INVOCATION BANK - LD SP,HBX_TMPSTK ; USE SMALL TEMP STACK FRAME IN HI MEM FOR BANK SWITCH LD A,BID_BIOS ; HBIOS BANK CALL HBX_BNKSEL ; SELECT IT LD SP,HB_STACK ; NOW USE FULL HBIOS STACK IN HBIOS BANK - + CALL HB_DISPATCH ; CALL HBIOS FUNCTION DISPATCHER - LD SP,HBX_TMPSTK ; USE SMALL TEMP STACK FRAME IN HI MEM FOR BANK SWITCH + LD SP,HBX_BUF_END ; BORROW HBX_BUF FOR TEMP STACK PUSH AF ; SAVE AF (FUNCTION RETURN) - + LD A,(HB_INVBNK) ; LOAD ORIGINAL BANK CALL HBX_BNKSEL ; SELECT IT POP AF ; RESTORE AF LD SP,0 ; RESTORE ORIGINAL STACK FRAME HBX_INVSP .EQU $ - 2 +#IF (HBIOS_MUTEX == TRUE) + PUSH HL ; SAVE HL + LD HL,HB_LOCK ; POINT TO LOCK + LD (HL),$FE ; RELEASE MUTEX LOCK + POP HL ; RESTORE HL +#ENDIF + RET ; RETURN TO CALLER ; ;;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -266,7 +335,7 @@ HBX_BNKSEL: ; HBX_BNKSEL_INT: ; -#IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA)) +#IF (MEMMGR == MM_SBC) #IF (INTMODE == 1) ; THIS BIT OF ABSURDITY HANDLES A RARE (BUT FATAL) SITUATION ; WHERE AN IM1 INTERRUPT OCCURS BETWEEN SETTING THE RAM AND @@ -290,7 +359,7 @@ HBX_ROM: OUT (MPCL_ROM),A ; SET ROM PAGE SELECTOR RET ; DONE #ENDIF -#IF ((PLATFORM == PLT_ZETA2) | (PLATFORM == PLT_RC) | (PLATFORM == PLT_RC180)) +#IF (MEMMGR == MM_Z2) BIT 7,A ; BIT 7 SET REQUESTS RAM PAGE JR Z,HBX_ROM ; NOT SET, SELECT ROM PAGE RES 7,A ; RAM PAGE REQUESTED: CLEAR ROM BIT @@ -299,12 +368,12 @@ HBX_ROM: HBX_ROM: RLCA ; TIMES 2 - GET 16K PAGE INSTEAD OF 32K OUT (MPGSEL_0),A ; BANK_0: 0K - 16K - ;OUT (DIAGP),A ; *DEBUG* + ;OUT (DIAGPORT),A ; *DEBUG* INC A ; OUT (MPGSEL_1),A ; BANK_1: 16K - 32K RET ; DONE #ENDIF -#IF (PLATFORM == PLT_N8) +#IF (MEMMGR == MM_N8) BIT 7,A ; TEST BIT 7 FOR RAM VS. ROM JR Z,HBX_ROM ; IF NOT SET, SELECT ROM PAGE ; @@ -327,7 +396,7 @@ HBX_ROM: RET ; DONE ; #ENDIF -#IF (PLATFORM == PLT_MK4) +#IF (MEMMGR == MM_Z180) RLCA ; RAM FLAG TO CARRY FLAG AND BIT 0 JR NC,HBX_BNKSEL1 ; IF NC, WANT ROM PAGE, SKIP AHEAD XOR %00100001 ; SET BIT FOR HI 512K, CLR BIT 0 @@ -341,9 +410,8 @@ HBX_BNKSEL1: ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; Copy Data - Possibly between banks. This resembles CP/M 3, but ; usage of the HL and DE registers is reversed. -; Caller MUST ensure stack is already in high memory. ; Caller MUST preset HBX_SRCBNK and HBX_DSTBNK. -; Caller MUST disable ints if IM1 active +; IM1/IM2 interrupts are disabled during HBX_BNKCPY. ; Enter: ; HL = Source Address ; DE = Destination Address @@ -354,6 +422,11 @@ HBX_BNKSEL1: ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; HBX_BNKCPY: +#IF (INTMODE > 0) + LD A,I + DI + PUSH AF +#ENDIF LD (HBX_BC_SP),SP ; PUT STACK LD SP,HBX_TMPSTK ; ... IN HI MEM @@ -383,6 +456,11 @@ HBX_BC_LAST: LD SP,$FFFF ; RESTORE STACK HBX_BC_SP .EQU $ - 2 ; ... TO ORIGINAL VALUE +#IF (INTMODE > 0) + POP AF + JP PO,$+4 + EI +#ENDIF RET ; HBX_BC_ITER: @@ -407,7 +485,7 @@ HBX_BC_ITER: ; CALL A ROUTINE IN ANOTHER BANK. ; CALLER MUST ENSURE STACK IS ALREADY IN HIGH MEMORY AND HAS ADEQUATE SPACE. ; IF IM1 INTERRUPTS ARE POSSIBLE, CALLER MUST EITHER DISABLE THEM PRIOR TO -; BNKCALL OR MAKE SURE THAT PAGE ZERO IN TARGTET BANK IS PREPARED FOR THEM. +; BNKCALL OR MAKE SURE THAT PAGE ZERO IN TARGET BANK IS PREPARED FOR THEM. ; ON INPUT A=TARGET BANK, HL=TARGET ADDRESS ; HBX_BNKCALL: @@ -432,11 +510,21 @@ HBX_TGTADR .EQU $ + 1 ; ; PEEK & POKE ROUTINES ; ADDRESS IN HL, BANK IN D, VALUE IN/OUT IN E, A IS TRASHED -; CALLER MUST DISABLE INTS IF IM1 AND ACCESSING PAGE W/O IM1 INT VECTOR +; +; THESE ROUTINES ARE NOT INTENDED TO BE CALLED DIRECTLY -- THEY ARE +; HELPERS FOR THE HBIOS API AND ARE CALLED BY HBIOS BANK CODE. THE +; HBIOS BANK CODE BRACKETS THE USE OF THESE ROUTINES WITH DI/EI IF +; NECESSARY FOR THE CURRENT INTERRUPT MODE. +; +; NOTE: THE SIZE OF HBX_TMPSTK (TYPICALLY 20 BYTES) IS INSUFFICIENT FOR +; FREERTOS IF AN INTERRUPT STRIKES WHILE THE TEMPORARY STACK IS ACTIVE. +; BELOW, HBX_BUF HAS BEEN USURPED TO PROVIDE A LARGER TEMP STACK TO +; ACCOMMODATE FREERTOS. HBX_BUF IS ONLY USED AS A BOUNCE BUFFER, SO IT'S +; USE WILL NEVER OVERLAP WITH BELOW. ; HBX_PEEK: LD (HBX_PPSP),SP ; SAVE ORIGINAL STACK FRAME - LD SP,HBX_TMPSTK ; USE SMALL TEMP STACK FRAME IN HI MEM + LD SP,HBX_BUF_END ; BORROW HBX_BUF FOR TEMP STACK LD A,(HB_CURBNK) PUSH AF LD A,D @@ -446,7 +534,7 @@ HBX_PEEK: ; HBX_POKE: LD (HBX_PPSP),SP ; SAVE ORIGINAL STACK FRAME - LD SP,HBX_TMPSTK ; USE SMALL TEMP STACK FRAME IN HI MEM + LD SP,HBX_BUF_END ; BORROW HBX_BUF FOR TEMP STACK LD A,(HB_CURBNK) PUSH AF LD A,D @@ -460,101 +548,91 @@ HBX_PPRET: HBX_PPSP .EQU $ - 2 RET ; -; SMALL TEMPORARY STACK FOR USE BY INVOKE, PEEK, AND POKE -; - .FILL 20,$CC ; 10 LEVEL STACK -HBX_TMPSTK .EQU $ -; ; PRIVATE STACK AT END OF HBIOS CODE ; OCCUPIES SPACE BEFORE IVT ; -HBX_STKSIZ .EQU $FF00 - $ - .ECHO "HBIOS PROXY STACK space: " - .ECHO HBX_STKSIZ +HBX_INTSTKSIZ .EQU $FF00 - $ + .ECHO "HBIOS INT STACK space: " + .ECHO HBX_INTSTKSIZ .ECHO " bytes.\n" - .FILL HBX_STKSIZ,$FF -HBX_STACK .EQU $ + .FILL HBX_INTSTKSIZ,$FF +HBX_INTSTK .EQU $ +; +#IF (HBX_INTSTKSIZ < 24) + .ECHO "*** ERROR: INTERRUPT STACK IS TOO SMALL!!!\n" + !!! ; FORCE AN ASSEMBLY ERROR +#ENDIF ; #IF (INTMODE == 2) ; -; HBIOS INTERRUPT VECTOR TABLE (16 ENTRIES) +; HBIOS INTERRUPT SLOT ASSIGNMENTS +; +; # Z80 Z180 +; --- -------------- -------------- +; 0 CTC0A INT1 -+ +; 1 CTC0B INT2 | +; 2 CTC0C TIM0 | +; 3 CTC0D TIM1 | +; 4 DMA0 +- Z180 INTERNAL +; 5 DMA1 | +; 6 CSIO | +; 7 SIO0 SER0 | +; 8 SIO1 SER1 -+ +; 9 PIO0A PIO0A +; 10 PIO0B PIO0B +; 11 PIO1A PIO1A +; 12 PIO1B PIO1B +; 13 SIO0 +; 14 SIO1 +; 15 ; HBX_IVT: - .DW INT_BAD ; IVT_INT1 - .DW INT_BAD ; IVT_INT2 - .DW INT_BAD ; IVT_TIM0 - .DW INT_BAD ; IVT_TIM1 - .DW INT_BAD ; IVT_DMA0 - .DW INT_BAD ; IVT_DMA1 - .DW INT_BAD ; IVT_CSIO - .DW INT_BAD ; IVT_SER0 - .DW INT_BAD ; IVT_SER1 - .DW INT_BAD ; - .DW INT_BAD ; - .DW INT_BAD ; - .DW INT_BAD ; - .DW INT_BAD ; - .DW INT_BAD ; - .DW INT_BAD ; + .DW HBX_IV00 + .DW HBX_IV01 + .DW HBX_IV02 + .DW HBX_IV03 + .DW HBX_IV04 + .DW HBX_IV05 + .DW HBX_IV06 + .DW HBX_IV07 + .DW HBX_IV08 + .DW HBX_IV09 + .DW HBX_IV0A + .DW HBX_IV0B + .DW HBX_IV0C + .DW HBX_IV0D + .DW HBX_IV0E + .DW HBX_IV0F ; HBX_IVTCNT .EQU ($ - HBX_IVT) / 2 ; -HBX_ITBL: - .DW HB_BADINT - .DW HB_BADINT - .DW HB_BADINT - .DW HB_BADINT - .DW HB_BADINT - .DW HB_BADINT - .DW HB_BADINT - .DW HB_BADINT - .DW HB_BADINT - .DW HB_BADINT - .DW HB_BADINT - .DW HB_BADINT - .DW HB_BADINT - .DW HB_BADINT - .DW HB_BADINT - .DW HB_BADINT -#ENDIF -; -; INTERRUPT HANDLER STUBS -; -; THE FOLLOWING INTERRUPT STUBS RECEIVE CONTROL FROM THE -; INTERRUPT, SETUP A HANDLER VECTOR IN HBIOS AND THEN -; BRANCH TO THE COMMON INTERRUPT DISPATCHER +HBX_IV00: CALL HBX_INT \ .DB $00 << 2 +HBX_IV01: CALL HBX_INT \ .DB $01 << 2 +HBX_IV02: CALL HBX_INT \ .DB $02 << 2 +HBX_IV03: CALL HBX_INT \ .DB $03 << 2 +HBX_IV04: CALL HBX_INT \ .DB $04 << 2 +HBX_IV05: CALL HBX_INT \ .DB $05 << 2 +HBX_IV06: CALL HBX_INT \ .DB $06 << 2 +HBX_IV07: CALL HBX_INT \ .DB $07 << 2 +HBX_IV08: CALL HBX_INT \ .DB $08 << 2 +HBX_IV09: CALL HBX_INT \ .DB $09 << 2 +HBX_IV0A: CALL HBX_INT \ .DB $0A << 2 +HBX_IV0B: CALL HBX_INT \ .DB $0B << 2 +HBX_IV0C: CALL HBX_INT \ .DB $0C << 2 +HBX_IV0D: CALL HBX_INT \ .DB $0D << 2 +HBX_IV0E: CALL HBX_INT \ .DB $0E << 2 +HBX_IV0F: CALL HBX_INT \ .DB $0F << 2 ; +#ENDIF ; INT_IM1: #IF (INTMODE == 1) - PUSH HL ; SAVE HL - LD HL,HB_IM1INT ; HL := IM1 INT HANDLER IN BIOS BANK - JR HBX_INT ; TO TO ROUTING CODE + CALL HBX_INT + .DB $00 #ELSE RETI ; UNEXPECTED INT, RET W/ INTS LEFT DISABLED #ENDIF ; -#IF (INTMODE == 2) -; -INT_BAD: ; BAD INTERRUPT HANDLER - PUSH HL ; SAVE HL - LD HL,HB_BADINT ; HL := INT HANDLER IN BIOS BANK - JR HBX_INT ; GO TO ROUTING CODE -; -INT_TIMER: ; TIMER INTERRUPT HANDLER - PUSH HL ; SAVE HL - LD HL,HB_TIMINT ; HL := INT ADR IN BIOS - JR HBX_INT ; GO TO ROUTING CODE -; - #IF (SIOENABLE) -INT_SIO: ; SIO INTERRUPT HANDLER - PUSH HL ; SAVE HL - LD HL,SIO_INT ; HL := SIO INT HANDLER IN BIOS BANK - JR HBX_INT ; GO TO ROUTING CODE - #ENDIF -; -#ENDIF -; #IF (INTMODE > 0) ; ; COMMON INTERRUPT DISPATCHING CODE @@ -562,23 +640,28 @@ INT_SIO: ; SIO INTERRUPT HANDLER ; HBX_INT: ; COMMON INTERRUPT ROUTING CODE ; + EX (SP),HL ; SAVE HL AND GET INT JP TABLE OFFSET + LD (HBX_INT_SP),SP ; SAVE ORIGINAL STACK FRAME - LD SP,HBX_STACK ; USE STACK FRAME IN HI MEM + LD SP,HBX_INTSTK ; USE DEDICATED INT STACK FRAME IN HI MEM ; SAVE STATE (HL SAVED PREVIOUSLY ON ORIGINAL STACK FRAME) PUSH AF ; SAVE AF PUSH BC ; SAVE BC PUSH DE ; SAVE DE PUSH IY ; SAVE IY - + LD A,BID_BIOS ; HBIOS BANK CALL HBX_BNKSEL_INT ; SELECT IT - CALL JPHL ; CALL INTERRUPT ROUTINE + LD L,(HL) ; OFFSET INTO JP TABLE FOR THIS INT + LD H,HB_IVT >> 8 ; MSB OF HBIOS INT JP TABLE + + CALL JPHL ; CALL HANDLER VIA INT JP TABLE LD A,(HB_CURBNK) ; GET PRE-INT BANK CALL HBX_BNKSEL ; SELECT IT - + ; RESTORE STATE POP IY ; RESTORE IY POP DE ; RESTORE DE @@ -595,40 +678,47 @@ HBX_INT_SP .EQU $ - 2 ; #ENDIF ; -; FILL TO START OF BOUNCE BUFFER +; SMALL TEMPORARY STACK FOR USE BY HBX_BNKCPY ; -HBX_INTFILL .EQU (HBX_XFC - HBX_BUFSIZ - $) - .ECHO "HBIOS INT space remaining: " - .ECHO HBX_INTFILL +HBX_TMPSTKSIZ .EQU (HBX_XFC - HBX_BUFSIZ - $) + .ECHO "HBIOS TEMP STACK space: " + .ECHO HBX_TMPSTKSIZ .ECHO " bytes.\n" - .FILL HBX_INTFILL,$FF + .FILL HBX_TMPSTKSIZ,$CC +HBX_TMPSTK .EQU $ +; +; INTERBANK COPY BOUNCE BUFFER (64 BYTES) ; -; INTERBANK COPY BUFFER (64 BYTES) +; N.B., THIS BUFFER IS ALSO USED AS A TEMPORARY STACK BY INVOKE, PEEK, AND POKE. +; THEREFORE, THIS BUFFER *CANNOT* BE USED TO PASS DATA OUTSIDE OF +; HBIOS FUNCTION CALLS. ; HBX_BUF .FILL HBX_BUFSIZ,0 +HBX_BUF_END .EQU $ ; ; HBIOS PROXY MGMT BLOCK (TOP 32 BYTES) ; #IFDEF ROMBOOT - .DB BID_BOOT ; CURRENTLY ACTIVE LOW MEMORY BANK ID + .DB BID_BOOT ; HB_CURBNK: CURRENTLY ACTIVE LOW MEMORY BANK ID #ELSE - .DB BID_USR ; CURRENTLY ACTIVE LOW MEMORY BANK ID -#ENDIF - .DB 0 ; BANK ACTIVE AT TIME OF HBIOS CALL INVOCATION - .DW 0 ; BNKCPY SOURCE ADDRESS - .DB BID_USR ; BNKCPY SOURCE BANK ID - .DW 0 ; BNKCPY DESTINATION ADDRESS - .DB BID_USR ; BNKCPY DESTINATION BANK ID - .DW 0 ; BNKCPY LENGTH - .FILL 6,0 ; FILLER, RESERVED FOR FUTURE HBIOS USE - JP HBX_INVOKE ; FIXED ADR ENTRY FOR HBX_INVOKE (ALT FOR RST 08) - JP HBX_BNKSEL ; FIXED ADR ENTRY FOR HBX_BNKSEL - JP HBX_BNKCPY ; FIXED ADR ENTRY FOR HBX_BNKCPY - JP HBX_BNKCALL ; FIXED ADR ENTRY FOR HBX_BNKCALL + .DB BID_USR ; HB_CURBNK: CURRENTLY ACTIVE LOW MEMORY BANK ID +#ENDIF + .DB $FF ; HB_INVBNK: BANK ACTIVE AT TIME OF HBIOS CALL INVOCATION + .DW 0 ; HB_SRCADR: BNKCPY SOURCE ADDRESS + .DB BID_USR ; HB_SRCBNK: BNKCPY SOURCE BANK ID + .DW 0 ; HB_DSTADR: BNKCPY DESTINATION ADDRESS + .DB BID_USR ; HB_DSTBNK: BNKCPY DESTINATION BANK ID + .DW 0 ; HB_CPYLEN: BNKCPY LENGTH + .FILL 5,0 ; FILLER, RESERVED FOR FUTURE HBIOS USE + .DB $FE ; HB_LOCK: HBIOS MUTEX LOCK + JP HBX_INVOKE ; HB_INVOKE: FIXED ADR ENTRY FOR HBX_INVOKE (ALT FOR RST 08) + JP HBX_BNKSEL ; HB_BNKSEL: FIXED ADR ENTRY FOR HBX_BNKSEL + JP HBX_BNKCPY ; HB_BNKCPY: FIXED ADR ENTRY FOR HBX_BNKCPY + JP HBX_BNKCALL ; HB_BNKCALL: FIXED ADR ENTRY FOR HBX_BNKCALL .DW HBX_IDENT ; ADDRESS OF HBIOS PROXY START (DEPRECATED) - .DW HBX_IDENT ; ADDRESS OF HBIOS IDENT INFO DATA BLOCK + .DW HBX_IDENT ; HB_IDENT: ADDRESS OF HBIOS IDENT INFO DATA BLOCK ; - .FILL $MEMTOP - $ ; FILL TO END OF MEMORY (AS NEEDED) + .FILL MEMTOP - $ ; FILL TO END OF MEMORY (AS NEEDED) .ORG HBX_IMG + HBX_SIZ ; RESET ORG ; ;================================================================================================== @@ -648,7 +738,73 @@ HB_STKSIZ .EQU HB_ENTRYTBL + 256 - $ ; .FILL HB_STKSIZ,$FF ; USE REMAINDER OF PAGE FOR HBIOS STACK HB_STACK .EQU $ ; TOP OF HBIOS STACK - +; +;================================================================================================== +; INTERRUPT VECTOR TABLE (MUST START AT PAGE BOUNDARY!!!) +;================================================================================================== +; +; IM1 INTERRUPTS ARRIVE HERE AFTER BANK SWITCH TO HBIOS BANK +; LIST OF IM1 INT CALLS IS BUILT DYNAMICALLY BELOW +; SEE HB_ADDIM1 ROUTINE +; EACH ENTRY WILL LOOK LIKE: +; CALL XXXX ; CALL INT HANDLER +; RET NZ ; RETURN IF HANDLED +; +; NOTE THAT THE LIST IS INITIALLY FILLED WITH CALLS TO HB_BADINT. +; AS THE TABLE IS POPULATED, THE ADDRESS OF HB_BADINT IS OVERLAID +; WITH THE ADDRESS OF A REAL INTERRUPT HANDLER. +; +; THERE IS ROOM FOR 8 ENTRIES PLUS A FINAL CALL TO HB_BADINT. +; +#IF (INTMODE < 2) +; +HB_IVT: + CALL HB_BADINT \ RET NZ + CALL HB_BADINT \ RET NZ + CALL HB_BADINT \ RET NZ + CALL HB_BADINT \ RET NZ + CALL HB_BADINT \ RET NZ + CALL HB_BADINT \ RET NZ + CALL HB_BADINT \ RET NZ + CALL HB_BADINT \ RET NZ + CALL HB_BADINT \ RET NZ +; +#ENDIF +; +; IM2 INTERRUPTS ARRIVE HERE AFTER BANK SWITCH TO HBIOS BANK +; THE LIST OF JP TABLE ENTRIES MATCHES THE IM2 VECTORS ONE FOR +; ONE. ANY CALL TO THE PRIMARY IVT (HBX_IVT) WILL BE MAPPED TO +; THE CORRESPONDING JP TABLE ENTRY BELOW AFTER THE BANK SWITCH. +; +; NOTE THAT THE LIST IS INITIALLY FILLED WITH CALLS TO HB_BADINT. +; IT IS INTENDED THAT HARDWARE DRIVERS WILL DYNAMICALLY OVERLAY +; THE ADDRESS PORTION OF THE APPROPRIATE JP TO POINT TO THE +; DESIRED INTERRUPT HANDLER DURING THE DRIVERS INITIALIZATION. +; +; NOTE THAT EACH ENTRY HAS A FILLER BYTE OF VALUE ZERO. THIS BYTE +; HAS NO FUNCTION. IT IS JUST USED TO MAKE ENTRIES AN EVEN 4 BYTES. +; +#IF (INTMODE == 2) +; +HB_IVT: +HB_IVT00: JP HB_BADINT \ .DB 0 +HB_IVT01: JP HB_BADINT \ .DB 0 +HB_IVT02: JP HB_BADINT \ .DB 0 +HB_IVT03: JP HB_BADINT \ .DB 0 +HB_IVT04: JP HB_BADINT \ .DB 0 +HB_IVT05: JP HB_BADINT \ .DB 0 +HB_IVT06: JP HB_BADINT \ .DB 0 +HB_IVT07: JP HB_BADINT \ .DB 0 +HB_IVT08: JP HB_BADINT \ .DB 0 +HB_IVT09: JP HB_BADINT \ .DB 0 +HB_IVT0A: JP HB_BADINT \ .DB 0 +HB_IVT0B: JP HB_BADINT \ .DB 0 +HB_IVT0C: JP HB_BADINT \ .DB 0 +HB_IVT0D: JP HB_BADINT \ .DB 0 +HB_IVT0E: JP HB_BADINT \ .DB 0 +HB_IVT0F: JP HB_BADINT \ .DB 0 +; +#ENDIF ; ;================================================================================================== ; SYSTEM INITIALIZATION @@ -657,21 +813,21 @@ HB_STACK .EQU $ ; TOP OF HBIOS STACK HB_START: DI ; NO INTERRUPTS IM 1 ; INTERRUPT MODE 1 -; -#IFDEF DIAGP +; +#IF (DIAGENABLE) LD A,%00000001 - OUT (DIAGP),A + OUT (DIAGPORT),A #ENDIF -; +; LD SP,HBX_LOC ; SETUP INITIAL STACK JUST BELOW HBIOS PROXY ; -#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4) | (PLATFORM == PLT_RC180)) +#IF (CPUFAM == CPU_Z180) ; SET BASE FOR CPU IO REGISTERS LD A,Z180_BASE OUT0 (Z180_ICR),A DIAG(%00000010) - + ; DISABLE REFRESH XOR A OUT0 (Z180_RCR),A @@ -679,18 +835,25 @@ HB_START: ; MASK OFF TIMER INTERRUPTS XOR A OUT0 (Z180_TCR),A + OUT0 (Z180_ITC),A ; SET DEFAULT CPU CLOCK MULTIPLIERS (XTAL / 2) + ; + ; IT HAS BEEN REPORTED THAT CMR NEEDS TO BE SET PRIOR TO CCR + ; SEEMS COUNTER-INTUITIVE AND I NEVER EXPERIENCED A PROBLEM + ; RELATED TO ORDER, BUT JUST FOR GOOD MEASURE, CMR + ; IS SET PRIOR TO CCR BELOW. + ; https://www.retrobrewcomputers.org/forum/index.php?t=msg&th=316&#msg_5045 XOR A - OUT0 (Z180_CCR),A OUT0 (Z180_CMR),A + OUT0 (Z180_CCR),A ; SET DEFAULT WAIT STATES LD A,$F0 OUT0 (Z180_DCNTL),A -#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4)) - ; MMU SETUP +#IF ((MEMMGR == MM_Z180) | (MEMMGR == MM_N8)) + ; Z180 MMU SETUP LD A,$80 OUT0 (Z180_CBAR),A ; SETUP FOR 32K/32K BANK CONFIG ;#IFDEF ROMBOOT @@ -700,22 +863,10 @@ HB_START: LD A,(RAMSIZE + RAMBIAS - 64) >> 2 OUT0 (Z180_CBR),A ; COMMON BASE = LAST (TOP) BANK #ENDIF - -#IF (Z180_CLKDIV >= 1) - ; SET CLOCK DIVIDE TO 1 RESULTING IN FULL XTAL SPEED - LD A,$80 - OUT0 (Z180_CCR),A -#ENDIF - -#IF (Z180_CLKDIV >= 2) - ; SET CPU MULTIPLIER TO 1 RESULTING IN XTAL * 2 SPEED - LD A,$80 - OUT0 (Z180_CMR),A -#ENDIF - +; #ENDIF ; -#IF ((PLATFORM == PLT_ZETA2) | (PLATFORM == PLT_RC) | (PLATFORM == PLT_RC180)) +#IF (MEMMGR == MM_Z2) ; SET PAGING REGISTERS #IFDEF ROMBOOT XOR A @@ -733,32 +884,52 @@ HB_START: #ENDIF ; DIAG(%00000011) + LED($00) +; +; CHECK BATTERY BACKUP STATUS BEFORE WE COPY PROXY TO UPPER MEMORY +; +; IF A DS1210 POWER CONTROLLER IS INSTALLED AND BATTERY BACKUP IS NOT INSTALLED +; OR IS LESS THAN 2V THEN THE DS1210 WILL BLOCK THE SECOND RAM ACCESS. +; FAILURE TO COMPLETE TWO RAM ACCESSES BEFORE INSTALLING PROXY WILL RESULT +; IN THE ROM ID BYTES NOT BEING COPIED CORRECTLY AND CP/M APPLICATIONS +; WILL NOT START CORRECTLY WHEN THEY CHECK THE ROM ID VERSION BYTES. +; THE BATTERY CONDITION VALUE IS TEMPORARILY STORED AT HBX_LOC - 1. +; IF THERE IS NO DS1210 IN THE SYSTEM, THE CODE BELOW DOES NO HARM. +; + DEC SP ; RESERVE A STACK BYTE + XOR A ; ZERO MEANS LOW BAT + LD (HBX_LOC - 1),A ; WRITE IT (SHOULD ALWAYS WORK) + INC A ; 1 MEANS BAT OK + LD (HBX_LOC - 1),A ; OVERWRITE IF NVC ALLOWS IT +; +; IF APPBOOT, SAVE CURRENT BANKID +; +; THIS IS NOT GOING TO WORK IF THE APP BOOT IMAGE IS LOADED +; USING THE UNA FAT32 LOADER. SHOULD PROBABLY CHECK THAT THERE +; IS A VALID ROMWBW PROXY IN MEMORY BEFORE DOING THIS. HOWEVER, +; THIS USE CASE IS PROBABLY NON-EXISTENT. THE IMG BOOT IMAGE +; SHOULD WORK FINE WITH THE UNA FAT32 LOADER. +; +#IFDEF APPBOOT + LD A,(HB_CURBNK) + DEC SP ; RESERVE A STACK BYTE + LD (HBX_LOC - 2),A ; SAVE BANK + PUSH AF ; ALSO ON STACK +#ENDIF ; ; INSTALL PROXY IN UPPER MEMORY ; - -;X1 .EQU $8000 -;X2 .EQU X1 + 2 -;X3 .EQU X2 + 2 -;X4 .EQU X3 + 2 - -; LD HL,(HBX_IMG) -; LD (X1),HL - -; LD HL,(HBX_IMG) -; LD (X2),HL - + LD DE,HBX_LOC ; AS PER ABOVE LD HL,HBX_IMG - LD DE,HBX_LOC LD BC,HBX_SIZ LDIR - -; LD HL,(HBX_IMG) -; LD (X3),HL - -; LD HL,(HBX_LOC) -; LD (X4),HL - +; +; IF APPBOOT, RESTORE CURRENT BANK ID +; +#IFDEF APPBOOT + POP AF + LD (HB_CURBNK),A +#ENDIF ; ; IF ALREADY EXECUTING IN RAM, BYPASS RAM BANK INSTALLATION ; @@ -791,6 +962,9 @@ HB_RAMFLAG .DB FALSE ; INITIALLY FALSE, SET TO TRUE BELOW AFTER RAM TRANSITION HB_START1: ; BNKCALL ARRIVES HERE, BUT NOW RUNNING IN RAM BANK ; DIAG(%00000111) +; + LD A,(HBX_LOC - 1) ; RECALL BATTERY STATE AND SAVE + LD (HB_BATCOND),A ; FOR FUTURE REFERENCE ; LD SP,HBX_LOC ; RESET STACK SINCE WE DO NOT RETURN LD A,TRUE ; ACCUM := TRUE @@ -799,6 +973,11 @@ HB_START1: ; BNKCALL ARRIVES HERE, BUT NOW RUNNING IN RAM BANK ; IF APPBOOT, WE NEED TO FIX UP A FEW THINGS IN PAGE ZERO ; #IFDEF APPBOOT +; + ; GET AND SAVE APP BOOT BANK ID + LD A,(HBX_LOC - 2) + LD (HB_APPBNK),A + ; MAKE SURE RST 08 VECTOR IS RIGHT LD A,$C3 LD ($0008),A @@ -814,35 +993,253 @@ HB_START1: ; BNKCALL ARRIVES HERE, BUT NOW RUNNING IN RAM BANK LD ($0039),HL #ELSE ; RETI ($ED, $4D) IF NON-INTERRUPT MODE - LD HL,($0038) + LD HL,$0038 LD (HL),$ED INC HL LD (HL),$4D #ENDIF #ENDIF +; +;================================================================================================== +; RECOVERY MODE +;================================================================================================== +; +; PLATFORM SPECIFIC CODE FOR DETECTING RECOVERY MODE SWITCH +; +#IF (BT_REC_TYPE != BT_REC_NONE) + #IF (BT_REC_TYPE == BT_REC_FORCE) + LD A,1 ; SET FOR RECOVERY MODE + LD (HB_BOOT_REC),A ; SAVE FOR LATER + #ENDIF + #IF (PLATFORM == PLT_SBC) + #IF (BT_REC_TYPE == BT_REC_SBC01) + LD A,%00100000 ; DISABLE RTC AND + OUT (RTCIO),A ; DRQ DRIVER READ + IN A,(RTCIO) ; BIT 0 (DRQ). + CPL ; PULLED HIGH + AND 1 ; IS RECOVERY MODE + LD (HB_BOOT_REC),A ; SAVE FOR LATER + #ENDIF + #IF (BT_REC_TYPE == BT_REC_SBC1B) + IN A,(RTCIO) ; RTC PORT, BIT 6 HAS THE + BIT 6,A ; STATE OF CONFIG JUMPER + LD A,1 ; JUMPER INSTALLED + JR Z,SAVE_REC_M ; IS RECOVERY MODE + LD A,0 +SAVE_REC_M: + LD (HB_BOOT_REC),A ; SAVE FOR LATER + #ENDIF + #IF (BT_REC_TYPE == BT_REC_SBCRI) + IN A,($68 + 6) ; UART_MSR MODEM + BIT 6,A ; STATUS REGISTER + LD A,0 ; BIT 6 + JR Z,SAVE_REC_M ; IS RECOVERY MODE + LD A,1 +SAVE_REC_M: + LD (HB_BOOT_REC),A ; SAVE FOR LATER + #ENDIF + #ENDIF +#ENDIF ; DIAG(%00001111) ; #IF (DSKYENABLE) + LD HL,MSG_HBVER + 5 + LD A,(DSKY_NUMS + RMJ) + AND $7F + LD (HL),A + INC HL + LD A,(DSKY_NUMS + RMN) + AND $7F + LD (HL),A + INC HL + LD A,(DSKY_NUMS + RUP) + LD (HL),A LD HL,MSG_HBVER CALL DSKY_SHOWSEG #ENDIF ; +#IF (WBWDEBUG == USEMIO) ; BUFFER OUTPUT UNTIL + CALL MIO_INIT ; WE GET TO BOOT MESSAGE +#ENDIF +; +#IF 0 +; +; TEST DEBUG *************************************************************************************** +; + PRTS("DEBUG-IVT$") + LD DE,HB_IVT + CALL DUMP_BUFFER + CALL NEWLINE +; +; TEST DEBUG *************************************************************************************** +; +#ENDIF +; +; DISCOVER CPU TYPE +; +; THIS CODE IS DERIVED FROM UNA BY JOHN COFFMAN +; +; 0: Z80 +; 1: Z80180 - ORIGINAL Z180 (EQUIVALENT TO HD64180) +; 2: Z8S180 - ORIGINAL S-CLASS, REV. K, AKA SL1960, NO ASCI BRG +; 3: Z8S180 - REVISED S-CLASS, REV. N, W/ ASCI BRG +; + LD HL,0 ; L = 0 MEANS Z80 +; +#IF (CPUFAM == CPU_Z180) +; + ; TEST FOR ORIGINAL Z180 USING MLT + LD DE,$0506 ; 5 X 6 + MLT DE ; DE = 30 IF Z180 + LD A,E ; CHECK IF MULTIPLY HAPPENED + CP 30 + JR NZ,HB_CPU1 ; IT IS A Z80 IF != 30 + INC L ; FLAG Z80180 OR BETTER +; + ; TEST FOR OLDER S-CLASS (REV K) + IN0 A,(Z180_CCR) ; SUPPOSEDLY ONLY ON S-CLASS + INC A ; FF -> 0 + JR Z,HB_CPU1 + INC L ; FLAG Z8S180 REV K (SL1960) OR BETTER +; + ; TEST FOR NEWER S-CLASS (REV N) + ; ON OLDER S-CLASS, ASCI TIME CONSTANT REG DOES NOT EXIST + ; AND WILL ALWYAS READ BACK AS $FF + OUT0 (Z180_ASTC1L),D ; D = 0 AT THIS POINT + IN0 A,(Z180_ASTC1L) ; ASCI TIME CONSTANT REG + INC A ; FF -> 0 + JR Z,HB_CPU1 + INC L ; FLAG Z8S180 REV N W/ ASCI BRG +; +#ENDIF +; +HB_CPU1: + LD A,L + LD (HB_CPUTYPE),A +; +#IF (DSRTCENABLE) + CALL DSRTC_PREINIT +#ENDIF +; +#IF (CPUFAM == CPU_Z180) +; + ; AT BOOT, Z180 PHI IS OSC / 2 + LD C,(CPUOSC / 2) / 1000000 + LD DE,(CPUOSC / 2) / 1000 +; +#IF (Z180_CLKDIV >= 1) + LD A,(HB_CPUTYPE) ; GET CPU TYPE + CP 2 ; Z8S180 REV K OR BETTER? + JR C,HB_CPU2 ; IF NOT, NOT POSSIBLE! + ; SET CLOCK DIVIDE TO 1 RESULTING IN FULL XTAL SPEED + LD A,$80 + OUT0 (Z180_CCR),A + ; REFLECT SPEED CHANGE + LD C,CPUOSC / 1000000 + LD DE,CPUOSC / 1000 +#ENDIF + +#IF (Z180_CLKDIV >= 2) + LD A,(HB_CPUTYPE) ; GET CPU TYPE + CP 3 ; Z8S180 REV N OR BETTER? + JR C,HB_CPU2 ; IF NOT, NOT POSSIBLE! + ; SET CPU MULTIPLIER TO 1 RESULTING IN XTAL * 2 SPEED + ; ALSO SET CCR AGAIN BECAUSE OF REPORTS THAT CCR + ; *MUST* BE SET AFTER CMR. + LD A,$80 + OUT0 (Z180_CMR),A ; CPU MULTIPLIER + OUT0 (Z180_CCR),A ; CLOCK DIVIDE + ; REFLECT SPEED CHANGE + LD C,(CPUOSC * 2) / 1000000 + LD DE,(CPUOSC * 2) / 1000 +#ENDIF +; +HB_CPU2: + ; SAVE CPU SPEED IN CONFIG BLOCK + LD A,C + LD (CB_CPUMHZ),A + LD (CB_CPUKHZ),DE +; +#ENDIF +; + DIAG(%00011111) +; ; PERFORM DYNAMIC CPU SPEED DERIVATION ; CALL HB_CPUSPD ; CPU SPEED DETECTION ; -#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4) | (PLATFORM == PLT_RC180)) + CALL DELAY_INIT ; INITIALIZE SPEED COMPENSATED DELAY FUNCTIONS +; +#IF (CPUFAM == CPU_Z180) ; - ; SET DESIRED WAIT STATES + ; SET FINAL DESIRED WAIT STATES LD A,0 + (Z180_MEMWAIT << 6) | (Z180_IOWAIT << 4) OUT0 (Z180_DCNTL),A ; #ENDIF ; - CALL DELAY_INIT ; INITIALIZE SPEED COMPENSATED DELAY FUNCTIONS +#IF (INTMODE == 2) + ; SETUP Z80 IVT AND INT MODE 2 + LD A,HBX_IVT >> 8 ; SETUP HI BYTE OF IVT ADDRESS + LD I,A ; ... AND PLACE IT IN I REGISTER + + #IF (CPUFAM == CPU_Z180) + ; SETUP Z180 IVT + XOR A ; SETUP LO BYTE OF IVT ADDRESS + OUT0 (Z180_IL),A ; ... AND PLACE IN Z180 IL REGISTER + #ENDIF + + IM 2 ; SWITCH TO INT MODE 2 +#ENDIF ; - DIAG(%00011111) +#IF (PLATFORM == PLT_SBC) +; + #IF (HTIMENABLE) ; SIMH TIMER +; + #IF (INTMODE == 1) + LD HL,HB_TIMINT + CALL HB_ADDIM1 ; ADD TO IM1 CALL LIST + #ENDIF + + #ENDIF +; +#ENDIF +; +#IF (KIOENABLE) + LD A,%11111001 ; RESET ALL DEVICES, SET DAISYCHAIN + OUT (KIOBASE+$0E),A ; DO IT +#ENDIF +; +#IF (CTCENABLE) + CALL CTC_PREINIT +#ENDIF +; +#IF (CPUFAM == CPU_Z180) +; + #IF (INTMODE == 2) +; + ; MASK ALL EXTERNAL INTERRUPTS FOR NOW + LD A,$01 ; INT0 ENABLED, INT1-2 DISABLED + OUT0 (Z180_ITC),A ; WRITE TO INT/TRAP CONTROL REGISTER +; + ; SETUP Z180 TIMER0 INTERRUPT VECTOR IN IVT + LD HL,HB_TIMINT + LD (IVT(INT_TIM0)),HL ; Z180 TIMER 0 + + ; SETUP PERIODIC TIMER INTERRUPT ON TIMER 0 + LD HL,(CB_CPUKHZ) ; 50HZ = 18432000 / 20 / 50 / X, SO X = CPU KHZ + OUT0 (Z180_TMDR0L),L ; INITIALIZE TIMER 0 DATA REGISTER + OUT0 (Z180_TMDR0H),H + DEC HL ; RELOAD OCCURS *AFTER* ZERO + OUT0 (Z180_RLDR0L),L ; INITIALIZE TIMER 0 RELOAD REGISTER + OUT0 (Z180_RLDR0H),H + LD A,%00010001 ; ENABLE TIMER0 INT AND DOWN COUNTING + OUT0 (Z180_TCR),A +; + #ENDIF +; +#ENDIF ; ; INITIALIZE HEAP STORAGE ; @@ -856,38 +1253,117 @@ HB_START1: ; BNKCALL ARRIVES HERE, BUT NOW RUNNING IN RAM BANK CALL FILL ; DO IT ; DIAG(%00111111) +#IF 0 ; -; PRE-CONSOLE INITIALIZATION +; TEST DEBUG *************************************************************************************** +; + CALL NEWLINE + CALL REGDMP +; +; TEST DEBUG *************************************************************************************** ; -#IF (ASCIENABLE) - CALL ASCI_PREINIT -#ENDIF -#IF (UARTENABLE) - CALL UART_PREINIT #ENDIF -#IF (SIOENABLE) - CALL SIO_PREINIT +; +; PRE-CONSOLE INITIALIZATION +; + LD A,FORCECON ; CALCULATE PRE-INIT TABLE ; A IS INDEX OF CONSOLE DEVICE ENTRY + RLCA ; ENTRY THAT WE WANT TO ; A IS OFFSET OF CONSOLE DEVICE ENTRY + LD DE,(HB_PCINITTBL) ; EXECUTE FIRST ; DE IS VALUE OF TOP ENTRY + LD HL,HB_PCINITTBL ; HL IS ADDRESS OF TOP OF TABLE + PUSH HL ; PUSH (1) TOP OF TABLE + PUSH DE ; PUSH (2) VALUE OF TOP ENTRY + PUSH HL ; PUSH (3) TOP OF TABLE + CALL ADDHLA ; HL IS ADDRESS OF DESIRED CONSOLE ENTRY + POP DE ; PLACE IT AT THE TOP OF THE ; POP (3) DE IS TOP OF TABLE + PUSH HL ; TABLE BY SWAPPING IT ; PUSH (3) ADDRESS OF DESIRED CONSOLE ENTRY + LDI ; WITH THE FIRST (DUMMY) ; COPY DESIRED ENTRY + LDI ; ENTRY ; ... TO TOP OF TABLE + POP HL ; POP (3) HL IS ADDRESS OF DESIRED CONSOLE ENTRY + POP DE ; POP (2) DE IS VALUE OF ORIGINAL TOP ENTRY + LD (HL),E ; SAVE DE OVER ORIGINAL ENTRY + INC HL + LD (HL),D + LD B,HB_PCINITTBLLEN + POP DE ; POP (1) DE IS ADDRESS OF TOP OF TABLE + +#IF (BT_REC_TYPE != BT_REC_NONE) + LD A,(HB_BOOT_REC) ; IF WE ARE IN RECOVERY MODE + OR A ; POINT TO THE RECOVER MODE + JR Z,NOT_REC_M0 ; INITIALIZATION TABLE + LD B,HB_PCINITRLEN + LD DE,HB_PCINIT_REC +NOT_REC_M0: + #ENDIF -#IF (ACIAENABLE) - CALL ACIA_PREINIT + CALL CALLLIST ; PROCESS THE PRE-INIT CALL TABLE +; +#IF 0 +; +; TEST DEBUG *************************************************************************************** +; + CALL NEWLINE + CALL REGDMP +; +; TEST DEBUG *************************************************************************************** +; #ENDIF ; DIAG(%01111111) + LED($FF) ; -; PRIOR TO THIS POINT, CONSOLE I/O WAS DIRECTED TO HARDWARE (XIO.ASM). +; PRIOR TO THIS POINT, CONSOLE I/O WAS NOT AVAILABLE UNLESS DIRECTED TO DEBUG OUTPUT I.E. XIO ; NOW THAT HBIOS IS READY, SET THE CONSOLE UNIT TO ACTIVATE CONSOLE I/O ; VIA HBIOS. ; XOR A ; INITIALLY, FIRST SERIAL UNIT IS CONSOLE LD (CB_CONDEV),A ; SAVE IT, ACTIVATES CONSOLE ON HBIOS + +#IF (WBWDEBUG == USEMIO) ; OUTPUT ANY CACHED DEBUG TEXT + LD HL,MIOOUTPTR + LD E,(HL) + INC HL + LD D,(HL) + INC HL +NXTMIO: LD A,(HL) + CALL COUT + INC HL + LD A,L + CP E + JR NZ,NXTMIO + LD A,H + CP D + JR NZ,NXTMIO +; CALL WRITESTR ; WRITESTR WILL WORK WILL ONLY PRINT UP TO FIRST $ +#ENDIF +; +#IF 0 +; +; TEST DEBUG *************************************************************************************** +; + CALL NEWLINE2 + PRTS("DEBUG+IVT$") + LD DE,HB_IVT + CALL DUMP_BUFFER +; +; TEST DEBUG *************************************************************************************** +; +#ENDIF ; ; ANNOUNCE HBIOS ; CALL NEWLINE2 PRTX(STR_BANNER) +; +; DISPLAY HBIOS MUTEX ENABLED MESSAGE +; +#IF (HBIOS_MUTEX == TRUE) + CALL NEWLINE + CALL PRTSTRD + .TEXT "HBIOS MUTEX ENABLED$" +#ENDIF ; DIAG(%11111111) -; +; ; IO PORT SCAN ; #IF 0 @@ -917,77 +1393,13 @@ PSCNX .EQU $ + 1 DJNZ PSCN1 #ENDIF ; -; SETUP INTERRUPT VECTORS, AS APPROPRIATE -; -;#IF (INTMODE == 1) -; ; OVERLAY $0038 WITH JP INT_IM1 -; LD A,$C3 ; JP INSTRUCTION -; LD ($0038),A ; INSTALL IT -; LD HL,INT_IM1 ; DESTINATION ADDRESS -; LD ($0039),HL ; INSTALL IT -;#ENDIF -; -#IF (INTMODE == 2) - ; SETUP Z80 IVT AND INT MODE 2 - LD A,HBX_IVT >> 8 ; SETUP HI BYTE OF IVT ADDRESS - LD I,A ; ... AND PLACE IT IN I REGISTER - - #IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4) | (PLATFORM == PLT_RC180)) - ; SETUP Z180 IVT - XOR A ; SETUP LO BYTE OF IVT ADDRESS - OUT0 (Z180_IL),A ; ... AND PLACE IN Z180 IL REGISTER - #ENDIF - - IM 2 ; SWITCH TO INT MODE 2 -#ENDIF - -#IF (PLATFORM == PLT_SBC) -; - #IF (HTIMENABLE) ; SIMH TIMER -; - #IF (INTMODE == 1) - LD HL,HB_TIMINT - CALL HB_ADDIM1 ; ADD TO IM1 CALL LIST - #ENDIF -; - #IF (INTMODE == 2) - ;LD HL,INT_TIMER - ;LD (HBX_IVT),HL - #ENDIF -; - #ENDIF -; -#ENDIF -; -#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4) | (PLATFORM == PLT_RC180)) -; - #IF (INTMODE == 2) -; - ; MASK ALL EXTERNAL INTERRUPTS FOR NOW - ;XOR A ; INT0-2 DISABLED - LD A,$01 ; INT0 ENABLED, INT1-2 DISABLED - OUT0 (Z180_ITC),A ; WRITE TO INT/TRAP CONTROL REGISTER -; - ; SETUP Z180 TIMER0 INTERRUPT VECTOR IN IVT - LD HL,INT_TIMER - LD (HBX_IVT + IVT_TIM0),HL - - ; SETUP PERIODIC TIMER INTERRUPT ON TIMER 0 - LD HL,(CB_CPUKHZ) ; 50HZ = 18432000 / 20 / 50 / X, SO X = CPU KHZ - LD B,0 - LD C,Z180_RLDR0L ; INITIALIZE TIMER 0 RELOAD REGISTER - OUT (C),L - INC C - OUT (C),H - LD C,Z180_TMDR0L ; INITIALIZE TIMER 0 DATA REGISTER - OUT (C),L - INC C - OUT (C),H - LD A,%00010001 ; ENABLE TIMER0 INT AND DOWN COUNTING - OUT0 (Z180_TCR),A -; - #ENDIF -; +#IF 0 +HB_SPDTST: + CALL HB_CPUSPD ; CPU SPEED DETECTION + CALL NEWLINE + LD HL,(CB_CPUKHZ) + CALL PRTD3M ; PRINT AS DECIMAL WITH 3 DIGIT MANTISSA + JR HB_SPDTST #ENDIF ; HB_EI ; INTERRUPTS SHOULD BE OK NOW @@ -996,12 +1408,17 @@ PSCNX .EQU $ + 1 ; CALL NEWLINE2 PRTX(STR_PLATFORM) +; + LD A,(HB_CPUTYPE) ; GET CPU TYPE + LD DE,HB_CPU_STR ; DISPLAY IT + CALL PRTIDXDEA +; PRTS(" @ $") LD HL,(CB_CPUKHZ) CALL PRTD3M ; PRINT AS DECIMAL WITH 3 DIGIT MANTISSA PRTS("MHz$") -#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4) | (PLATFORM == PLT_RC180)) - CALL PC_COMMA +; +#IF (CPUFAM == CPU_Z180) PRTS(" IO=0x$") LD A,Z180_BASE CALL PRTHEXBYTE @@ -1009,10 +1426,8 @@ PSCNX .EQU $ + 1 ; ; DISPLAY CPU CONFIG ; - ;CALL PRTSTRD - ;.TEXT ", $" CALL NEWLINE -#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4) | (PLATFORM == PLT_RC180)) +#IF (CPUFAM == CPU_Z180) LD A,Z180_MEMWAIT #ELSE LD A,0 @@ -1020,7 +1435,7 @@ PSCNX .EQU $ + 1 CALL PRTDECB CALL PRTSTRD .TEXT " MEM W/S, $" -#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4) | (PLATFORM == PLT_RC180)) +#IF (CPUFAM == CPU_Z180) LD A,Z180_IOWAIT + 1 #ELSE LD A,1 @@ -1038,8 +1453,6 @@ PSCNX .EQU $ + 1 ; DISPLAY MEMORY CONFIG ; CALL NEWLINE - ;CALL PRTSTRD - ;.TEXT "MEMORY CONFIG: $" LD HL,ROMSIZE CALL PRTDEC CALL PRTSTRD @@ -1049,24 +1462,33 @@ PSCNX .EQU $ + 1 CALL PRTSTRD .TEXT "KB RAM$" ; +; LOW BATTERY DIAGNOSTIC MESSAGE +; +#IF (BATCOND) + LD A,(HB_BATCOND) + OR A + LD DE,STR_LOWBAT + CALL Z,WRITESTR +#ENDIF +; ; PERFORM DEVICE INITIALIZATION ; CALL NEWLINE + +#IF (BT_REC_TYPE != BT_REC_NONE) + LD A,(HB_BOOT_REC) ; IF WE ARE IN RECOVERY MODE + OR A ; POINT TO THE RECOVER MODE + JR Z,NOT_REC_M1 ; INITIALIZATION TABLE + LD B,HB_INITRLEN + LD DE,HB_INIT_REC + JR IS_REC_M1 +#ENDIF + +NOT_REC_M1: LD B,HB_INITTBLLEN LD DE,HB_INITTBL -INITSYS1: - LD A,(DE) - LD L,A - INC DE - LD A,(DE) - LD H,A - INC DE - PUSH DE - PUSH BC - CALL JPHL - POP BC - POP DE - DJNZ INITSYS1 +IS_REC_M1: + CALL CALLLIST ; ; RECORD HEAP CURB AT THE CURRENT VALUE OF HEAP TOP. HEAP CURB ; MARKS THE POINT IN THE HEAP AFTER WHICH MEMORY IS RELEASED @@ -1089,8 +1511,8 @@ INITSYS1: ; IF PLATFORM HAS A CONFIG JUMPER, CHECK TO SEE IF IT IS JUMPERED. ; IF SO, BYPASS SWITCH TO CRT CONSOLE (FAILSAFE MODE) ; -#IF ((PLATFORM != PLT_N8) & (PLATFORM != PLT_MK4) & (PLATFORM != PLT_RC) & (PLATFORM != PLT_RC180)) - IN A,(RTC) ; RTC PORT, BIT 6 HAS STATE OF CONFIG JUMPER +#IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA) | (PLATFORM == PLT_ZETA2)) + IN A,(RTCIO) ; RTC PORT, BIT 6 HAS STATE OF CONFIG JUMPER BIT 6,A ; BIT 6 HAS CONFIG JUMPER STATE JR Z,INITSYS3 ; Z=SHORTED, BYPASS CONSOLE SWITCH #ENDIF @@ -1131,7 +1553,9 @@ INITSYS3: ;CALL HBX_BNKCPY LD B,BF_SYSSETCPY ; HBIOS FUNC: SETUP BANK COPY LD D,BID_USR ; D = DEST BANK = USER BANK - LD E,BID_USR ; E = SRC BANK = USER BANK + ;LD E,BID_USR ; E = SRC BANK = USER BANK + LD A,(HB_APPBNK) ; GET APP LOAD BANK + LD E,A ; USE AS SOURCE LD HL,$8000 ; HL = COPY LEN = ENTIRE BANK RST 08 ; DO IT LD B,BF_SYSBNKCPY ; HBIOS FUNC: PERFORM BANK COPY @@ -1149,11 +1573,83 @@ INITSYS3: ; RET ; +; CALL A LIST OF ROUTINES POINTED TO BY DE OF LENGTH B. +; +CALLLIST: + LD A,(DE) + LD L,A + INC DE + LD A,(DE) + LD H,A + INC DE + PUSH DE + PUSH BC + CALL JPHL + POP BC + POP DE + DJNZ CALLLIST +CALLDUMMY: + RET + +; +;================================================================================================== +; TABLE OF RECOVERY MODE INITIALIZATION ENTRY POINTS +;================================================================================================== +; +; USE "CALLDUMMY" IF NO ENTRY REQUIRED +; +#IF (BT_REC_TYPE != BT_REC_NONE) +; +HB_PCINIT_REC: + #IF (PLATFORM == PLT_SBC) + .DW UART_PREINIT +; .DW CALLDUMMY + #ENDIF +HB_PCINITRLEN .EQU (($ - HB_PCINIT_REC) / 2) +; +HB_INIT_REC: + #IF (PLATFORM == PLT_SBC) + .DW UART_INIT + .DW MD_INIT + #ENDIF +HB_INITRLEN .EQU (($ - HB_INIT_REC) / 2) +; +#ENDIF +; +;================================================================================================== +; TABLE OF PRE-CONSOLE INITIALIZATION ENTRY POINTS +;================================================================================================== +; +HB_PCINITTBL: + .DW CALLDUMMY ; RESERVED FOR FORCING PRIMARY CONSOLE +#IF (ASCIENABLE) + .DW ASCI_PREINIT +#ENDIF +#IF (UARTENABLE) + .DW UART_PREINIT +#ENDIF +#IF (SIOENABLE) + .DW SIO_PREINIT +#ENDIF +#IF (ACIAENABLE) + .DW ACIA_PREINIT +#ENDIF +#IF (PIO_4P | PIO_ZP) + .DW PIO_PREINIT +#ENDIF +#IF (UFENABLE) + .DW UF_PREINIT +#ENDIF +HB_PCINITTBLLEN .EQU (($ - HB_PCINITTBL) / 2) + ;================================================================================================== ; TABLE OF INITIALIZATION ENTRY POINTS ;================================================================================================== ; HB_INITTBL: +#IF (CTCENABLE) + .DW CTC_INIT +#ENDIF #IF (SPKENABLE) .DW SPK_INIT ; AUDIBLE INDICATOR OF BOOT START #ENDIF @@ -1172,11 +1668,17 @@ HB_INITTBL: #IF (ACIAENABLE) .DW ACIA_INIT #ENDIF +#IF (DSRTCENABLE) + .DW DSRTC_INIT +#ENDIF +#IF (BQRTCENABLE) + .DW BQRTC_INIT +#ENDIF #IF (SIMRTCENABLE) .DW SIMRTC_INIT #ENDIF -#IF (DSRTCENABLE) - .DW DSRTC_INIT +#IF (INTRTCENABLE) + .DW INTRTC_INIT #ENDIF #IF (VDUENABLE) .DW VDU_INIT @@ -1223,6 +1725,12 @@ HB_INITTBL: #IF (PPPENABLE) .DW PPP_INIT #ENDIF +#IF (PIO_4P | PIO_ZP) + .DW PIO_INIT +#ENDIF +#IF (UFENABLE) + .DW UF_INIT +#ENDIF ; HB_INITTBLLEN .EQU (($ - HB_INITTBL) / 2) ; @@ -1308,7 +1816,7 @@ CIO_DISPATCH: LD IY,CIO_TBL ; POINT IY TO START OF DIO TABLE CALL HB_DISPCALL ; GO TO GENERIC API CALL CODE - + POP IY ; RESTORE IY RET ; AND DONE ; @@ -1341,7 +1849,7 @@ CIO_ADDENT: CIO_FNCNT .EQU 7 ; NUMBER OF CIO FUNCS (FOR RANGE CHECK) CIO_MAX .EQU 32 ; UP TO 32 UNITS CIO_SIZ .EQU CIO_MAX * 4 ; EACH ENTRY IS 4 BYTES -; +; .DB CIO_FNCNT ; CIO FUNCTION COUNT (FOR RANGE CHECK) .DB CIO_MAX ; MAX ENTRY COUNT TABLE PREFIX CIO_CNT .DB 0 ; ENTRY COUNT PREFIX @@ -1380,7 +1888,7 @@ DIO_DISPCALL: LD IY,DIO_TBL ; POINT IY TO START OF DIO TABLE CALL HB_DISPCALL ; GO TO GENERIC API CALL CODE - + POP IY ; RESTORE IY RET ; AND DONE ; @@ -1404,27 +1912,270 @@ DIO_ADDENT: DIO_FNCNT .EQU 12 ; NUMBER OF DIO FUNCS (FOR RANGE CHECK) DIO_MAX .EQU 16 ; UP TO 16 UNITS DIO_SIZ .EQU DIO_MAX * 4 ; EACH ENTRY IS 4 BYTES -; +; .DB DIO_FNCNT ; DIO FUNCTION COUNT (FOR RANGE CHECK) .DB DIO_MAX ; MAX ENTRY COUNT TABLE PREFIX DIO_CNT .DB 0 ; ENTRY COUNT PREFIX DIO_TBL .FILL DIO_SIZ,0 ; SPACE FOR ENTRIES ; -;================================================================================================== -; REAL TIME CLOCK DEVICE DISPATCHER -;================================================================================================== +;================================================================================================== +; DISK READ HELPER +;================================================================================================== +; +; IMPLEMENTS MULTI SECTOR READS AND I/O TO/FROM +; BANKED RAM VIA BOUNCE BUFFER +; +; TOS=READ FN ADR +; HL=BUF ADR +; E=SEC COUNT +; D=BUF BANK ID +; +HB_DSKREAD: +; + ; THE ACTUAL SECTOR READ FUNCTION ADDRESS IS ON TOS, SAVE IT + EX (SP),HL ; SAVE HL TO TOS, HL := READ FN ADR + LD (HB_DSKFNADR),HL ; IMBED IN CALL OP BELOW + POP HL ; RECOVER HL +; +#IF (DIAGENABLE) + ; SAVE DISK UNIT NUMBER BIT MASK + LD A,C ; GET DISK UNIT NUMBER + LD B,A ; PUT IN B FOR LOOP COUNTER + INC B ; LOOP ONE EXTRA TIME TO HANDLE UNIT=0 + XOR A ; START WITH ACCUM ZERO + SCF ; ... AND CF SET +HB_DSKREAD0: + RLA ; ROTATE BIT + DJNZ HB_DSKREAD0 ; ... UNTIL IN PROPER LOCATION + LD (HB_DSKBIT),A ; SAVE IT FOR DIAGNOSTICS +#ENDIF +; +#IF 1 + ; CHECK TO SEE IF INTER-BANK I/O NEEDED. + BIT 7,H ; TGT BUF IN UPPER 32K? + JP NZ,HB_DSKIO ; IF SO, NON-BANKED + LD A,D ; GET TGT BANK + CP BID_BIOS ; BIOS BANK? + JP Z,HB_DSKIO ; IF SO, NON-BANKED +#ENDIF +; +#IF 1 + ; RAM BANK RANGE CHECK + LD A,D ; GET TGT BANK + CP BID_RAMN ; BANK IN RANGE 0-N? + CALL NC,PANIC ; IF >N, PANIC +#ENDIF +; + ; SAVE TGT BUF BNK/ADR + LD (HB_IOBUF),HL + LD A,D + LD (HB_IOBNK),A +; + ; SETUP READ AND LOOP COUNT + LD B,E ; SEC LOOP COUNTER + LD C,0 ; SEC COMPLETE COUNTER +; +HB_DSKREAD1: + LD HL,HB_WRKBUF ; USE WORK BUF REAL I/O +; + ; CALL READ FN + CALL HB_DSKFN ; READ ONE SECTOR +; + ; IF FAIL, RETURN ERR + JR NZ,HB_DSKREADX ; BAIL OUT ON ERROR +; + ; BNKCPY SEC DATA TO REAL BANK/BUF & INC BUF ADR + PUSH BC ; SAVE COUNTERS + LD A,(HB_IOBNK) ; DEST BANK + LD (HB_DSTBNK),A ; ... TO PROXY + LD A,BID_BIOS ; SRC BANK + LD (HB_SRCBNK),A ; ... TO PROXY + LD BC,512 ; COPY 512 BYTES (1 SEC) + LD DE,(HB_IOBUF) ; TGT BUFFER ADR + LD HL,HB_WRKBUF ; SOURCE BUFFER + CALL HBX_BNKCPY ; DO BANK COPY + LD (HB_IOBUF),DE ; SAVE UPDATED TGT BUF ADR + POP BC ; RESTORE COUNTERS +; + ; INC READ COUNT + INC C ; BUMP SEC READ COUNT + DJNZ HB_DSKREAD1 ; LOOP AS NEEDED + XOR A ; SIGNAL SUCCESS +; +HB_DSKREADX: + LD HL,(HB_IOBUF) ; NEXT BUF ADR + JR HB_DSKIOX ; DONE +; +;================================================================================================== +; DISK WRITE HELPER +;================================================================================================== +; +; IMPLEMENTS MULTI SECTOR WRITES AND I/O TO/FROM +; BANKED RAM VIA BOUNCE BUFFER +; +; TOS=WRITE FN ADR +; HL=BUF ADR +; E=SEC COUNT +; D=BUF BANK ID +; +HB_DSKWRITE: +; + ; THE ACTUAL SECTOR READ FUNCTION ADDRESS IS ON TOS, SAVE IT + EX (SP),HL ; SAVE HL TO TOS, HL := READ FN ADR + LD (HB_DSKFNADR),HL ; IMBED IN CALL OP BELOW + POP HL ; RECOVER HL +; +#IF (DIAGENABLE) + ; SAVE DISK UNIT NUMBER BIT MASK + LD A,C ; GET DISK UNIT NUMBER + LD B,A ; PUT IN B FOR LOOP COUNTER + INC B ; LOOP ONE EXTRA TIME TO HANDLE UNIT=0 + XOR A ; START WITH ACCUM ZERO + SCF ; ... AND CF SET +HB_DSKWRITE0: + RLA ; ROTATE BIT + DJNZ HB_DSKWRITE0 ; ... UNTIL IN PROPER LOCATION + LD (HB_DSKBIT),A ; SAVE IT FOR DIAGNOSTICS +#ENDIF +; +#IF 1 + ; CHECK TO SEE IF INTER-BANK I/O NEEDED. + BIT 7,H ; TGT BUF IN UPPER 32K? + JP NZ,HB_DSKIO ; IF SO, NON-BANKED + LD A,D ; GET TGT BANK + CP BID_BIOS ; BIOS BANK? + JP Z,HB_DSKIO ; IF SO, NON-BANKED +#ENDIF +; +#IF 1 + ; RAM BANK RANGE CHECK + LD A,D ; GET TGT BANK + CP BID_RAMN ; BANK IN RANGE 0-N? + CALL NC,PANIC ; IF >N, PANIC +#ENDIF +; + ; SAVE TGT BUF BNK/ADR + LD (HB_IOBUF),HL + LD A,D + LD (HB_IOBNK),A +; + ; SETUP WRITE AND LOOP COUNT + LD B,E ; SEC LOOP COUNTER + LD C,0 ; SEC COMPLETE COUNTER +; +HB_DSKWRITE1: + ; BNKCPY SEC DATA TO WORK BANK/BUF & INC BUF ADR + PUSH BC ; SAVE COUNTERS + LD A,BID_BIOS ; DEST BANK + LD (HB_DSTBNK),A ; ... TO PROXY + LD A,(HB_IOBNK) ; SRC BANK + LD (HB_SRCBNK),A ; ... TO PROXY + LD BC,512 ; COPY 512 BYTES (1 SEC) + LD DE,HB_WRKBUF ; TGT BUFFER ADR + LD HL,(HB_IOBUF) ; SOURCE BUFFER + CALL HBX_BNKCPY ; DO BANK COPY + LD (HB_IOBUF),HL ; SAVE UPDATED SRC BUF ADR + POP BC ; RESTORE COUNTERS +; + ; CALL WRITE FN + LD HL,HB_WRKBUF ; WRITE FROM WORK BUFFER + CALL HB_DSKFN ; WRITE ONE SECTOR +; + ; IF FAIL, RETURN ERR + JR NZ,HB_DSKWRITEX ; BAIL OUT ON ERROR +; + ; INC WRITE COUNT + INC C ; BUMP SEC WRITE COUNT + DJNZ HB_DSKWRITE1 ; LOOP AS NEEDED + XOR A ; SIGNAL SUCCESS +; +HB_DSKWRITEX: + LD HL,(HB_IOBUF) ; NEXT BUF ADR + JR HB_DSKIOX ; DONE +; +;================================================================================================== +; NON-BANKED DISK READ/WRITE +;================================================================================================== +; +HB_DSKIO: +; + ; SETUP LOOP COUNT + LD B,E ; SEC LOOP COUNTER + LD C,0 ; SEC COMPLETE COUNTER +; +HB_DSKIO1: + ; CALL READ/WRITE FN + CALL HB_DSKFN ; READ/WRITE ONE SECTOR +; + ; IF FAIL, RETURN ERR + JR NZ,HB_DSKIOX ; BAIL OUT ON ERROR +; + ; INC SECTOR COUNT + INC C ; BUMP SEC READ/WRITE COUNT + DJNZ HB_DSKIO1 ; LOOP AS NEEDED + XOR A ; SIGNAL SUCCESS +; +HB_DSKIOX: + LD E,C ; WRITE COUNT TO E + OR A ; SET RESULT FLAGS + RET ; DONE +; +HB_DSKFN: + PUSH BC ; SAVE COUNTERS +#IF (DIAGENABLE & DIAGDISKIO) + LD A,(HB_DSKBIT) ; LOAD UNIT DISK BIT MASK + OUT (DIAGPORT),A ; DISPLAY ON DIAG LEDS +#ENDIF +#IF (LEDENABLE & LEDDISKIO) + LED($FF) +#ENDIF + LD E,1 ; ONE SECTOR +HB_DSKFNADR .EQU $+1 + CALL PANIC ; READ ONE SECTOR +#IF (DIAGENABLE & DIAGDISKIO) + DIAG(0) ; CLEAR DIAG LEDS +#ENDIF +#IF (LEDENABLE & LEDDISKIO) + LED($00) +#ENDIF + POP BC ; RESTORE COUNTERS + RET ; RETURN +; +HB_DSKBIT .DB 0 ; ACTIVE DISK UNIT +HB_IOBUF .DW 0 ; CURRENT IO BUFFER ADR +HB_IOBNK .DB 0 ; CURRENT IO BUFFER BANK ID +; +;================================================================================================== +; REAL TIME CLOCK DEVICE DISPATCHER +;================================================================================================== +; +; ROUTE CALL TO REAL TIME CLOCK DRIVER +; B: FUNCTION +; +RTC_DISPATCH: + PUSH HL ; SAVE INCOMING HL + LD HL,(RTC_DISPADR) ; + EX (SP),HL + RET +; +RTC_DISPERR: + OR $FF + RET +; +; SET RTC DISPATCH ADDRESS, USED BY RTC DRIVERS DURING INIT +; BC HAS ADDRESS OF DISPATCH ADDRESS +; WILL ONLY SAVE THE FIRST ADDRESS SET +; +RTC_SETDISP: + LD (RTC_DISPADR),BC ; SAVE THE ADDRESS + OR $FF ; FLAG ACTIVE VALUE + LD (RTC_DISPACT),A ; SAVE IT + RET ; AND DONE +; ; -; ROUTE CALL TO REAL TIME CLOCK DRIVER -; B: FUNCTION ; -RTC_DISPATCH: -#IF (SIMRTCENABLE) - JP SIMRTC_DISPATCH -#ENDIF -#IF (DSRTCENABLE) - JP DSRTC_DISPATCH -#ENDIF - CALL PANIC +RTC_DISPADR .DW RTC_DISPERR ; RTC DISPATCH ADDRESS +RTC_DISPACT .DB 0 ; SET WHEN DISPADR SET ; ;================================================================================================== ; VIDEO DISPLAY ADAPTER DEVICE DISPATCHER @@ -1439,7 +2190,7 @@ VDA_DISPATCH: LD IY,VDA_TBL ; POINT IY TO START OF DIO TABLE CALL HB_DISPCALL ; GO TO GENERIC API CALL CODE - + POP IY ; RESTORE IY RET ; AND DONE ; @@ -1573,13 +2324,7 @@ SYS_BNKCPY: LD HL,(HB_CPYLEN) ; HL := COPY LEN (SAVED IN SETCPY) EX (SP),HL ; RESTORE HL & SET (SP) TO COPY LEN POP BC ; BC := COPY LEN -#IF (INTMODE == 1) - DI -#ENDIF CALL HB_BNKCPY -#IF (INTMODE == 1) - EI -#ENDIF XOR A RET ; @@ -1616,6 +2361,8 @@ SYS_GET: JR Z,SYS_GETVDACNT CP BF_SYSGET_TIMER JR Z,SYS_GETTIMER + CP BF_SYSGET_SECS + JR Z,SYS_GETSECS CP BF_SYSGET_BOOTINFO JR Z,SYS_GETBOOTINFO CP BF_SYSGET_CPUINFO @@ -1639,6 +2386,23 @@ SYS_GETTIMER: XOR A RET ; +; GET SECONDS +; RETURNS: +; DE:HL: SECONDS VALUE (32 BIT) +; C: NUM TICKS WITHIN CURRENT SECOND +; +SYS_GETSECS: + LD HL,HB_SECS + HB_DI + CALL LD32 + LD A,(HB_SECTCK) + HB_EI + NEG ; CONVERT DOWNCOUNTER TO UPCOUNTER + ADD A,TICKSPERSEC + LD C,A + XOR A + RET +; ; GET BOOT INFORMATION ; RETURNS: ; L: BOOT BANK ID @@ -1658,7 +2422,8 @@ SYS_GETBOOTINFO: ; DE: CPU SPEED IN KHZ ; SYS_GETCPUINFO: - LD H,0 ; NOT YET DEFINED + LD A,(HB_CPUTYPE) + LD H,A LD A,(CB_CPUMHZ) LD L,A LD DE,(CB_CPUKHZ) @@ -1720,6 +2485,8 @@ SYS_SET: LD A,C ; GET REQUESTED SUB-FUNCTION CP BF_SYSSET_TIMER JR Z,SYS_SETTIMER + CP BF_SYSSET_SECS + JR Z,SYS_SETSECS CP BF_SYSSET_BOOTINFO JR Z,SYS_SETBOOTINFO OR $FF ; SIGNAL ERROR @@ -1749,16 +2516,36 @@ SYS_SETTIMER: XOR A RET ; +; SET SECS +; ON ENTRY: +; DE:HL: SECONDS VALUE (32 BIT) +; +SYS_SETSECS: + LD BC,HB_SECS + HB_DI + CALL ST32 + HB_EI + XOR A + RET +; ; RETURN A BYTE OF MEMORY FROM SPECIFIED BANK ; ENTRY: D=BANK ID, HL=ADDRESS ; RETURN: E=BYTE VALUE ; +; IF WE ARE USING INTERRUPT MODE 1, WE NEED TO PREVENT INTERRUPTS +; BECAUSE THE LOW MEMORY BANK CONTAINING THE IM1 VECTOR WILL PROBABLY +; GET BANKED OUT DURING THE PEEK PROCESSING. +; SYS_PEEK: #IF (INTMODE == 1) + LD A,I DI + PUSH AF #ENDIF CALL HBX_PEEK ; IMPLEMENTED IN PROXY #IF (INTMODE == 1) + POP AF + JP PO,$+4 EI #ENDIF XOR A @@ -1767,12 +2554,20 @@ SYS_PEEK: ; WRITE A BYTE OF MEMORY TO SPECIFIED BANK ; ENTRY: D=BANK ID, HL=ADDRESS IN HBIOS BANK, E=BYTE VALUE ; +; IF WE ARE USING INTERRUPT MODE 1, WE NEED TO PREVENT INTERRUPTS +; BECAUSE THE LOW MEMORY BANK CONTAINING THE IM1 VECTOR WILL PROBABLY +; GET BANKED OUT DURING THE POKE PROCESSING. +; SYS_POKE: #IF (INTMODE == 1) + LD A,I DI + PUSH AF #ENDIF CALL HBX_POKE ; IMPLEMENTED IN PROXY #IF (INTMODE == 1) + POP AF + JP PO,$+4 EI #ENDIF XOR A @@ -1813,7 +2608,7 @@ SYS_INTINFO: ; ROUTINE SHARED BY INT GET/SET. RETURNS ADDRESS OF VECTOR FOR SPECIFIED LIST / TABLE ; POSITION. ZF SET ON RETURN FOR SUCCESS, ELSE ERROR. ; -SYS_INTVECADR: +SYS_INTVECADR: #IF (INTMODE == 0) CALL PANIC ; INVALID FOR INT MODE 0 OR $FF @@ -1834,20 +2629,13 @@ SYS_INTVECADR: OR $FF RET SYS_INTGET1: - OR A - RLA ; HL := (A * 2) FOR IM2 -#IF (INTMODE == 1) - RLA ; ... HL := (A * 4) + 1 FOR IM1 - INC A -#ENDIF + OR A ; CLEAR CARRY + RLA ; ADJUST FOR TABLE ENTRY + RLA ; SIZE OF 4 BYTES + INC A ; BUMP TO ADR FIELD LD H,0 LD L,A -#IF (INTMODE == 1) - LD DE,HB_IM1INT ; DE := START OF CALL LIST -#ENDIF -#IF (INTMODE == 2) - LD DE,HBX_IVT ; DE := START OF VECTOR TABLE -#ENDIF + LD DE,HB_IVT ; DE := START OF VECTOR TABLE ADD HL,DE ; HL := ADR OF VECTOR XOR A ; INDICATE SUCCESS RET @@ -1889,9 +2677,6 @@ SYS_INTSET1: INC HL LD (HL),B ; SAVE MSB EX DE,HL ; HL := PREV VEC -#IF (INTMODE == 2) - LD DE,HBX_INT ; DE := IM2 INT ROUTING ENGINE -#ENDIF XOR A ; SIGNAL SUCCESS RET ; DONE ; @@ -1913,30 +2698,6 @@ CIO_IDLE: ; #IF (INTMODE == 1) ; -; IM1 INTERRUPTS ARRIVE HERE AFTER BANK SWITCH TO HBIOS BANK -; LIST OF IM1 INT CALLS IS BUILT DYNAMICALLY BELOW -; SEE HB_ADDIM1 ROUTINE -; EACH ENTRY WILL LOOK LIKE: -; CALL XXXX ; CALL INT HANDLER -; RET NZ ; RETURN IF HANDLED -; -; NOTE THAT THE LIST IS INITIALLY FILLED WITH CALLS TO HB_BADINT. -; AS THE TABLE IS POPULATED, THE ADDRESS OF HB_BADINT IS OVERLAID -; WITH THE ADDRESS OF A REAL INTERRUPT HANDLER. -; -; THERE IS ROOM FOR 8 ENTRIES PLUS A FINAL CALL TO HB_BADINT. -; -HB_IM1INT: ; IM1 DEVICE INTERRUPT HANDLER CALL LIST - CALL HB_BADINT \ RET NZ - CALL HB_BADINT \ RET NZ - CALL HB_BADINT \ RET NZ - CALL HB_BADINT \ RET NZ - CALL HB_BADINT \ RET NZ - CALL HB_BADINT \ RET NZ - CALL HB_BADINT \ RET NZ - CALL HB_BADINT \ RET NZ - CALL HB_BADINT \ RET NZ -; ; ROUTINE BELOW IS USED TO ADD A NEW VECTOR TO THE IM1 ; CALL LIST ABOVE. ENTER WITH HL=VECTOR ADDRESS IN HBIOS ; @@ -1956,54 +2717,54 @@ HB_ADDIM1: ; HB_IM1CNT .DB 0 ; NUMBER OF ENTRIES IN CALL LIST HB_IM1MAX .DB 8 ; MAX ENTRIES IN CALL LIST -HB_IM1PTR .DW HB_IM1INT ; POINTER FOR NEXT IM1 ENTRY +HB_IM1PTR .DW HB_IVT ; POINTER FOR NEXT IM1 ENTRY ; #ENDIF ; -; TIMER INTERRUPT +; TIMER HANDLER VECTORS +; THESE CAN BE HOOKED AS DESIRED BY DRIVERS ; HB_TIMINT: - ; INCREMENT TICK COUNTER (32 BIT) - LD HL,HB_TICKS ; POINT TO TICK COUNTER - INC (HL) - JR NZ,HB_TIMINT1 - INC HL - INC (HL) - JR NZ,HB_TIMINT1 - INC HL - INC (HL) - JR NZ,HB_TIMINT1 - INC HL - INC (HL) -; -HB_TIMINT1: -; -#IF 0 -; - LD HL,TEMPCNT - DEC (HL) - JR NZ,HB_TIMINT2 - LD (HL),250 -; - LD A,'*' - CALL COUT - JR HB_TIMINT2 +VEC_TICK: + JP HB_TICK ; TICK PROCESSING VECTOR +VEC_SECOND: + JP HB_SECOND ; SECOND PROCESSING VECTOR ; -TEMPCNT .DB 250 +; TIMER HANDLERS ; -#ENDIF +HB_TICK: + ; INCREMENT TICK COUNTER (32 BIT) + LD HL,HB_TICKS ; POINT TO TICK COUNTER + CALL INC32HL + LD HL,HB_SECTCK ; POINT TO SECONDS TICK COUNTER + DEC (HL) ; COUNTDOWN 50 TICKS + JR NZ,HB_TICK1 ; NOT DONE, SKIP AHEAD + LD A,TICKSPERSEC ; 50 TICKS PER SECOND + LD (HL),A ; RESET COUNTDOWN REGISTER + CALL VEC_SECOND ; DO SECONDS PROCESSING VIA VECTOR ; -HB_TIMINT2: +HB_TICK1: ; -#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4) | (PLATFORM == PLT_RC180)) +#IF (CPUFAM == CPU_Z180) ; ACK/RESET Z180 TIMER INTERRUPT IN0 A,(Z180_TCR) IN0 A,(Z180_TMDR0L) #ENDIF +; +#IF (PLATFORM == PLT_EZZ80) + ; PULSE WATCHDOG + OUT (WDOGIO),A ; VALUE IS IRRELEVANT +#ENDIF ; OR $FF ; NZ SET TO INDICATE INT HANDLED RET ; +HB_SECOND: + ; INCREMENT SECONDS COUNTER + LD HL,HB_SECS ; POINT TO SECONDS COUNTER + JP INC32HL ; INCREMENT AND RETURN + +; ; BAD INTERRUPT HANDLER ; HB_BADINT: @@ -2012,15 +2773,21 @@ HB_BADINT: LD HL,HB_BADINTCNT INC (HL) LD A,(HL) - OUT (DIAGP),A + OUT (DIAGPORT),A OR $FF RET HB_BADINTCNT .DB 0 #ENDIF ; *DEBUG* CALL NEWLINE2 - PRTS("+++ BAD INT: $") - CALL _REGDMP + PRTS("+++ BAD INT $") + LD A,L + RRCA + RRCA + CALL PRTHEXBYTE + PRTS("H: $") + + CALL XREGDMP ;CALL CONTINUE OR $FF ; SIGNAL INTERRUPT HANDLED RET @@ -2047,10 +2814,12 @@ HB_DISPCALL: JR NC,HB_DISPERR ; HANDLE FN NUM OUT OF RANGE ERROR ; BUMP IY TO ACTUAL XXX_TBL ENTRY FOR INCOMING UNIT INDEX + PUSH BC ; SAVE BC LD B,0 ; MSB IS ALWAYS ZERO RLC C ; MULTIPLY UNIT INDEX RLC C ; ... BY 4 FOR TABLE ENTRY OFFSET ADD IY,BC ; SET IY TO ENTRY ADDRESS + POP BC ; RESTORE BC ; DERIVE DRIVER FUNC ADR TO CALL PUSH HL ; SAVE INCOMING HL @@ -2172,6 +2941,23 @@ HB_TMPREF .DW 0 ; DEVICE DRIVERS ;================================================================================================== ; +#IF (DSRTCENABLE) +ORG_DSRTC .EQU $ + #INCLUDE "dsrtc.asm" +SIZ_DSRTC .EQU $ - ORG_DSRTC + .ECHO "DSRTC occupies " + .ECHO SIZ_DSRTC + .ECHO " bytes.\n" +#ENDIF +; +#IF (BQRTCENABLE) +ORG_BQRTC .EQU $ + #INCLUDE "bqrtc.asm" +SIZ_BQRTC .EQU $ - ORG_BQRTC + .ECHO "BQRTC occupies " + .ECHO SIZ_BQRTC + .ECHO " bytes.\n" +#ENDIF #IF (SIMRTCENABLE) ORG_SIMRTC .EQU $ #INCLUDE "simrtc.asm" @@ -2181,12 +2967,12 @@ SIZ_SIMRTC .EQU $ - ORG_SIMRTC .ECHO " bytes.\n" #ENDIF ; -#IF (DSRTCENABLE) -ORG_DSRTC .EQU $ - #INCLUDE "dsrtc.asm" -SIZ_DSRTC .EQU $ - ORG_DSRTC - .ECHO "DSRTC occupies " - .ECHO SIZ_DSRTC +#IF (INTRTCENABLE) +ORG_INTRTC .EQU $ + #INCLUDE "intrtc.asm" +SIZ_INTRTC .EQU $ - ORG_INTRTC + .ECHO "INTRTC occupies " + .ECHO SIZ_INTRTC .ECHO " bytes.\n" #ENDIF ; @@ -2271,24 +3057,47 @@ SIZ_NEC .EQU $ - ORG_NEC .ECHO " bytes.\n" #ENDIF ; -#IF (CVDUENABLE | VGAENABLE) -ORG_FONTHI .EQU $ - #INCLUDE "font_hi.asm" -SIZ_FONTHI .EQU $ - ORG_FONTHI - .ECHO "FONTHI occupies " - .ECHO SIZ_FONTHI - .ECHO " bytes.\n" +; FONTS AREA +; +ORG_FONTS .EQU $ +; + .ECHO "FONTS" +; +#IFDEF USEFONT8X8 +FONT8X8: + #IF USELZSA2 + #INCLUDE "font8x8c.asm" + #ELSE + #INCLUDE "font8x8u.asm" + #ENDIF + .ECHO " 8X8" #ENDIF ; -#IF (TMSENABLE) -ORG_FONTTMS .EQU $ - #INCLUDE "font_tms.asm" -SIZ_FONTTMS .EQU $ - ORG_FONTTMS - .ECHO "FONTTMS occupies " - .ECHO SIZ_FONTTMS - .ECHO " bytes.\n" +#IFDEF USEFONT8X11 +FONT8X11: + #IF USELZSA2 + #INCLUDE "font8x11c.asm" + #ELSE + #INCLUDE "font8x11u.asm" + #ENDIF + .ECHO " 8X11" +#ENDIF +; +#IFDEF USEFONT8X16 +FONT8X16: + #IF USELZSA2 + #INCLUDE "font8x16c.asm" + #ELSE + #INCLUDE "font8x16u.asm" + #ENDIF + .ECHO " 8X16" #ENDIF ; +SIZ_FONTS .EQU $ - ORG_FONTS + .ECHO " occupy " + .ECHO SIZ_FONTS + .ECHO " bytes.\n" +; #IF (CVDUENABLE | VGAENABLE) ORG_KBD .EQU $ #INCLUDE "kbd.asm" @@ -2333,7 +3142,7 @@ SIZ_MD .EQU $ - ORG_MD .ECHO SIZ_MD .ECHO " bytes.\n" #ENDIF - +; #IF (FDENABLE) ORG_FD .EQU $ #INCLUDE "fd.asm" @@ -2342,7 +3151,7 @@ SIZ_FD .EQU $ - ORG_FD .ECHO SIZ_FD .ECHO " bytes.\n" #ENDIF - +; #IF (RFENABLE) ORG_RF .EQU $ #INCLUDE "rf.asm" @@ -2351,7 +3160,7 @@ SIZ_RF .EQU $ - ORG_RF .ECHO SIZ_RF .ECHO " bytes.\n" #ENDIF - +; #IF (IDEENABLE) ORG_IDE .EQU $ #INCLUDE "ide.asm" @@ -2360,7 +3169,7 @@ SIZ_IDE .EQU $ - ORG_IDE .ECHO SIZ_IDE .ECHO " bytes.\n" #ENDIF - +; #IF (PPIDEENABLE) ORG_PPIDE .EQU $ #INCLUDE "ppide.asm" @@ -2369,7 +3178,7 @@ SIZ_PPIDE .EQU $ - ORG_PPIDE .ECHO SIZ_PPIDE .ECHO " bytes.\n" #ENDIF - +; #IF (SDENABLE) ORG_SD .EQU $ #INCLUDE "sd.asm" @@ -2378,7 +3187,7 @@ SIZ_SD .EQU $ - ORG_SD .ECHO SIZ_SD .ECHO " bytes.\n" #ENDIF - +; #IF (HDSKENABLE) ORG_HDSK .EQU $ #INCLUDE "hdsk.asm" @@ -2387,7 +3196,7 @@ SIZ_HDSK .EQU $ - ORG_HDSK .ECHO SIZ_HDSK .ECHO " bytes.\n" #ENDIF - +; #IF (TERMENABLE) ORG_TERM .EQU $ #INCLUDE "term.asm" @@ -2397,6 +3206,7 @@ SIZ_TERM .EQU $ - ORG_TERM .ECHO " bytes.\n" #ENDIF ; +;#IF (SPKENABLE & DSRTCENABLE) #IF (SPKENABLE) ORG_SPK .EQU $ #INCLUDE "spk.asm" @@ -2415,71 +3225,101 @@ SIZ_AY .EQU $ - ORG_AY .ECHO " bytes.\n" #ENDIF ; +#IF (PIO_4P | PIO_ZP | PPI_SBC) +ORG_PIO .EQU $ + #INCLUDE "pio.asm" +SIZ_PIO .EQU $ - ORG_PIO + .ECHO "PIO occupies " + .ECHO SIZ_PIO + .ECHO " bytes.\n" +#ENDIF +; +#IF (UFENABLE) +ORG_UF .EQU $ + #INCLUDE "uf.asm" +SIZ_UF .EQU $ - ORG_UF + .ECHO "UF occupies " + .ECHO SIZ_UF + .ECHO " bytes.\n" +#ENDIF +#IF (CTCENABLE) +ORG_CTC .EQU $ + #INCLUDE "ctc.asm" +SIZ_CTC .EQU $ - ORG_CTC + .ECHO "CTC occupies " + .ECHO SIZ_CTC + .ECHO " bytes.\n" +#ENDIF +; #DEFINE USEDELAY #INCLUDE "util.asm" #INCLUDE "time.asm" #INCLUDE "bcd.asm" #INCLUDE "decode.asm" -;#INCLUDE "xio.asm" +#INCLUDE "encode.asm" +; +#IF (WBWDEBUG == USEXIO) +#INCLUDE "xio.asm" +#ENDIF +#IF (WBWDEBUG == USEMIO) +#INCLUDE "mio.asm" +#ENDIF ; #IF (DSKYENABLE) #DEFINE DSKY_KBD #INCLUDE "dsky.asm" #ENDIF ; +; INCLUDE LZSA2 decompression engine if required. +; +#IF ((VGAENABLE | CVDUENABLE | TMSENABLE) & USELZSA2) +#INCLUDE "unlzsa2s.asm" +#ENDIF +; ; DETECT CPU SPEED USING DS-1302 RTC ; HB_CPUSPD: ; #IF (DSRTCENABLE) ; - CALL DSRTC_TSTCLK ; IS CLOCK RUNNING? - JR Z,HB_CPUSPD1 ; YES, CONTINUE - ; MAKE SURE CLOCK IS RUNNING - LD HL,DSRTC_TIMDEF - CALL DSRTC_TIM2CLK - LD HL,DSRTC_BUF - CALL DSRTC_WRCLK - CALL DSRTC_TSTCLK ; NOW IS CLOCK RUNNING? - RET NZ + LD A,(DSRTC_STAT) ; GET RTC STATUS + OR A ; SET FLAGS + RET NZ ; NOT ZERO IS ERROR ; HB_CPUSPD1: -; LD B,8 -;HB_CPUSPDX: -; PUSH BC +#IF (CPUFAM == CPU_Z180) + ; USE MEM W/S = 2 AND I/O W/S = 3 FOR TEST + IN0 A,(Z180_DCNTL) + PUSH AF + LD A,$B0 + ;LD A,$F0 + OUT0 (Z180_DCNTL),A +#ENDIF ; WAIT FOR AN INITIAL TICK TO ALIGN, THEN WAIT ; FOR SECOND TICK AND TO GET A FULL ONE SECOND LOOP COUNT - CALL HB_RDSEC ; GET SECONDS LD (HB_CURSEC),A ; AND INIT CURSEC CALL HB_WAITSEC ; WAIT FOR SECONDS TICK LD (HB_CURSEC),A ; SAVE NEW VALUE CALL HB_WAITSEC ; WAIT FOR SECONDS TICK - -; PUSH DE -; POP BC -; CALL NEWLINE -; CALL PRTHEXWORD - -; POP BC -; DJNZ HB_CPUSPDX +; +#IF (CPUFAM == CPU_Z180) + ; RESTORE W/S SETTINGS FROM BEFORE TEST + POP AF + OUT0 (Z180_DCNTL),A +#ENDIF ; LD A,H OR L RET Z ; FAILURE, USE DEFAULT CPU SPEED - ; ; MOVE LOOP COUNT TO HL PUSH DE POP HL ; ; TIMES 4 FOR CPU SPEED IN KHZ - RES 0,L ; GRANULARITY -#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4) | (PLATFORM == PLT_RC180)) - SLA L - RL H -#ENDIF +; RES 0,L ; GRANULARITY SLA L RL H SLA L @@ -2498,60 +3338,47 @@ HB_WAITSEC: ; RETURN SECS VALUE IN A, LOOP COUNT IN DE LD DE,0 ; INIT LOOP COUNTER HB_WAITSEC1: -#IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA) | (PLATFORM == PLT_ZETA2)| (PLATFORM == PLT_RC)) +; +#IF (CPUFAM == CPU_Z80) ; LOOP TARGET IS 4000 T-STATES, SO CPU FREQ IN KHZ = LOOP COUNT * 4 CALL DLY32 - CALL DLY8 - CALL DLY2 - JP $ + 3 ; 10 TSTATES - JP $ + 3 ; 10 TSTATES - JP $ + 3 ; 10 TSTATES - JP $ + 3 ; 10 TSTATES - ;LD A,R ; 9 TSTATES - INC BC ; 6 TSTATES - ;LD A,(BC) ; 7 TSTATES - ;NOP ; 4 TSTATES - NOP ; 4 TSTATES -#ENDIF - -#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4) | (PLATFORM == PLT_RC180)) - ; LOOP TARGET IS 8000 T-STATES, SO CPU FREQ IN KHZ = LOOP COUNT * 8 - ;CALL DLY64 - CALL DLY32 CALL DLY16 - CALL DLY8 - CALL DLY4 + CALL DLY1 ; 27 TSTATES + SBC HL,HL ; 15 TSTATES + SBC HL,HL ; 15 TSTATES + INC HL ; 6 TSTATES + INC HL ; 6 TSTATES +#ENDIF +; +#IF (CPUFAM == CPU_Z180) + ; LOOP TARGET IS 4000 T-STATES, SO CPU FREQ IN KHZ = LOOP COUNT * 4 CALL DLY2 - CALL DLY1 ; CALL (25TS) & RET (18TS) = 43TS - OR A ; 7 TSTATES - OR A ; 7 TSTATES - ;OR A ; 7 TSTATES - ;OR A ; 7 TSTATES - NOP ; 6 TSTATES - ;NOP ; 6 TSTATES - ;NOP ; 6 TSTATES - ;NOP ; 6 TSTATES - ;NOP ; 6 TSTATES + ADD IX,BC ; 10 + 4 = 14 TSTATES + NOP ; 5 TSTATES + NOP ; 5 TSTATES + NOP ; 5 TSTATES + NOP ; 5 TSTATES #ENDIF ; - PUSH DE + PUSH DE ; SAVE COUNTER CALL HB_RDSEC ; GET SECONDS - POP DE + POP DE ; RESTORE COUNTER INC DE ; BUMP COUNTER LD HL,HB_CURSEC ; POINT TO COMP VALUE CP (HL) ; TEST FOR CHANGE RET NZ ; DONE IF TICK OCCURRED - LD A,D ; CHECK HL + LD A,D ; CHECK DE OR E ; ... FOR OVERFLOW RET Z ; TIMEOUT, SOMETHING IS WRONG JR HB_WAITSEC1 ; LOOP ; HB_RDSEC: ; READ SECONDS BYTE INTO A - LD C,$81 ; SECONDS REGISTER + LD E,$81 ; SECONDS REGISTER CALL DSRTC_CMD ; SEND THE COMMAND CALL DSRTC_GET ; READ THE REGISTER CALL DSRTC_END ; FINISH IT + LD A,E ; VALUE TO A RET ; #ELSE @@ -2596,7 +3423,6 @@ PRTD3M2: CALL COUT PRTD3M3: RET -; ;================================================================================================== ; DISPLAY SUMMARY OF ATTACHED UNITS/DEVICES ;================================================================================================== @@ -2606,58 +3432,38 @@ PRTSUM: LD DE,PS_STRHDR ; POINT TO HEADER CALL WRITESTR ; PRINT IT ; - ; PRINT DISK DEVICES - LD B,BF_SYSGET ; FUNC: SYSTEM INFO GET - LD C,BF_SYSGET_DIOCNT ; SUBFUNC: DISK UNIT COUNT - RST 08 ; E := DISK UNIT COUNT - LD B,E ; MOVE TO B FOR LOOP COUNT - LD A,E ; MOVE TO ACCUM - OR A ; SET FLAGS - JR Z,PRTSUM1A ; IF NONE, BYPASS - LD C,0 ; C WILL BE UNIT INDEX -PRTSUM1: - PUSH BC ; SAVE LOOP CONTROL - CALL PS_DISK ; PRINT DISK INFO - POP BC ; RESTORE LOOP CONTROL - INC C ; BUMP DISK UNIT INDEX - DJNZ PRTSUM1 ; LOOP THRU ALL DISK DEVICES + LD C,BF_SYSGET_CIOCNT ; CHARACTER DEVICES + LD HL,PS_SERIAL + CALL PRT_ALLD ; -PRTSUM1A: - ; PRINT SERIAL DEVICES - LD B,BF_SYSGET ; FUNC: SYSTEM INFO GET - LD C,BF_SYSGET_CIOCNT ; SUBFUNC: SERIAL UNIT COUNT - RST 08 ; E := SERIAL UNIT COUNT - LD B,E ; MOVE TO B FOR LOOP COUNT - LD A,E ; MOVE TO ACCUM - OR A ; SET FLAGS - JR Z,PRTSUM2A ; IF NONE, BYPASS - LD C,0 ; C WILL BE UNIT INDEX -PRTSUM2: - PUSH BC ; SAVE LOOP CONTROL - CALL PS_SERIAL ; PRINT SERIAL INFO - POP BC ; RESTORE LOOP CONTROL - INC C ; BUMP SERIAL UNIT INDEX - DJNZ PRTSUM2 ; LOOP THRU ALL SERIAL DEVICES + LD C,BF_SYSGET_DIOCNT ; DISK DRIVES + LD HL,PS_DISK + CALL PRT_ALLD + + LD C,BF_SYSGET_VDACNT ; VIDEO DEVICES + LD HL,PS_VIDEO + CALL PRT_ALLD + RET ; -PRTSUM2A: - ; PRINT VIDEO DEVICES +PRT_ALLD: LD B,BF_SYSGET ; FUNC: SYSTEM INFO GET - LD C,BF_SYSGET_VDACNT ; SUBFUNC: VIDEO UNIT COUNT - RST 08 ; E := SERIAL UNIT COUNT + RST 08 ; E := UNIT COUNT LD B,E ; MOVE TO B FOR LOOP COUNT LD A,E ; MOVE TO ACCUM OR A ; SET FLAGS - JR Z,PRTSUM3A ; IF NONE, BYPASS + RET Z ; IF NONE, JUST RETURN LD C,0 ; C WILL BE UNIT INDEX -PRTSUM3: +PRT_ALLD1: PUSH BC ; SAVE LOOP CONTROL - CALL PS_VIDEO ; PRINT VIDEO INFO + PUSH DE + PUSH HL + CALL JPHL ; CALL THE ROUTINE PASSED IN HL + POP HL + POP DE POP BC ; RESTORE LOOP CONTROL - INC C ; BUMP VIDEO UNIT INDEX - DJNZ PRTSUM3 ; LOOP THRU ALL VIDEO DEVICES -; -PRTSUM3A: - RET ; DONE + INC C ; BUMP UNIT INDEX + DJNZ PRT_ALLD1 ; LOOP THRU ALL DEVICES + RET ; ; PRINT ONE LINE DISK UNIT/DEVICE INFO, DISK UNIT INDEX IN C ; @@ -2667,8 +3473,10 @@ PS_DISK: ; UNIT COLUMN PRTS("Disk $") LD A,C ; MOVE UNIT NUM TO A - CALL PRTDECB ; PRINT IT, ASSUME SINGLE DIGIT - PRTS(" $") ; PAD TO NEXT COLUMN + CALL PRTDECB ; PRINT IT + CP 10 ; CHECK FOR MULTIPLE DIGITS + CALL C,PC_SPACE ; EXTRA SPACE IF NEEDED + PRTS(" $") ; PAD TO NEXT COLUMN ; ; DEVICE COLUMN LD B,BF_DIODEVICE ; FUNC=GET DEVICE INFO, UNIT NUM STILL IN C @@ -2757,51 +3565,19 @@ PS_PRTDC1: ; PS_PRTDC2: LD C,E ; ATTRIBUTE TO C FOR SAFE KEEPING - ; PRINT FLOPPY TYPE - LD A,C ; ATTRIBUTE TO ACCUM - RLCA ; ISOLATE FORM FACTOR BITS - RLCA - RLCA - AND $03 - LD DE,PS_FLP8 ; ASSUME 8" - CP 0 - JR Z,PS_PRTDC2A - LD DE,PS_FLP5 ; ASSUME 5.25" - CP 1 - JR Z,PS_PRTDC2A - LD DE,PS_FLP3 ; ASSUME 3.5" - CP 2 - JR Z,PS_PRTDC2A - LD DE,PS_FLPN ; ASSUME OTHER" -PS_PRTDC2A: - CALL WRITESTR - ; PRINT FLOPPY SIDES - LD A,C ; ATTRIBUTE TO ACCUM - LD DE,PS_FLPSS ; ASSUME SINGLE SIDED - BIT 4,A ; DS? - JR Z,PS_PRTDC2B - LD DE,PS_FLPDS ; DOUBLE SIDED -PS_PRTDC2B: - CALL WRITESTR - ; PRINT FLOPPY DENSITY - LD A,C ; ATTRIBUTE TO ACCUM - RRCA ; ISOLATE DENSITY BITS - RRCA - AND $03 - LD DE,PS_FLPSD ; SINGLE DENSITY - CP 0 - JR Z,PS_PRTDC2C - LD DE,PS_FLPDD ; DOUBLE DENSITY - CP 1 - JR Z,PS_PRTDC2C - LD DE,PS_FLPHD ; HIGH DENSITY - CP 2 - JR Z,PS_PRTDC2C - LD DE,PS_FLPED ; EXTENDED DENSITY - CP 3 - JR Z,PS_PRTDC2C -PS_PRTDC2C: - CALL WRITESTR +; + LD A,01100000B ; DISPLAY FORM FACTOR + LD DE,PS_FLP_FSTR ; WHICH IS DEFINED IN + CALL PRTIDXMSK ; BITS 5 AND 6. +; + LD A,00010000B ; DISPLAY SIDES + LD DE,PS_FLP_SSTR ; WHICH IS DEFINED + CALL PRTIDXMSK ; IN BIT 4 +; + LD A,00001100B ; DISPLAY DENSITY + LD DE,PS_FLP_DSTR ; WHICH IS DEFINED IN + CALL PRTIDXMSK ; BITS 2 AND 3. +; CALL PC_COMMA PRTS("CHS$") ; FOR NOW, WE ASSUME HARD DISK DOES LBA ; @@ -2813,10 +3589,10 @@ PS_SERIAL: PUSH BC ; SAVE UNIT INDEX FOR LATER ; ; UNIT COLUMN - PRTS("Serial $") + PRTS("Char $") LD A,C ; MOVE UNIT NUM TO A CALL PRTDECB ; PRINT IT, ASSUME SINGLE DIGIT - PRTS(" $") ; PAD TO NEXT COLUMN + PRTS(" $") ; PAD TO NEXT COLUMN ; ; DEVICE COLUMN LD B,BF_CIODEVICE ; FUNC=GET DEVICE INFO, UNIT NUM STILL IN C @@ -2834,9 +3610,12 @@ PS_SERIAL: CALL NEWLINE RET ; -; PRINT SERIAL TYPE (SERIAL ATTRIBUTE IN E) +; PRINT CHARACTER TYPE (SERIAL ATTRIBUTE IN E) ; PS_PRTST: + LD HL,PS_STPPT + BIT 6,C + JR NZ,PS_PRTST1 ; PARALLEL TYPE? LD HL,PS_STRS232 ; ASSUME RS-232 BIT 7,C ; 0=RS-232, 1=TERMINAL JR Z,PS_PRTST1 ; HANDLE TERMINAL TYPE @@ -2852,8 +3631,11 @@ PS_PRTST1: ; PRINT SERIAL CONFIG (UNIT IN E, ATTRIBUTE IN C) ; PS_PRTSC: + BIT 6,C ; PARALLEL TYPE? + JR NZ,PSPRTPC0 + BIT 7,C ; 0=RS-232, 1=TERMINAL - JR NZ,PS_PRTSC1 ; PRINT TERMINAL CONFIG + JP NZ,PS_PRTSC1 ; PRINT TERMINAL CONFIG ; ; PRINT RS-232 CONFIG LD B,BF_CIOQUERY ; HBIOS FUNC: GET CIO CONFIG @@ -2912,10 +3694,26 @@ PS_PRTSC0: ; RET ; +PSPRTPC0: + LD B,BF_CIOQUERY ; HBIOS FUNC: GET CIO CONFIG + LD C,E ; SET PARALLEL UNIT NUM + RST 08 ; DE:HL := I/O SETTING + LD A,D ; TEST FOR $FF + AND E + INC A ; SET Z IF DE == $FF + JP Z,PS_PRTNUL ; $FF == NO CONFIG DEFINED +; +PS_PRTPC0: + LD C,E ; DISPLAY PIO TYPE + LD A,11000000B ; WHICH IS DEFINE BY + LD DE,PIO_MODE_STR ; BITS 6 AND 7 + JP PRTIDXMSK +; RET ; TRICK RETURN +; PS_PRTSC1: ; PRINT TERMINAL CONFIG LD A,C ; GET ATTRIBUTE VALUE - CP $FF ; NO ATTACHED VDA + CP $BF ; NO ATTACHED VDA JR Z,PS_PRTSC2 PRTS("Video $") ; FORMATTING AND $0F ; ISOLATE VIDEO UNIT NUM @@ -3041,7 +3839,10 @@ PS_PAD1: DJNZ PS_PAD1 RET ; -; +HB_CPU_STR: .TEXT " Z80$" ; HB_STRZ80 + .TEXT " Z80180$" ; HB_STRZ180 + .TEXT " Z8S180-K$" ; HB_STRZS180K + .TEXT " Z8S180-N$" ; HB_STRZS180N ; PS_STRNUL .TEXT "--$" ; DISPLAY STRING FOR NUL VALUE ; @@ -3080,24 +3881,24 @@ PS_DTOTHER .TEXT "???$" ; ; FLOPPY ATTRIBUTE STRINGS ; -PS_FLP8 .TEXT "8\",$" -PS_FLP5 .TEXT "5.25\",$" -PS_FLP3 .TEXT "3.5\",$" -PS_FLPN .TEXT "???\",$" +PS_FLP_FSTR: .TEXT "8\",$" ; PS_FLP8 + .TEXT "5.25\",$" ; PS_FLP5 + .TEXT "3.5\",$" ; PS_FLP3 + .TEXT "???\",$" ; PS_FLPN ; -PS_FLPSS .TEXT "SS/$" -PS_FLPDS .TEXT "DS/$" +PS_FLP_SSTR: .TEXT "SS/$" ; PS_FLPSS + .TEXT "DS/$" ; PS_FLPDS ; -PS_FLPSD .TEXT "SD$" -PS_FLPDD .TEXT "DD$" -PS_FLPHD .TEXT "HD$" -PS_FLPED .TEXT "ED$" +PS_FLP_DSTR: .TEXT "SD$" ; PS_FLPSD + .TEXT "DD$" ; PS_FLPDD + .TEXT "HD$" ; PS_FLPHD + .TEXT "ED$" ; PS_FLPED ; -; SERIAL DEVICE STRINGS +; CHARACTER DEVICE STRINGS ; PS_SDSTRREF: - .DW PS_SDUART, PS_SDASCI, PS_SDTERM, - .DW PS_SDPRPCON, PS_SDPPPCON, PS_SDSIO, PS_SDACIA + .DW PS_SDUART, PS_SDASCI, PS_SDTERM + .DW PS_SDPRPCON, PS_SDPPPCON, PS_SDSIO, PS_SDACIA, PS_SDPIO,PS_SDUF ; PS_SDUART .TEXT "UART$" PS_SDASCI .TEXT "ASCI$" @@ -3106,17 +3907,23 @@ PS_SDPRPCON .TEXT "PRPCON$" PS_SDPPPCON .TEXT "PPPCON$" PS_SDSIO .TEXT "SIO$" PS_SDACIA .TEXT "ACIA$" +PS_SDPIO .TEXT "PORT$" +PS_SDUF .TEXT "UF$" ; -; SERIAL TYPE STRINGS +; CHARACTER SUB TYPE STRINGS ; PS_STRS232 .TEXT "RS-232$" PS_STTERM .TEXT "Terminal$" +PS_STPPT .TEXT "Parallel$" ; PS_STPARMAP .DB "NONENMNS" - ; -; SERIAL TYPE STRINGS +; PARALLEL TYPE STRINGS ; +PIO_MODE_STR: .TEXT "Output$" + .TEXT "Input$" + .TEXT "Bidirectional$" + .TEXT "BitCtrl$" ; ; VIDEO DEVICE STRINGS ; @@ -3133,10 +3940,6 @@ PS_VDVGA .TEXT "VGA$" ; PS_VTCRT .TEXT "CRT$" ; -; VIDEO CONFIG STRINGS -; -; -; ; 0 1 2 3 4 5 6 7 ; 01234567890123456789012345678901234567890123456789012345678901234567890123456789 PS_STRHDR .TEXT "Unit Device Type Capacity/Mode\r\n" @@ -3159,7 +3962,7 @@ COUT: LD E,A ; TEMPORARILY STASH OUTPUT CHAR IN E LD A,(CB_CONDEV) ; GET CONSOLE UNIT BYTE CP $FF ; TEST FOR $FF (HBIOS NOT READY) - JR Z,COUT1 ; IF NOT READY, USE XIO + JR Z,COUT1 ; IF NOT READY, TRY DEBUG OUTPUT ; ; USE HBIOS LD C,A ; CONSOLE UNIT TO C @@ -3168,9 +3971,16 @@ COUT: JR COUT2 ; CONTINUE ; COUT1: - ;; USE XIO - ;LD A,E ; GET OUTPUT CHAR BACK TO ACCUM - ;CALL XIO_OUTC ; OUTPUT VIA XIO +; +#IF (WBWDEBUG == USEXIO) + LD A,E ; GET OUTPUT CHAR BACK TO ACCUM + CALL XIO_OUTC ; OUTPUT VIA XIO +#ENDIF +; +#IF (WBWDEBUG == USEMIO) + LD A,E + CALL MIO_OUTC ; OUTPUT VIA MIO +#ENDIF ; COUT2: ; RESTORE ALL REGISTERS @@ -3190,7 +4000,7 @@ CIN: ; LD A,(CB_CONDEV) ; GET CONSOLE UNIT BYTE CP $FF ; TEST FOR $FF (HBIOS NOT READY) - JR Z,CIN1 ; IF NOT READY, USE XIO + JR Z,CIN1 ; IF NOT READY, TRY DEBUG INPUT ; ; USE HBIOS LD C,A ; CONSOLE UNIT TO C @@ -3200,8 +4010,14 @@ CIN: JR CIN2 ; CONTINUE ; CIN1: - ;; USE XIO - ;CALL XIO_INC ; GET CHAR +; +#IF (WBWDEBUG == USEXIO) + CALL XIO_INC ; GET CHAR +#ENDIF +; +#IF (WBWDEBUG == USEMIO) + CALL MIO_INC ; GET CHAR +#ENDIF ; CIN2: ; @@ -3221,7 +4037,7 @@ CST: ; LD A,(CB_CONDEV) ; GET CONSOLE UNIT BYTE CP $FF ; TEST FOR $FF (HBIOS NOT READY) - JR Z,CST1 ; IF NOT READY, USE XIO + JR Z,CST1 ; IIF NOT READY, TRY DEBUG DEBUG STATUS ; ; USE HBIOS LD C,A ; CONSOLE UNIT TO C @@ -3230,8 +4046,14 @@ CST: JR CST2 ; CONTINUE ; CST1: - ;; USE XIO - ;CALL XIO_IST ; GET STATUS +; +#IF (WBWDEBUG == USEXIO) + CALL XIO_IST ; GET STATUS +#ENDIF +; +#IF (WBWDEBUG == USEMIO) + CALL MIO_IST ; GET STATUS +#ENDIF ; CST2: ; RESTORE REGISTERS (AF IS OUTPUT) @@ -3287,16 +4109,31 @@ IDLECOUNT .DB 0 HEAPCURB .DW 0 ; MARK HEAP ADDRESS AFTER INITIALIZATION ; HB_TICKS .FILL 4,0 ; 32 BIT TICK COUNTER +HB_SECTCK .DB TICKSPERSEC ; TICK COUNTER FOR FRACTIONAL SECONDS +HB_SECS .FILL 4,0 ; 32 BIT SECONDS COUNTER +; +HB_CPUTYPE .DB 0 ; 0=Z80, 1=80180, 2=SL1960, 3=ASCI BRG +; +RTCVAL .DB RTCDEF ; SHADOW VALUE FOR RTC LATCH PORT +; +HB_BATCOND .DB 0 ; BATTERY CONDITION (0=LOW, 1=OK) ; -STR_BANNER .DB "RetroBrew HBIOS v", BIOSVER, ", ", TIMESTAMP, "$" +#IF (BT_REC_TYPE != BT_REC_NONE) +HB_BOOT_REC .DB 0 ; BOOT MODE (0=NORMAL, 1=RECOVERY MODE) +#ENDIF +; +STR_BANNER .DB "RomWBW HBIOS v", BIOSVER, ", ", TIMESTAMP, "$" STR_PLATFORM .DB PLATFORM_NAME, "$" STR_SWITCH .DB "*** Activating CRT Console ***$" STR_BADINT .DB "\r\n*** BAD INT ***\r\n$" +STR_LOWBAT .DB "\r\n\r\n+++ LOW BATTERY +++$" ; -#IF (DSKYENABLE) -MSG_HBVER .DB $BE,$FF,$8A,$FB,$D7,$6D,$77,$B0 ; "HBIOS291" +#IF (DSKYENABLE) ; 'H','B','I','O',' ',' ',' ',' ' +MSG_HBVER .DB $BE,$FF,$8A,$FB,$80,$80,$80,$80 ; "HBIO " #ENDIF ; +HB_APPBNK .DB 0 ; START BANK WHEN RUN IN APP MODE +; HB_CURSEC .DB 0 ; CURRENT SECOND (TEMP) ; HB_BCDTMP .FILL 5,0 ; BCD NUMBER STORAGE (TEMP) diff --git a/Source/HBIOS/hbios.inc b/Source/HBIOS/hbios.inc index 50e06531..6c166307 100644 --- a/Source/HBIOS/hbios.inc +++ b/Source/HBIOS/hbios.inc @@ -71,18 +71,22 @@ BF_SYSGET_CIOCNT .EQU $00 ; GET CHAR UNIT COUNT BF_SYSGET_DIOCNT .EQU $10 ; GET DISK UNIT COUNT BF_SYSGET_VDACNT .EQU $40 ; GET VDA UNIT COUNT BF_SYSGET_TIMER .EQU $D0 ; GET CURRENT TIMER VALUE +BF_SYSGET_SECS .EQU $D1 ; GET CURRENT SECONDS VALUE BF_SYSGET_BOOTINFO .EQU $E0 ; GET BOOT INFORMATION BF_SYSGET_CPUINFO .EQU $F0 ; GET CPU INFORMATION BF_SYSGET_MEMINFO .EQU $F1 ; GET MEMORY CAPACTITY INFO BF_SYSGET_BNKINFO .EQU $F2 ; GET BANK ASSIGNMENT INFO ; BF_SYSSET_TIMER .EQU $D0 ; SET TIMER VALUE +BF_SYSSET_SECS .EQU $D1 ; SET SECONDS VALUE BF_SYSSET_BOOTINFO .EQU $E0 ; SET BOOT INFORMATION ; BF_SYSINT_INFO .EQU $00 ; GET INTERRUPT SYSTEM INFO BF_SYSINT_GET .EQU $10 ; GET INT VECTOR ADDRESS BF_SYSINT_SET .EQU $20 ; SET INT VECTOR ADDRESS ; +CIO_CONSOLE .EQU $80 ; CIO UNIT NUM FOR CUR CON +; ; CHAR DEVICE IDS ; CIODEV_UART .EQU $00 @@ -92,8 +96,8 @@ CIODEV_PRPCON .EQU $30 CIODEV_PPPCON .EQU $40 CIODEV_SIO .EQU $50 CIODEV_ACIA .EQU $60 -CIODEV_CONSOLE .EQU $D0 CIODEV_PIO .EQU $70 +CIODEV_UF .EQU $80 ; ; SUB TYPES OF CHAR DEVICES ; @@ -121,14 +125,14 @@ VDADEV_VDU .EQU $00 ; ECB VDU - MOTOROLA 6545 VDADEV_CVDU .EQU $10 ; ECB COLOR VDU - MOS 8563 VDADEV_NEC .EQU $20 ; ECB UPD7220 - NEC UPD7220 VDADEV_TMS .EQU $30 ; N8 ONBOARD VDA SUBSYSTEM - TMS 9918 -VDADEV_VGA .EQU $40 ; VGA -; -; EMULATION TYPES +VDADEV_VGA .EQU $40 ; ECB VGA3 - HITACHI HD6445 +;; +;; EMULATION TYPES - moved to std.asm +;; +;EMUTYP_NONE .EQU 0 ; NONE +;EMUTYP_TTY .EQU 1 ; TTY +;EMUTYP_ANSI .EQU 2 ; ANSI ; -EMUTYP_NONE .EQU 0 ; NONE -EMUTYP_TTY .EQU 1 ; TTY -EMUTYP_ANSI .EQU 2 ; ANSI -; ; HBIOS CONTROL BLOCK OFFSETS ; WARNING: THESE OFFSETS WILL CHANGE SIGNIFICANTLY BETWEEN RELEASES ; IT IS STRONGLY RECOMMENDED THAT YOU DO NOT USE THEM! @@ -177,6 +181,7 @@ HB_SRCBNK .EQU HBX_XFCDAT + 4 ; BNKCPY: SOURCE BANK ID HB_DSTADR .EQU HBX_XFCDAT + 5 ; BNKCPY: DESTINATION ADDRESS HB_DSTBNK .EQU HBX_XFCDAT + 7 ; BNKCPY: SOURCE ADDRESS HB_CPYLEN .EQU HBX_XFCDAT + 8 ; BNKCPY: COPY LENGTH +HB_LOCK .EQU HBX_XFCDAT + 15 ; INVOKE: HBIOS MUTEX LOCK ; HBX_XFCFNS .EQU HBX_XFC + $10 ; JUMP TABLE PORTION OF HBIOS PROXY INTERFACE AREA HB_INVOKE .EQU HBX_XFCFNS + (0 * 3) ; INVOKE HBIOS FUNCTION diff --git a/Source/HBIOS/hdsk.asm b/Source/HBIOS/hdsk.asm index e9da24f8..3c8825bb 100644 --- a/Source/HBIOS/hdsk.asm +++ b/Source/HBIOS/hdsk.asm @@ -138,9 +138,13 @@ HDSK_RESET: ; SLICE C/H/S = 65/16/16 OR 16,640 TOTAL SECTORS ; ASSUME 8 SLICES, SO 16640 X 8 = 133,120 TOTAL SECTORS ; +; 2048 TRKS X 16 HDS X 16 SPT = 524288 OR 80000H +; HDSK_CAP: - LD DE,133120 >> 16 ; BLOCK COUNT MSW - LD HL,133120 & $FFFF ; BLOCK COUNT LSW + ;LD DE,133120 >> 16 ; BLOCK COUNT MSW + ;LD HL,133120 & $FFFF ; BLOCK COUNT LSW + LD DE,8 ; BLOCK COUNT MSW + LD HL,0 ; BLOCK COUNT LSW LD BC,512 ; 512 BYTE SECTOR XOR A ; SIGNAL SUCCESS RET @@ -191,12 +195,14 @@ HDSK_SEEK: ; ; HDSK_READ: + CALL HB_DSKREAD ; HOOK HBIOS DISK READ SUPERVISOR LD A,HDSK_CMDREAD JR HDSK_RW ; ; ; HDSK_WRITE: + CALL HB_DSKWRITE ; HOOK HBIOS DISK WRITE SUPERVISOR LD A,HDSK_CMDWRITE JR HDSK_RW ; diff --git a/Source/HBIOS/ide.asm b/Source/HBIOS/ide.asm index 30c8e408..c018384a 100644 --- a/Source/HBIOS/ide.asm +++ b/Source/HBIOS/ide.asm @@ -4,9 +4,20 @@ ;============================================================================= ; ; TODO: -; - IMPLEMENT IDE_INITDEVICE -; - HANDLE SECONDARY INTERFACE ON DIDE -; - IMPLEMENT INTELLIGENT RESET, CHECK IF DEVICE IS ACTUALLY BROKEN BEFORE RESET +; - FIX SCALER CONSTANT +; - GOPARTNER NEEDS TO HANDLE "NO PARTNER" CONDITION +; - IMPLEMENT H/W PROBES FOR DIO AND DIDE +; +; NOTES: +; - WELL KNOWN IDE PORT ADDRESSES: +; BOARD BASE DATLO DATHI +; ------ ------ ------ ------ +; DIO $20 $20 $28 +; DIDE-A $20 $28 $28 +; DIDE-B $30 $38 $38 +; MK4 $80 N/A N/A +; RC $10 N/A N/A +; SMB $E0 N/A N/A ; ; +-----------------------------------------------------------------------+ ; | CONTROL BLOCK REGISTERS | @@ -94,82 +105,31 @@ ; SRST: SOFTWARE RESET ; ~IEN: INTERRUPT ENABLE ; -#IF (IDETRACE >= 3) -#DEFINE DCALL CALL -#ELSE -#DEFINE DCALL \; -#ENDIF -; -; UNIT MAPPING IS AS FOLLOWS: -; IDE0: PRIMARY MASTER -; IDE1: PRIMARY SLAVE -; IDE2: SECONDARY MASTER -; IDE3: SECONDARY SLAVE -; -IDE_DEVCNT .EQU 2 ; ASSUME ONLY PRIMARY INTERFACE -IDE_IO_BASE .EQU $20 ; DEFAULT IO BASE (NOTE OVERRIDES BELOW) -; -#IF (IDEMODE == IDEMODE_MK4) -IDE_IO_BASE .SET MK4_IDE -#ENDIF - -#IF (IDEMODE == IDEMODE_RC) -IDE_IO_BASE .SET $10 -#ENDIF - -#IF (IDEMODE == IDEMODE_SMB) -IDE_IO_BASE .SET $E0 -#ENDIF - -#IF ((IDEMODE == IDEMODE_DIO) | (IDEMODE == IDEMODE_MK4)) -#IF (IDE8BIT) -IDE_IO_DATA .EQU $IDE_IO_BASE + $00 ; DATA PORT (8 BIT PIO) (R/W) -#ELSE -IDE_IO_DATALO .EQU $IDE_IO_BASE + $00 ; DATA PORT (16 BIT PIO LO BYTE) (R/W) -IDE_IO_DATAHI .EQU $IDE_IO_BASE + $08 ; DATA PORT (16 BIT PIO HI BYTE) (R/W) -IDE_IO_DATA .EQU IDE_IO_DATALO -#ENDIF -#ENDIF -; -#IF (IDEMODE == IDEMODE_DIDE) -IDE_DEVCNT .SET 4 ; DIDE HAS PRIMARY AND SECONDARY INTERACES -#IF (IDE8BIT) -IDE_IO_DATA .EQU $IDE_IO_BASE + $00 ; DATA PORT (8 BIT PIO) (R/W) -#ELSE -IDE_IO_DATA .EQU $IDE_IO_BASE + $08 ; DATA PORT (16 BIT PIO LO/HI BYTES) (R/W) -IDE_IO_DMA .EQU $IDE_IO_BASE + $09 ; DATA PORT (16 BIT DMA LO/HI BYTES) (R/W) -#ENDIF -#ENDIF -; -#IF ((IDEMODE == IDEMODE_RC) | (IDEMODE == IDEMODE_SMB)) -IDE_DEVCNT .SET 1 ; RC2014 COMPACT FLASH SUPPORTS ONLY 1 DEVICE -IDE_IO_DATA .EQU $IDE_IO_BASE + $00 ; DATA PORT (8 BIT) (R/W) -#ENDIF -; -;IDE_IO_DATA .EQU $IDE_IO_BASE + $00 ; DATA INPUT/OUTPUT (R/W) -IDE_IO_ERR .EQU $IDE_IO_BASE + $01 ; ERROR REGISTER (R) -IDE_IO_FEAT .EQU $IDE_IO_BASE + $01 ; FEATURES REGISTER (W) -IDE_IO_COUNT .EQU $IDE_IO_BASE + $02 ; SECTOR COUNT REGISTER (R/W) -IDE_IO_SECT .EQU $IDE_IO_BASE + $03 ; SECTOR NUMBER REGISTER (R/W) -IDE_IO_CYLLO .EQU $IDE_IO_BASE + $04 ; CYLINDER NUM REGISTER (LSB) (R/W) -IDE_IO_CYLHI .EQU $IDE_IO_BASE + $05 ; CYLINDER NUM REGISTER (MSB) (R/W) -IDE_IO_DRVHD .EQU $IDE_IO_BASE + $06 ; DRIVE/HEAD REGISTER (R/W) -IDE_IO_LBA0 .EQU $IDE_IO_BASE + $03 ; LBA BYTE 0 (BITS 0-7) (R/W) -IDE_IO_LBA1 .EQU $IDE_IO_BASE + $03 ; LBA BYTE 1 (BITS 8-15) (R/W) -IDE_IO_LBA2 .EQU $IDE_IO_BASE + $03 ; LBA BYTE 2 (BITS 16-23) (R/W) -IDE_IO_LBA3 .EQU $IDE_IO_BASE + $03 ; LBA BYTE 3 (BITS 24-27) (R/W) -IDE_IO_STAT .EQU $IDE_IO_BASE + $07 ; STATUS REGISTER (R) -IDE_IO_CMD .EQU $IDE_IO_BASE + $07 ; COMMAND REGISTER (EXECUTE) (W) -IDE_IO_ALTSTAT .EQU $IDE_IO_BASE + $0E ; ALTERNATE STATUS REGISTER (R) -IDE_IO_CTRL .EQU $IDE_IO_BASE + $0E ; DEVICE CONTROL REGISTER (W) -IDE_IO_DRVADR .EQU $IDE_IO_BASE + $0F ; DRIVE ADDRESS REGISTER (R) +IDE_REG_DATA .EQU $00 ; DATA /OUTPUT (R/W) +IDE_REG_ERR .EQU $01 ; ERROR REGISTER (R) +IDE_REG_FEAT .EQU $01 ; FEATURES REGISTER (W) +IDE_REG_COUNT .EQU $02 ; SECTOR COUNT REGISTER (R/W) +IDE_REG_SECT .EQU $03 ; SECTOR NUMBER REGISTER (R/W) +IDE_REG_CYLLO .EQU $04 ; CYLINDER NUM REGISTER (LSB) (R/W) +IDE_REG_CYLHI .EQU $05 ; CYLINDER NUM REGISTER (MSB) (R/W) +IDE_REG_DRVHD .EQU $06 ; DRIVE/HEAD REGISTER (R/W) +IDE_REG_LBA0 .EQU $03 ; LBA BYTE 0 (BITS 0-7) (R/W) +IDE_REG_LBA1 .EQU $04 ; LBA BYTE 1 (BITS 8-15) (R/W) +IDE_REG_LBA2 .EQU $05 ; LBA BYTE 2 (BITS 16-23) (R/W) +IDE_REG_LBA3 .EQU $06 ; LBA BYTE 3 (BITS 24-27) (R/W) +IDE_REG_STAT .EQU $07 ; STATUS REGISTER (R) +IDE_REG_CMD .EQU $07 ; COMMAND REGISTER (EXECUTE) (W) +IDE_REG_XAR .EQU $08 ; ECB DIDE EXTERNAL ADDRESS REGISTER (W) +IDE_REG_ALTSTAT .EQU $0E ; ALTERNATE STATUS REGISTER (R) +IDE_REG_CTRL .EQU $0E ; DEVICE CONTROL REGISTER (W) +IDE_REG_DRVADR .EQU $0F ; DRIVE ADDRESS REGISTER (R) ; ; COMMAND BYTES ; -IDE_CIDE_RECAL .EQU $10 -IDE_CIDE_READ .EQU $20 -IDE_CIDE_WRITE .EQU $30 -IDE_CIDE_IDDEV .EQU $EC +IDE_CIDE_RECAL .EQU $10 +IDE_CIDE_READ .EQU $20 +IDE_CIDE_WRITE .EQU $30 +IDE_CIDE_IDDEV .EQU $EC IDE_CIDE_SETFEAT .EQU $EF ; ; FEATURE BYTES @@ -196,57 +156,130 @@ IDE_STBSYTO .EQU -7 ; ; DRIVE SELECTION BYTES (FOR USE IN DRIVE/HEAD REGISTER) ; -IDE_DRVSEL: -IDE_DRVMASTER .DB %11100000 ; LBA, MASTER DEVICE -IDE_DRVSLAVE .DB %11110000 ; LBA, SLAVE DEVICE +;IDE_DRVSEL: +IDE_DRVMASTER .EQU %11100000 ; LBA, MASTER DEVICE +IDE_DRVSLAVE .EQU %11110000 ; LBA, SLAVE DEVICE ; ; IDE DEVICE CONFIGURATION ; -IDE_CFGSIZ .EQU 12 ; SIZE OF CFG TBL ENTRIES +IDE_CFGSIZ .EQU 19 ; SIZE OF CFG TBL ENTRIES ; ; PER DEVICE DATA OFFSETS ; IDE_DEV .EQU 0 ; OFFSET OF DEVICE NUMBER (BYTE) -IDE_STAT .EQU 1 ; LAST STATUS (BYTE) -IDE_TYPE .EQU 2 ; DEVICE TYPE (BYTE) -IDE_FLAGS .EQU 3 ; FLAG BITS BIT 0=CF, 1=LBA (BYTE) -IDE_MEDCAP .EQU 4 ; MEDIA CAPACITY (DWORD) -IDE_LBA .EQU 8 ; OFFSET OF LBA (DWORD) +IDE_MODE .EQU 1 ; OPERATION MODE: IDE MODE (BYTE) +IDE_STAT .EQU 2 ; LAST STATUS (BYTE) +IDE_TYPE .EQU 3 ; DEVICE TYPE (BYTE) +IDE_ACC .EQU 4 ; ACCESS FLAG BITS BIT 0=MASTER, 1=8BIT (BYTE) +IDE_MED .EQU 5 ; MEDIA FLAG BITS BIT 0=CF, 1=LBA (BYTE) +IDE_MEDCAP .EQU 6 ; MEDIA CAPACITY (DWORD) +IDE_LBA .EQU 10 ; OFFSET OF LBA (DWORD) +IDE_IOBASE .EQU 14 ; IO BASE ADDRESS (BYTE) +IDE_DATALO .EQU 15 ; 16 BIT DATA LO BYTE +IDE_DATAHI .EQU 16 ; 16 BIT DATA HI BYTE +IDE_PARTNER .EQU 17 ; PARTNER DEVICE (MASTER <-> SLAVE) (WORD) +; +IDE_ACC_MAS .EQU %00000001 ; UNIT IS MASTER (ELSE SLAVE) +IDE_ACC_8BIT .EQU %00000010 ; UNIT WANTS 8 BIT I/O (ELSE 16 BIT) +; +IDE_MED_CF .EQU %00000001 ; MEDIA IS CF CARD +IDE_MED_LBA .EQU %00000010 ; MEDIA HAS LBA CAPABILITY +; +IDE_DEVCNT .EQU IDECNT * 2 ; IDE_CFGTBL: - ; DEVICE 0, PRIMARY MASTER +; +#IF (IDECNT >= 1) +; +IDE_DEV0M: ; DEVICE 0, MASTER .DB 0 ; DRIVER DEVICE NUMBER + .DB IDE0MODE ; DRIVER DEVICE MODE .DB 0 ; DEVICE STATUS .DB 0 ; DEVICE TYPE - .DB 0 ; FLAGS BYTE + .DB IDE_ACC_MAS | (IDE0A8BIT & IDE_ACC_8BIT) ; UNIT ACCESS FLAGS + .DB 0 ; MEDIA FLAGS .DW 0,0 ; DEVICE CAPACITY .DW 0,0 ; CURRENT LBA -#IF (IDE_DEVCNT >= 2) - ; DEVICE 1, PRIMARY SLAVE - .DB 1 ; DRIVER DEVICE NUMBER + .DB IDE0BASE ; IO BASE ADDRESS + .DB IDE0DATLO ; IO BASE ADDRESS + .DB IDE0DATHI ; IO BASE ADDRESS + .DW IDE_DEV0S ; PARTNER +; +IDE_DEV0S: ; DEVICE 0, SLAVE + .DB 0 ; DRIVER DEVICE NUMBER + .DB IDE0MODE ; DRIVER DEVICE MODE .DB 0 ; DEVICE STATUS .DB 0 ; DEVICE TYPE - .DB 0 ; FLAGS BYTE + .DB (IDE0B8BIT & IDE_ACC_8BIT) ; UNIT ACCESS FLAGS + .DB 0 ; MEDIA FLAGS .DW 0,0 ; DEVICE CAPACITY .DW 0,0 ; CURRENT LBA + .DB IDE0BASE ; IO BASE ADDRESS + .DB IDE0DATLO ; IO BASE ADDRESS + .DB IDE0DATHI ; IO BASE ADDRESS + .DW IDE_DEV0M ; PARTNER #ENDIF -#IF (IDE_DEVCNT >= 3) - ; DEVICE 2, SECONDARY MASTER - .DB 2 ; DRIVER DEVICE NUMBER +; +#IF (IDECNT >= 2) +; +IDE_DEV1M: ; DEVICE 1, MASTER + .DB 0 ; DRIVER DEVICE NUMBER + .DB IDE1MODE ; DRIVER DEVICE MODE .DB 0 ; DEVICE STATUS .DB 0 ; DEVICE TYPE - .DB 0 ; FLAGS BYTE + .DB IDE_ACC_MAS | (IDE1A8BIT & IDE_ACC_8BIT) ; UNIT ACCESS FLAGS + .DB 0 ; MEDIA FLAGS .DW 0,0 ; DEVICE CAPACITY .DW 0,0 ; CURRENT LBA + .DB IDE1BASE ; IO BASE ADDRESS + .DB IDE1DATLO ; IO BASE ADDRESS + .DB IDE1DATHI ; IO BASE ADDRESS + .DW IDE_DEV1S ; PARTNER +; +IDE_DEV1S: ; DEVICE 1, SLAVE + .DB 0 ; DRIVER DEVICE NUMBER + .DB IDE1MODE ; DRIVER DEVICE MODE + .DB 0 ; DEVICE STATUS + .DB 0 ; DEVICE TYPE + .DB (IDE1B8BIT & IDE_ACC_8BIT) ; UNIT ACCESS FLAGS + .DB 0 ; MEDIA FLAGS + .DW 0,0 ; DEVICE CAPACITY + .DW 0,0 ; CURRENT LBA + .DB IDE1BASE ; IO BASE ADDRESS + .DB IDE1DATLO ; IO BASE ADDRESS + .DB IDE1DATHI ; IO BASE ADDRESS + .DW IDE_DEV1M ; PARTNER #ENDIF -#IF (IDE_DEVCNT >= 4) - ; DEVICE 2, SECONDARY SLAVE - .DB 3 ; DRIVER DEVICE NUMBER +; +#IF (IDECNT >= 3) +; +IDE_DEV2M: ; DEVICE 2, MASTER + .DB 0 ; DRIVER DEVICE NUMBER + .DB IDE2MODE ; DRIVER DEVICE MODE .DB 0 ; DEVICE STATUS .DB 0 ; DEVICE TYPE - .DB 0 ; FLAGS BYTE + .DB IDE_ACC_MAS | (IDE2A8BIT & IDE_ACC_8BIT) ; UNIT ACCESS FLAGS + .DB 0 ; MEDIA FLAGS .DW 0,0 ; DEVICE CAPACITY .DW 0,0 ; CURRENT LBA + .DB IDE2BASE ; IO BASE ADDRESS + .DB IDE2DATLO ; IO BASE ADDRESS + .DB IDE2DATHI ; IO BASE ADDRESS + .DW IDE_DEV2S ; PARTNER +; +IDE_DEV2S: ; DEVICE 2, SLAVE + .DB 0 ; DRIVER DEVICE NUMBER + .DB IDE2MODE ; DRIVER DEVICE MODE + .DB 0 ; DEVICE STATUS + .DB 0 ; DEVICE TYPE + .DB (IDE2B8BIT & IDE_ACC_8BIT) ; UNIT ACCESS FLAGS + .DB 0 ; MEDIA FLAGS + .DW 0,0 ; DEVICE CAPACITY + .DW 0,0 ; CURRENT LBA + .DB IDE2BASE ; IO BASE ADDRESS + .DB IDE2DATLO ; IO BASE ADDRESS + .DB IDE2DATHI ; IO BASE ADDRESS + .DW IDE_DEV1M ; PARTNER #ENDIF ; #IF ($ - IDE_CFGTBL) != (IDE_DEVCNT * IDE_CFGSIZ) @@ -272,78 +305,87 @@ IDE_TOFAST .EQU 10 ; FAST TIMEOUT IS 0.5 SECS ;============================================================================= ; IDE_INIT: - CALL NEWLINE ; FORMATTING - PRTS("IDE:$") -; ; COMPUTE CPU SPEED COMPENSATED TIMEOUT SCALER - ; AT 1MHZ, THE SCALER IS 961 (50000US / 52TS = 961) - ; SCALER IS THEREFORE 961 * CPU SPEED IN MHZ + ; AT 1MHZ, THE SCALER IS 218 (50000US / 229TS = 218) + ; SCALER IS THEREFORE 218 * CPU SPEED IN MHZ LD DE,961 ; LOAD SCALER FOR 1MHZ LD A,(CB_CPUMHZ) ; LOAD CPU SPEED IN MHZ CALL MULT8X16 ; HL := DE * A LD (IDE_TOSCALER),HL ; SAVE IT ; -#IF (IDEMODE == IDEMODE_DIO) - PRTS(" MODE=DIO$") -#ENDIF -#IF (IDEMODE == IDEMODE_DIDE) - PRTS(" MODE=DIDE$") -#ENDIF -#IF (IDEMODE == IDEMODE_MK4) - PRTS(" MODE=MK4$") -#ENDIF - ; PRINT IDE INTERFACE PORT ADDRESS - PRTS(" IO=0x$") ; LABEL FOR IO ADDRESS - LD A,IDE_IO_DATA ; GET IO ADDRESS - CALL PRTHEXBYTE ; PRINT IT + XOR A ; ZERO ACCUM + LD (IDE_DEVNUM),A ; INIT DEV UNIT NUM FOR DYNAMIC ASSIGNMENT + LD IY,IDE_CFGTBL ; POINT TO START OF CONFIG TABLE ; +IDE_INIT1: + LD A,(IY) ; LOAD FIRST BYTE TO CHECK FOR END + CP $FF ; CHECK FOR END OF TABLE VALUE + JR NZ,IDE_INIT2 ; IF NOT END OF TABLE, CONTINUE + XOR A ; SIGNAL SUCCESS + RET ; AND RETURN ; - CALL IDE_DETECT ; CHECK FOR HARDWARE - JR Z,IDE_INIT00 ; CONTINUE IF PRESENT -; - ; HARDWARE NOT PRESENT - PRTS(" NOT PRESENT$") - OR $FF ; SIGNAL FAILURE - RET -; -IDE_INIT00: - ; PRINT UNIT COUNT - PRTS(" DEVICES=$") ; PRINT LABEL FOR DEVICE COUNT - LD A,IDE_DEVCNT ; GET UNIT COUNT - CALL PRTDECB ; PRINT IT IN DECIMAL +IDE_INIT2: + BIT 0,(IY+IDE_ACC) ; MASTER? + JR Z,IDE_INIT4 ; IF NOT MASTER, SKIP AHEAD ; -; SETUP THE DISPATCH TABLE ENTRIES + CALL NEWLINE ; FORMATTING + PRTS("IDE:$") ; LABEL FOR IO ADDRESS ; - LD B,IDE_DEVCNT ; LOOP CONTROL - LD IY,IDE_CFGTBL ; START OF CFG TABLE -IDE_INIT0: - PUSH BC ; SAVE LOOP CONTROL + PRTS(" IO=0x$") ; LABEL FOR IO ADDRESS + LD A,(IY+IDE_IOBASE) ; GET IO BASE ADDRES + CALL PRTHEXBYTE ; DISPLAY IT +; + PRTS(" MODE=$") ; LABEL FOR MODE + LD A,(IY+IDE_MODE) ; GET MODE BITS + LD DE,IDE_STR_MODE_DIO ; MODE LABEL + CP IDEMODE_DIO ; TEST FOR MODE + JR Z,IDE_INIT2A ; IF SO, DISPLAY IT + LD DE,IDE_STR_MODE_DIDE ; MODE LABEL + CP IDEMODE_DIDE ; TEST FOR MODE + JR Z,IDE_INIT2A ; IF SO, DISPLAY IT + LD DE,IDE_STR_MODE_MK4 ; MODE LABEL + CP IDEMODE_MK4 ; TEST FOR MODE + JR Z,IDE_INIT2A ; IF SO, DISPLAY IT + LD DE,IDE_STR_MODE_RC ; MODE LABEL + CP IDEMODE_RC ; TEST FOR MODE + JR Z,IDE_INIT2A ; IF SO, DISPLAY IT + JR IDE_INIT4 ; NO MODE? BYPASS ENTRY +IDE_INIT2A: + CALL WRITESTR ; DISPLAY MODE +; + CALL IDE_DETECT ; PROBE FOR INTERFACE + JR Z,IDE_INIT3 ; GOT IT, MOVE ON TO INIT UNITS + CALL PC_SPACE ; FORMATTING + LD DE,IDE_STR_NOHW ; NOT PRESENT MESSAGE + CALL WRITESTR ; DISPLAY IT + JR IDE_INIT4 ; SKIP CFG ENTRY +; +IDE_INIT3: + CALL IDE_RESET ; RESET THE BUS + CALL IDE_INIT5 ; DETECT/INIT MASTER + PUSH IY ; SAVE CFG PTR + CALL IDE_GOPARTNER ; SWITCH IY TO PARTNER CFG + CALL IDE_INIT5 ; DETECT/INIT SLAVE + POP IY ; RESTORE CFG PTR +; +IDE_INIT4: + LD DE,IDE_CFGSIZ ; SIZE OF CFG TABLE ENTRY + ADD IY,DE ; BUMP POINTER + JP IDE_INIT1 ; AND LOOP +; +IDE_INIT5: + ; UPDATE DRIVER RELATIVE UNIT NUMBER IN CONFIG TABLE + LD A,(IDE_DEVNUM) ; GET NEXT UNIT NUM TO ASSIGN + LD (IY+IDE_DEV),A ; UPDATE IT + INC A ; BUMP TO NEXT UNIT NUM TO ASSIGN + LD (IDE_DEVNUM),A ; SAVE IT +; + ; ADD UNIT TO GLOBAL DISK UNIT TABLE LD BC,IDE_FNTBL ; BC := FUNC TABLE ADR PUSH IY ; CFG ENTRY POINTER POP DE ; COPY TO DE - CALL DIO_ADDENT ; ADD ENTRY, BC IS NOT DESTROYED - LD BC,IDE_CFGSIZ ; SIZE OF CFG ENTRY - ADD IY,BC ; BUMP IY TO NEXT ENTRY - POP BC ; RESTORE BC - DJNZ IDE_INIT0 ; LOOP AS NEEDED -; - ; INITIALIZE THE IDE INTERFACE NOW - CALL IDE_RESET ; DO HARDWARE SETUP/INIT - RET NZ ; ABORT IF RESET FAILS -; - ; DEVICE DISPLAY LOOP - LD B,IDE_DEVCNT ; LOOP ONCE PER DEVICE - LD IY,IDE_CFGTBL ; START OF CFG TABLE -IDE_INIT1: - PUSH BC ; SAVE LOOP CONTROL - CALL IDE_INIT2 ; DISPLAY UNIT INFO - LD BC,IDE_CFGSIZ ; SIZE OF CFG ENTRY - ADD IY,BC ; BUMP IY TO NEXT ENTRY - POP BC ; RESTORE LOOP CONTROL - DJNZ IDE_INIT1 ; LOOP UNTIL DONE - RET ; DONE + CALL DIO_ADDENT ; ADD ENTRY TO GLOBAL DISK DEV TABLE ; -IDE_INIT2: ; CHECK FOR BAD STATUS LD A,(IY+IDE_STAT) ; GET STATUS OR A ; SET FLAGS @@ -351,13 +393,13 @@ IDE_INIT2: ; CALL IDE_PRTPREFIX ; PRINT DEVICE PREFIX ; -#IF (IDE8BIT) - PRTS(" 8BIT$") -#ENDIF + LD DE,IDE_STR_8BIT + BIT 1,(IY+IDE_ACC) ; 8 BIT ACCESS? + CALL NZ,WRITESTR ; ; PRINT LBA/NOLBA CALL PC_SPACE ; FORMATTING - BIT 1,(IY+IDE_FLAGS) ; TEST LBA FLAG + BIT 1,(IY+IDE_MED) ; TEST LBA FLAG LD DE,IDE_STR_NO ; POINT TO "NO" STRING CALL Z,WRITESTR ; PRINT "NO" BEFORE "LBA" IF LBA NOT SUPPORTED PRTS("LBA$") ; PRINT "LBA" REGARDLESS @@ -376,8 +418,7 @@ IDE_INIT2: CALL PRTDEC ; PRINT LOW WORD IN DECIMAL (HIGH WORD DISCARDED) PRTS("MB$") ; PRINT SUFFIX ; - XOR A ; SIGNAL SUCCESS - RET ; RETURN WITH A=0, AND Z SET + RET ; ;---------------------------------------------------------------------- ; PROBE FOR IDE HARDWARE @@ -387,8 +428,11 @@ IDE_INIT2: ; IDE_DETECT: ; -#IF (IDEMODE == IDEMODE_DIDE) -#ENDIF +;#IF (IDEMODE == IDEMODE_DIDE) +;#ENDIF +;; +;#IF (IDEMODE == IDEMODE_DIO) +;#ENDIF ; XOR A ; SIGNAL SUCCESS RET ; AND RETURN @@ -422,6 +466,7 @@ IDE_DEFMED: ; ; IDE_READ: + CALL HB_DSKREAD ; HOOK HBIOS DISK READ SUPERVISOR LD BC,IDE_RDSEC ; GET ADR OF SECTOR READ FUNC LD (IDE_IOFNADR),BC ; SAVE IT AS PENDING IO FUNC JR IDE_IO ; CONTINUE TO GENERIC IO ROUTINE @@ -429,6 +474,7 @@ IDE_READ: ; ; IDE_WRITE: + CALL HB_DSKWRITE ; HOOK HBIOS DISK WRITE SUPERVISOR LD BC,IDE_WRSEC ; GET ADR OF SECTOR WRITE FUNC LD (IDE_IOFNADR),BC ; SAVE IT AS PENDING IO FUNC JR IDE_IO ; CONTINUE TO GENERIC IO ROUTINE @@ -448,7 +494,7 @@ IDE_IO: #ENDIF PUSH BC ; SAVE COUNTERS CALL IDE_SELUNIT ; HARDWARE SELECTION OF TARGET UNIT - CALL IDE_CHKDEVICE ; CHECK DEVICE AND CLEAR STATUS + CALL IDE_CHKERR ; CHECK FOR ERR STATUS AND RESET IF SO POP BC ; RESTORE COUNTERS JR NZ,IDE_IO3 ; BAIL OUT ON ERROR IDE_IO1: @@ -489,7 +535,7 @@ IDE_STATUS: IDE_DEVICE: LD D,DIODEV_IDE ; D := DEVICE TYPE LD E,(IY+IDE_DEV) ; E := PHYSICAL DEVICE NUMBER - BIT 0,(IY+IDE_FLAGS) ; TEST CF BIT IN FLAGS + BIT 0,(IY+IDE_MED) ; TEST CF BIT IN FLAGS LD C,%00000000 ; ASSUME NON-REMOVABLE HARD DISK JR Z,IDE_DEVICE1 ; IF Z, WE ARE DONE LD C,%01001000 ; OTHERWISE REMOVABLE COMPACT FLASH @@ -579,13 +625,21 @@ IDE_SETFEAT: PRTS(" SETFEAT$") #ENDIF LD A,(IDE_DRVHD) - OUT (IDE_IO_DRVHD),A - DCALL PC_SPACE - DCALL PRTHEXBYTE + ;OUT (IDE_IO_DRVHD),A + CALL IDE_OUT + .DB IDE_REG_DRVHD +#IF (IDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF POP AF - OUT (IDE_IO_FEAT),A ; SET THE FEATURE VALUE - DCALL PC_SPACE - DCALL PRTHEXBYTE + ;OUT (IDE_IO_FEAT),A ; SET THE FEATURE VALUE + CALL IDE_OUT + .DB IDE_REG_FEAT +#IF (IDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF LD A,IDE_CIDE_SETFEAT ; CMD = SETFEAT LD (IDE_CMD),A ; SAVE IT JP IDE_RUNCMD ; RUN COMMAND AND EXIT @@ -598,9 +652,13 @@ IDE_IDENTIFY: PRTS(" IDDEV$") #ENDIF LD A,(IDE_DRVHD) - OUT (IDE_IO_DRVHD),A - DCALL PC_SPACE - DCALL PRTHEXBYTE + ;OUT (IDE_IO_DRVHD),A + CALL IDE_OUT + .DB IDE_REG_DRVHD +#IF (IDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF LD A,IDE_CIDE_IDDEV LD (IDE_CMD),A CALL IDE_RUNCMD @@ -617,9 +675,13 @@ IDE_RDSEC: PRTS(" READ$") #ENDIF LD A,(IDE_DRVHD) - OUT (IDE_IO_DRVHD),A - DCALL PC_SPACE - DCALL PRTHEXBYTE + ;OUT (IDE_IO_DRVHD),A + CALL IDE_OUT + .DB IDE_REG_DRVHD +#IF (IDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF CALL IDE_SETADDR ; SETUP CYL, TRK, HEAD LD A,IDE_CIDE_READ LD (IDE_CMD),A @@ -637,9 +699,13 @@ IDE_WRSEC: PRTS(" WRITE$") #ENDIF LD A,(IDE_DRVHD) - OUT (IDE_IO_DRVHD),A - DCALL PC_SPACE - DCALL PRTHEXBYTE + ;OUT (IDE_IO_DRVHD),A + CALL IDE_OUT + .DB IDE_REG_DRVHD +#IF (IDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF CALL IDE_SETADDR ; SETUP CYL, TRK, HEAD LD A,IDE_CIDE_WRITE LD (IDE_CMD),A @@ -654,33 +720,37 @@ IDE_SETADDR: ; SEND 3 LOWEST BYTES OF LBA IN REVERSE ORDER ; IDE_IO_LBA3 HAS ALREADY BEEN SET ; HSTLBA2-0 --> IDE_IO_LBA2-0 - LD C,IDE_IO_LBA0 + 3 ; STARTING IO PORT (NOT PRE-DEC BELOW) - LD A,IDE_LBA + 2 ; OFFSET OF 3RD BYTE OF LBA IN CFG - CALL LDHLIYA ; HL := IY + A, REG A TRASHED - LD B,3 ; SEND 3 BYTES -IDE_SETADDR1: -; + LD A,(IY+IDE_LBA+2) #IF (IDETRACE >= 3) - LD A,(HL) CALL PC_SPACE CALL PRTHEXBYTE #ENDIF + CALL IDE_OUT + .DB IDE_REG_LBA2 ; - DEC C ; NEXT PORT - OUTD ; SEND NEXT BYTE - JR NZ,IDE_SETADDR1 ; LOOP TILL DONE -; - ; SEND COUNT OF BLOCKS TO TRANSFER - ; 1 --> IDE_IO_COUNT - LD A,1 ; COUNT VALUE IS 1 BLOCK + LD A,(IY+IDE_LBA+1) +#IF (IDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF + CALL IDE_OUT + .DB IDE_REG_LBA1 ; + LD A,(IY+IDE_LBA+0) #IF (IDETRACE >= 3) - DCALL PC_SPACE - DCALL PRTHEXBYTE + CALL PC_SPACE + CALL PRTHEXBYTE #ENDIF + CALL IDE_OUT + .DB IDE_REG_LBA0 ; - DEC C ; PORT := IDE_IO_COUNT - OUT (C),A ; SEND IT + LD A,1 +#IF (IDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF + CALL IDE_OUT + .DB IDE_REG_COUNT ; #IF (DSKYENABLE) CALL IDE_DSKY @@ -697,9 +767,13 @@ IDE_RUNCMD: RET NZ ; BAIL OUT ON TIMEOUT ; LD A,(IDE_CMD) ; GET THE COMMAND - DCALL PC_SPACE - DCALL PRTHEXBYTE - OUT (IDE_IO_CMD),A ; SEND IT (STARTS EXECUTION) +#IF (IDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF + ;OUT (IDE_IO_CMD),A ; SEND IT (STARTS EXECUTION) + CALL IDE_OUT + .DB IDE_REG_CMD #IF (IDETRACE >= 3) PRTS(" -->$") #ENDIF @@ -717,81 +791,122 @@ IDE_GETBUF: #IF (IDETRACE >= 3) PRTS(" GETBUF$") #ENDIF - +; CALL IDE_WAITDRQ ; WAIT FOR BUFFER READY RET NZ ; BAIL OUT IF TIMEOUT - +; LD B,0 - -#IF (IDE8BIT | (IDEMODE == IDEMODE_DIDE)) - LD C,IDE_IO_DATA - INIR - INIR -;X1: -; NOP -; INI -; JR NZ,X1 -;X2: -; NOP -; INI -; JR NZ,X2 -#ELSE - LD C,IDE_IO_DATAHI +; + BIT 1,(IY+IDE_ACC) ; 8 BIT? + JR Z,IDE_GETBUF1 ; IF NOT, DO 16 BIT + CALL IDE_GETBUF8 ; DO 8 BIT + JR IDE_GETBUF2 +; IDE_GETBUF1: - IN A,(IDE_IO_DATALO) ; READ THE LO BYTE - LD (HL),A ; SAVE IN BUFFER - INC HL ; INC BUFFER POINTER - INI ; READ AND SAVE HI BYTE, INC HL, DEC B - JP NZ,IDE_GETBUF1 ; LOOP AS NEEDED -#ENDIF + CALL IDE_GETBUF16 +; +IDE_GETBUF2: CALL IDE_WAITRDY ; PROBLEMS IF THIS IS REMOVED! CALL IDE_GETRES JP NZ,IDE_IOERR RET ; +IDE_GETBUF8: + ; 8 BIT I/O + ;LD C,IDE_IO_DATA + LD C,(IY+IDE_IOBASE) + INIR + INIR + RET +; +IDE_GETBUF16: + ; 16 BIT I/O + ;LD C,IDE_IO_DATAHI + LD D,(IY+IDE_DATALO) + LD E,(IY+IDE_DATAHI) + CALL IDE_GETBUF16A ; GET FIRST 256 BYTES + CALL IDE_GETBUF16A ; GET SECOND 256 BYTES + RET +; +IDE_GETBUF16A: + LD C,D ; PORT FOR LSB + INI ; GET IT, SAVE IT, AND DEC B + LD C,E ; PORT FOR MSB + INI ; GET IT, SAVE IT, AND DEC B + JR NZ,IDE_GETBUF16A ; LOOP TILL COUNTER EXHAUSTED + RET +; ; ; IDE_PUTBUF: #IF (IDETRACE >= 3) - PRTS(" GETBUF$") + PRTS(" PUTBUF$") #ENDIF - +; CALL IDE_WAITDRQ ; WAIT FOR BUFFER READY RET NZ ; BAIL OUT IF TIMEOUT ; - ;LD HL,(IDE_DSKBUF) LD B,0 - -#IF (IDE8BIT | (IDEMODE == IDEMODE_DIDE)) - LD C,IDE_IO_DATA - OTIR - OTIR -#ELSE - LD C,IDE_IO_DATAHI +; + BIT 1,(IY+IDE_ACC) ; 8 BIT? + JR Z,IDE_PUTBUF1 ; IF NOT, DO 16 BIT + CALL IDE_PUTBUF8 ; DO 8 BIT + JR IDE_PUTBUF2 +; IDE_PUTBUF1: - LD A,(HL) ; GET THE LO BYTE AND KEEP IT IN A FOR LATER - INC HL ; BUMP TO NEXT BYTE IN BUFFER - OUTI ; WRITE HI BYTE, INC HL, DEC B - OUT (IDE_IO_DATALO),A ; NOW WRITE THE SAVED LO BYTE TO LO BYTE - JP NZ,IDE_PUTBUF1 ; LOOP AS NEEDED -#ENDIF + CALL IDE_PUTBUF16 +; +IDE_PUTBUF2: CALL IDE_WAITRDY ; PROBLEMS IF THIS IS REMOVED! CALL IDE_GETRES JP NZ,IDE_IOERR RET ; +IDE_PUTBUF8: + ; 8 BIT I/O + ;LD C,IDE_IO_DATA + LD C,(IY+IDE_IOBASE) + OTIR + OTIR + RET +; +IDE_PUTBUF16: + ; 16 BIT I/O + ;LD C,IDE_IO_DATAHI + LD D,(IY+IDE_DATALO) + LD E,(IY+IDE_DATAHI) + CALL IDE_PUTBUF16A ; PUT FIRST 256 BYTES + CALL IDE_PUTBUF16A ; PUT SECOND 256 BYTES + RET +; +IDE_PUTBUF16A: + LD C,D ; PORT FOR LSB + OUTI ; PUT IT AND DEC B + LD C,E ; PORT FOR MSB + OUTI ; PUT IT AND DEC B + JR NZ,IDE_PUTBUF16A ; LOOP TILL COUNTER EXHAUSTED + RET +; ; ; IDE_GETRES: - IN A,(IDE_IO_STAT) ; GET STATUS - DCALL PC_SPACE - DCALL PRTHEXBYTE + ;IN A,(IDE_IO_STAT) ; GET STATUS + CALL IDE_IN + .DB IDE_REG_STAT +#IF (IDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF AND %00000001 ; ERROR BIT SET? RET Z ; NOPE, RETURN WITH ZF ; - IN A,(IDE_IO_ERR) ; READ ERROR REGISTER - DCALL PC_SPACE - DCALL PRTHEXBYTE + ;IN A,(IDE_IO_ERR) ; READ ERROR REGISTER + CALL IDE_IN + .DB IDE_REG_ERR +#IF (IDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF OR $FF ; FORCE NZ TO SIGNAL ERROR RET ; RETURN ; @@ -802,44 +917,110 @@ IDE_GETRES: ; RESET ALL DEVICES ON BUS ; IDE_RESET: +#IF (IDETRACE >= 3) + CALL IDE_PRTPREFIX + PRTS(" RESET$") +#ENDIF +; +;#IF (IDEMODE == IDEMODE_MK4) +; + LD A,(IY+IDE_MODE) ; GET MODE + CP IDEMODE_MK4 ; MK4? + JR NZ,IDE_RESET1 ; IF NOT, BYPASS ; -#IF (IDEMODE == IDEMODE_MK4) ; USE HARDWARE RESET LINE +#IF (IDETRACE >= 3) + PRTS(" HARD$") +#ENDIF LD A,$80 ; HIGH BIT OF XAR IS IDE RESET - OUT (MK4_XAR),A + ;OUT (IDE_IO_XAR),A + CALL IDE_OUT + .DB IDE_REG_XAR LD DE,2 ; DELAY 32US (SPEC IS >= 25US) CALL VDELAY XOR A ; CLEAR RESET BIT - OUT (MK4_XAR),A -#ENDIF - -#IF ((IDEMODE == IDEMODE_RC) | (IDEMODE == IDEMODE_SMB)) + ;OUT (IDE_IO_XAR),A + CALL IDE_OUT + .DB IDE_REG_XAR +; +IDE_RESET1: +; +;#ENDIF +; +;#IF (IDEMODE == IDEMODE_RC) +; + LD A,(IY+IDE_MODE) ; GET MODE + CP IDEMODE_RC ; RC2014? + JR NZ,IDE_RESET2 ; IF NOT, BYPASS +; ; RC2014 CANNOT ADDRESS THE DEVICE CONTROL PORT AND ; HAS NO WAY TO PERFORM A HARD RESET FROM SOFTWARE, ; SO FAKE IT BY SETTING THE REGISTERS TO THE SAME ; VALUES THAT A RESET WOULD CAUSE. +#IF (IDETRACE >= 3) + PRTS(" FAKE$") +#ENDIF XOR A - OUT (IDE_IO_CYLLO),A - OUT (IDE_IO_CYLHI),A + ;OUT (IDE_IO_CYLLO),A + CALL IDE_OUT + .DB IDE_REG_CYLLO + ;OUT (IDE_IO_CYLHI),A + CALL IDE_OUT + .DB IDE_REG_CYLHI INC A - OUT (IDE_IO_COUNT),A - OUT (IDE_IO_SECT),A -#ENDIF - -#IF ((IDEMODE != IDEMODE_MK4) & (IDEMODE != IDEMODE_RC) & (IDEMODE != IDEMODE_SMB)) + ;OUT (IDE_IO_COUNT),A + CALL IDE_OUT + .DB IDE_REG_COUNT + ;OUT (IDE_IO_SECT),A + CALL IDE_OUT + .DB IDE_REG_SECT +; +IDE_RESET2: +; +;#ENDIF +; +;#IF ((IDEMODE != IDEMODE_MK4) & (IDEMODE != IDEMODE_RC)) +; + LD A,(IY+IDE_MODE) ; GET MODE + CP IDEMODE_MK4 ; MK4? + JR Z,IDE_RESET3 ; IF SO, BYPASS + CP IDEMODE_RC ; RC2014? + JR Z,IDE_RESET3 ; IF SO, BYPASS +; ; INITIATE SOFT RESET - LD A,%00001110 ; NO INTERRUPTS, ASSERT RESET BOTH DRIVES - OUT (IDE_IO_CTRL),A +#IF (IDETRACE >= 3) + PRTS(" SOFT$") #ENDIF + LD A,%00001110 ; NO INTERRUPTS, ASSERT RESET BOTH DRIVES + ;OUT (IDE_IO_CTRL),A + CALL IDE_OUT + .DB IDE_REG_CTRL +; +IDE_RESET3: +; +;#ENDIF ; LD DE,2 ; DELAY 32US (SPEC IS >= 25US) CALL VDELAY ; -#IF ((IDEMODE != IDEMODE_RC) & (IDEMODE != IDEMODE_SMB)) +;#IF (IDEMODE != IDEMODE_RC) +; + LD A,(IY+IDE_MODE) ; GET MODE + CP IDEMODE_RC ; RC2014? + JR Z,IDE_RESET4 ; IF SO, BYPASS +; ; CONFIGURE OPERATION AND END SOFT RESET - LD A,%00001010 ; NO INTERRUPTS, DEASSERT RESET - OUT (IDE_IO_CTRL),A ; PUSH TO REGISTER +#IF (IDETRACE >= 3) + PRTS(" CONFIG$") #ENDIF + LD A,%00001010 ; NO INTERRUPTS, DEASSERT RESET + ;OUT (IDE_IO_CTRL),A ; PUSH TO REGISTER + CALL IDE_OUT + .DB IDE_REG_CTRL +; +IDE_RESET4: +; +;#ENDIF ; ; SPEC ALLOWS UP TO 450MS FOR DEVICES TO ASSERT THEIR PRESENCE ; VIA -DASP. I ENCOUNTER PROBLEMS LATER ON IF I DON'T WAIT HERE @@ -851,28 +1032,16 @@ IDE_RESET: LD DE,150000/16 ; ~???MS CALL VDELAY ; - ;; CLEAR OUT ALL DATA (FOR ALL UNITS) - ;LD HL,IDE_UDATA - ;LD BC,IDE_UDLEN - ;XOR A - ;CALL FILL -; - ;LD A,(IDE_UNIT) ; GET THE CURRENT UNIT SELECTION - ;PUSH AF ; AND SAVE IT - PUSH IY ; SAVE CURRENT DEVICE CFG PTR - - ; PROBE / INITIALIZE ALL UNITS - LD B,IDE_DEVCNT ; NUMBER OF UNITS TO TRY - LD IY,IDE_CFGTBL ; START OF CFG TABLE -IDE_RESET1: - PUSH BC ; SAVE LOOP CONTROL - CALL IDE_INITUNIT ; PROBE/INIT UNIT - LD BC,IDE_CFGSIZ ; SIZE OF CFG ENTRY - ADD IY,BC ; BUMP IY TO NEXT ENTRY - POP BC ; RESTORE BC - DJNZ IDE_RESET1 ; LOOP AS NEEDED -; - POP IY ; RECOVER DEVICE CFG PTR + ; INITIALIZE THE INDIVIDUAL UNITS (MASTER AND SLAVE). + ; BASED ON TESTING, IT APPEARS THAT THE MASTER UNIT MUST + ; BE DONE FIRST OR THIS BEHAVES BADLY. + PUSH IY ; SAVE CFG PTR + BIT 0,(IY+IDE_ACC) ; MASTER? + CALL Z,IDE_GOPARTNER ; IF NOT, SWITCH TO MASTER + CALL IDE_INITUNIT ; INIT CURRENT UNIT + CALL IDE_GOPARTNER ; POINT TO SLAVE + CALL IDE_INITUNIT ; INIT PARTNER UNIT + POP IY ; RECOVER ORIG CFG PTR ; XOR A ; SIGNAL SUCCESS RET ; AND DONE @@ -883,19 +1052,14 @@ IDE_INITUNIT: CALL IDE_SELUNIT ; SELECT UNIT RET NZ ; ABORT IF ERROR - ;LD HL,IDE_TIMEOUT ; POINT TO TIMEOUT - ;LD (HL),IDE_TOFAST ; USE FAST TIMEOUT DURING INIT - LD HL,IDE_TIMEOUT ; POINT TO TIMEOUT LD (HL),IDE_TONORM ; SET NORMAL TIMEOUT CALL IDE_PROBE ; DO PROBE - CALL Z,IDE_INITDEV ; IF FOUND, ATTEMPT TO INIT DEVICE + RET NZ ; JUST RETURN IF NOTHING THERE - ;LD HL,IDE_TIMEOUT ; POINT TO TIMEOUT - ;LD (HL),IDE_TONORM ; BACK TO NORMAL TIMEOUT - - RET + CALL IDE_INITDEV ; IF FOUND, ATTEMPT TO INIT DEVICE + RET ; DONE ; ; TAKE ANY ACTIONS REQUIRED TO SELECT DESIRED PHYSICAL UNIT ; UNIT IS SPECIFIED IN IDE_UNIT @@ -907,22 +1071,16 @@ IDE_SELUNIT: PRTS(" SELUNIT$") #ENDIF ; -#IF (IDEMODE == IDEMODE_DIDE) - ; SELECT PRIMARY/SECONDARY INTERFACE FOR DIDE HARDWARE -#ENDIF - PUSH HL ; SAVE HL, IT IS DESTROYED BELOW - PUSH IY - POP BC - LD A,(IY+IDE_DEV) ; GET DEVICE - AND $01 ; LS BIT DETERMINES MASTER/SLAVE - LD HL,IDE_DRVSEL - CALL ADDHLA - LD A,(HL) ; LOAD DRIVE/HEAD VALUE - POP HL ; RECOVER HL + BIT 0,(IY+IDE_ACC) ; MASTER? + JR Z,IDE_SELUNIT1 ; HANDLE SLAVE + LD A,IDE_DRVMASTER ; MASTER + JR IDE_SELUNIT2 +IDE_SELUNIT1: + LD A,IDE_DRVSLAVE ; SLAVE +IDE_SELUNIT2: LD (IDE_DRVHD),A ; SAVE IT -; - XOR A ; SIGNAL SUCCESS - RET ; AND DONE + XOR A ; SUCCESS + RET ; ; ; @@ -933,22 +1091,32 @@ IDE_PROBE: #ENDIF ; LD A,(IDE_DRVHD) - OUT (IDE_IO_DRVHD),A - DCALL PC_SPACE - DCALL PRTHEXBYTE + ;OUT (IDE_IO_DRVHD),A + CALL IDE_OUT + .DB IDE_REG_DRVHD +#IF (IDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF CALL DELAY ; DELAY ~16US ; - LD C,IDE_IO_STAT - IN A,(C) - DCALL PC_SPACE - DCALL PRTHEXBYTE + ;LD C,IDE_IO_STAT + ;IN A,(C) + CALL IDE_IN + .DB IDE_REG_STAT +#IF (IDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF CP $FF JP Z,IDE_NOMEDIA CP $78 JP Z,IDE_NOMEDIA ; - DCALL IDE_REGDUMP +#IF (IDETRACE >= 3) + CALL IDE_REGDUMP +#ENDIF ; ;JR IDE_PROBE1 ; *DEBUG* ; @@ -956,34 +1124,64 @@ IDE_PROBE0: CALL IDE_WAITBSY ; WAIT FOR BUSY TO CLEAR JP NZ,IDE_NOMEDIA ; CONVERT TIMEOUT TO NO MEDIA AND RETURN ; - DCALL IDE_REGDUMP +#IF (IDETRACE >= 3) + CALL IDE_REGDUMP +#ENDIF ; ; CHECK STATUS - IN A,(IDE_IO_STAT) ; GET STATUS - DCALL PC_SPACE - DCALL PRTHEXBYTE ; IF DEBUG, PRINT STATUS + ;IN A,(IDE_IO_STAT) ; GET STATUS + CALL IDE_IN + .DB IDE_REG_STAT +#IF (IDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE ; IF DEBUG, PRINT STATUS +#ENDIF OR A ; SET FLAGS TO TEST FOR ZERO JP Z,IDE_NOMEDIA ; ; CHECK SIGNATURE - DCALL PC_SPACE - IN A,(IDE_IO_COUNT) - DCALL PRTHEXBYTE +#IF (IDETRACE >= 3) + CALL PC_SPACE +#ENDIF + ;IN A,(IDE_IO_COUNT) + CALL IDE_IN + .DB IDE_REG_COUNT +#IF (IDETRACE >= 3) + CALL PRTHEXBYTE +#ENDIF CP $01 JP NZ,IDE_NOMEDIA - DCALL PC_SPACE - IN A,(IDE_IO_SECT) - DCALL PRTHEXBYTE +#IF (IDETRACE >= 3) + CALL PC_SPACE +#ENDIF + ;IN A,(IDE_IO_SECT) + CALL IDE_IN + .DB IDE_REG_SECT +#IF (IDETRACE >= 3) + CALL PRTHEXBYTE +#ENDIF CP $01 JP NZ,IDE_NOMEDIA - DCALL PC_SPACE - IN A,(IDE_IO_CYLLO) - DCALL PRTHEXBYTE +#IF (IDETRACE >= 3) + CALL PC_SPACE +#ENDIF + ;IN A,(IDE_IO_CYLLO) + CALL IDE_IN + .DB IDE_REG_CYLLO +#IF (IDETRACE >= 3) + CALL PRTHEXBYTE +#ENDIF CP $00 JP NZ,IDE_NOMEDIA - DCALL PC_SPACE - IN A,(IDE_IO_CYLHI) - DCALL PRTHEXBYTE +#IF (IDETRACE >= 3) + CALL PC_SPACE +#ENDIF + ;IN A,(IDE_IO_CYLHI) + CALL IDE_IN + .DB IDE_REG_CYLHI +#IF (IDETRACE >= 3) + CALL PRTHEXBYTE +#ENDIF CP $00 JP NZ,IDE_NOMEDIA ; @@ -1002,42 +1200,32 @@ IDE_INITDEV: OR A ; SET FLAGS JP Z,IDE_NOMEDIA ; EXIT SETTING NO MEDIA STATUS ; - ; CLEAR OUT UNIT SPECIFIC DATA, BUT PRESERVE THE EXISTING - ; VALUE OF THE UNIT TYPE WHICH WAS ESTABLISHED BY THE DEVICE - ; PROBES WHEN THE IDE BUS WAS RESET - ;PUSH AF ; SAVE UNIT TYPE VALUE FROM ABOVE - ;PUSH HL ; SAVE UNIT TYPE FIELD POINTER - ;IDE_DPTR(0) ; SET HL TO START OF UNIT DATA - ;LD BC,IDE_UDLEN - ;XOR A - ;CALL FILL - ;POP HL ; RECOVER UNIT TYPE FIELD POINTER - ;POP AF ; RECOVER UNIT TYPE VALUE - ;LD (HL),A ; AND PUT IT BACK -; -#IF (IDE8BIT) + BIT 1,(IY+IDE_ACC) ; 8 BIT ACCESS? + JR Z,IDE_INITDEV0 ; NO, DO 16 BIT INIT LD A,IDE_FEAT_ENABLE8BIT ; FEATURE VALUE = ENABLE 8-BIT PIO -#ELSE - LD A,IDE_FEAT_DISABLE8BIT ; FEATURE VALUE = DISABLE 8-BIT PIO -#ENDIF - CALL IDE_SETFEAT ; SET FEATURE - -#IF (IDE8BIT) + RET NZ ; BAIL OUT ON ERROR + JR IDE_INITDEV00 ; CONTINUE +; +IDE_INITDEV0: ; "REAL" IDE DRIVES MAY NOT ACCEPT THE DISABLE8BIT FEATURE COMMAND, ; SO IT IS ONLY AN ERROR IF WE ARE ATTEMPTING TO ENABLE8BIT. - ; CREDIT TO ED BRINDLEY FOR THIS CORRECTION. - RET NZ ; BAIL OUT ON ERROR -#ENDIF + ; CREDIT TO ED BRINDLEY FOR THIS CORRECTION. SO ERROR RETURN IGNORED HERE. + LD A,IDE_FEAT_DISABLE8BIT ; FEATURE VALUE = ENABLE 8-BIT PIO + CALL IDE_SETFEAT ; SET FEATURE, IGNORE ERRORS +; +IDE_INITDEV00: ; CALL IDE_IDENTIFY ; EXECUTE IDENTIFY COMMAND RET NZ ; BAIL OUT ON ERROR ; LD DE,HB_WRKBUF ; POINT TO BUFFER - DCALL DUMP_BUFFER ; DUMP IT IF DEBUGGING +#IF (IDETRACE >= 3) + CALL DUMP_BUFFER ; DUMP IT IF DEBUGGING +#ENDIF ; XOR A - LD (IY+IDE_FLAGS),0 ; CLEAR FLAGS + LD (IY+IDE_MED),0 ; CLEAR FLAGS ; DETERMINE IF CF DEVICE LD HL,HB_WRKBUF ; FIRST WORD OF IDENTIFY DATA HAS CF FLAG @@ -1048,14 +1236,14 @@ IDE_INITDEV: LD A,$84 ; SECOND BYTE OF MARKER IS $84 CP (HL) ; COMPARE JR NZ,IDE_INITDEV1 ; IF NOT MATCH, NOT CF - SET 0,(IY+IDE_FLAGS) ; SET FLAGS BIT FOR CF MEDIA + SET 0,(IY+IDE_MED) ; SET FLAGS BIT FOR CF MEDIA ; IDE_INITDEV1: ; DETERMINE IF LBA CAPABLE LD A,(HB_WRKBUF+98+1) ; GET BYTE WITH LBA BIT FROM BUFFER BIT 1,A ; CHECK THE LBA BIT JR Z,IDE_INITDEV2 ; NOT SET, BYPASS - SET 1,(IY+IDE_FLAGS) ; SET FLAGS BIT FOR LBA + SET 1,(IY+IDE_MED) ; SET FLAGS BIT FOR LBA ; IDE_INITDEV2: ; GET DEVICE CAPACITY AND SAVE IT @@ -1075,15 +1263,24 @@ IDE_INITDEV2: ; RET ; RETURN, A=0, Z SET ; +; SWITCH IY POINTER FROM CURRENT UNIT CFG TO PARTNER UNIT CFG ; +IDE_GOPARTNER: + PUSH HL ; SAVE HL + LD L,(IY+IDE_PARTNER) ; GET PARTNER ENTRY + LD H,(IY+IDE_PARTNER+1) ; ... + PUSH HL ; MOVE HL + POP IY ; ... TO IY + POP HL ; RESTORE INCOMING HL + RET ; AND DONE +; +; CHECK CURRENT DEVICE FOR ERROR STATUS AND ATTEMPT TO RECOVER +; VIA RESET IF DEVICE IS IN ERROR. ; -IDE_CHKDEVICE: +IDE_CHKERR: LD A,(IY+IDE_STAT) ; GET STATUS OR A ; SET FLAGS - RET Z ; RETURN IF ALL IS WELL -; - ; ATTEMPT TO REINITIALIZE HERE??? - JP IDE_ERR + CALL NZ,IDE_RESET ; IF ERROR STATUS, RESET BUS RET ; ; @@ -1094,7 +1291,9 @@ IDE_WAITRDY: IDE_WAITRDY1: LD DE,(IDE_TOSCALER) ; CPU SPPED SCALER TO INNER LOOP VAR IDE_WAITRDY2: - IN A,(IDE_IO_STAT) ; READ STATUS + ;IN A,(IDE_IO_STAT) ; READ STATUS + CALL IDE_IN + .DB IDE_REG_STAT LD C,A ; SAVE IT AND %11000000 ; ISOLATE BUSY AND RDY BITS XOR %01000000 ; WE WANT BUSY(7) TO BE 0 AND RDY(6) TO BE 1 @@ -1114,7 +1313,9 @@ IDE_WAITDRQ: IDE_WAITDRQ1: LD DE,(IDE_TOSCALER) ; CPU SPPED SCALER TO INNER LOOP VAR IDE_WAITDRQ2: - IN A,(IDE_IO_STAT) ; WAIT FOR DRIVE'S 512 BYTE READ BUFFER + ;IN A,(IDE_IO_STAT) ; WAIT FOR DRIVE'S 512 BYTE READ BUFFER + CALL IDE_IN + .DB IDE_REG_STAT LD C,A ; SAVE IT AND %10001000 ; TO FILL (OR READY TO FILL) XOR %00001000 @@ -1134,7 +1335,9 @@ IDE_WAITBSY: IDE_WAITBSY1: LD DE,(IDE_TOSCALER) ; CPU SPPED SCALER TO INNER LOOP VAR IDE_WAITBSY2: - IN A,(IDE_IO_STAT) ; WAIT FOR DRIVE'S 512 BYTE READ BUFFER ; 11TS + ;IN A,(IDE_IO_STAT) ; WAIT FOR DRIVE'S 512 BYTE READ BUFFER ; 11TS + CALL IDE_IN ; 17TS + ???TS + .DB IDE_REG_STAT ; 0TS LD C,A ; SAVE IT ; 4TS AND %10000000 ; TO FILL (OR READY TO FILL) ; 7TS RET Z ; 5TS @@ -1143,7 +1346,39 @@ IDE_WAITBSY2: OR E ; 4TS JR NZ,IDE_WAITBSY2 ; 12TS DJNZ IDE_WAITBSY1 ; ----- - JP IDE_BSYTO ; EXIT WITH BSYTO ERR ; 52TS + JP IDE_BSYTO ; EXIT WITH BSYTO ERR ; ??TS +; +; READ A VALUE FROM THE DEVICE POINTED TO BY IY AND RETURN IT IN A +; +IDE_IN: + EX (SP),HL ; GET PARM POINTER + PUSH BC + LD A,(HL) + INC HL + LD C,(IY+IDE_IOBASE) + ADD A,C + LD C,A + IN A,(C) + POP BC + EX (SP),HL ; RESTORE STACK + RET +; +; OUTPUT VALUE IN A TO THE DEVICE POINTED TO BY IY +; +IDE_OUT: + EX (SP),HL ; GET PARM POINTER + PUSH BC + PUSH AF + LD A,(HL) + INC HL + LD C,(IY+IDE_IOBASE) + ADD A,C + LD C,A + POP AF + OUT (C),A + POP BC + EX (SP),HL ; RESTORE STACK + RET ; ;============================================================================= ; ERROR HANDLING AND DIAGNOSTICS @@ -1249,7 +1484,10 @@ IDE_REGDUMP: PUSH BC CALL PC_SPACE CALL PC_LBKT - LD C,IDE_IO_CMD + ;LD C,IDE_IO_CMD + LD A,(IY+IDE_IOBASE) + ADD A,IDE_REG_CMD + LD C,A LD B,7 IDE_REGDUMP1: IN A,(C) @@ -1281,16 +1519,20 @@ IDE_PRTPREFIX: #IF (DSKYENABLE) IDE_DSKY: LD HL,DSKY_HEXBUF ; POINT TO DSKY BUFFER - IN A,(IDE_IO_DRVHD) ; GET DRIVE/HEAD + CALL IDE_IN + .DB IDE_REG_DRVHD LD (HL),A ; SAVE IN BUFFER INC HL ; INCREMENT BUFFER POINTER - IN A,(IDE_IO_CYLHI) ; GET DRIVE/HEAD + CALL IDE_IN + .DB IDE_REG_CYLHI LD (HL),A ; SAVE IN BUFFER INC HL ; INCREMENT BUFFER POINTER - IN A,(IDE_IO_CYLLO) ; GET DRIVE/HEAD + CALL IDE_IN + .DB IDE_REG_CYLLO LD (HL),A ; SAVE IN BUFFER INC HL ; INCREMENT BUFFER POINTER - IN A,(IDE_IO_SECT) ; GET DRIVE/HEAD + CALL IDE_IN + .DB IDE_REG_SECT LD (HL),A ; SAVE IN BUFFER CALL DSKY_HEXOUT ; SEND IT TO DSKY RET @@ -1311,6 +1553,13 @@ IDE_STR_STBSYTO .TEXT "BUSY TIMEOUT$" IDE_STR_STUNK .TEXT "UNKNOWN ERROR$" ; IDE_STR_NO .TEXT "NO$" +IDE_STR_NOHW .TEXT "NOT PRESENT$" +IDE_STR_8BIT .TEXT " 8-BIT$" +; +IDE_STR_MODE_DIO .TEXT "DIO$" +IDE_STR_MODE_DIDE .TEXT "DIDE$" +IDE_STR_MODE_MK4 .TEXT "MK4$" +IDE_STR_MODE_RC .TEXT "RC$" ; ;============================================================================= ; DATA STORAGE @@ -1324,3 +1573,5 @@ IDE_IOFNADR .DW 0 ; PENDING IO FUNCTION ADDRESS IDE_DRVHD .DB 0 ; CURRENT DRIVE/HEAD MASK ; IDE_DSKBUF .DW 0 ; ACTIVE DISK BUFFER +; +IDE_DEVNUM .DB 0 ; TEMP DEVICE NUM USED DURING INIT diff --git a/Source/HBIOS/imgpad0.asm b/Source/HBIOS/imgpad0.asm index e2822918..2aa54841 100644 --- a/Source/HBIOS/imgpad0.asm +++ b/Source/HBIOS/imgpad0.asm @@ -1,6 +1,6 @@ #INCLUDE "std.asm" ; -SLACK .EQU ($8000-BAS_SIZ-TBC_SIZ-FTH_SIZ) +SLACK .EQU ($8000-BAS_SIZ-TBC_SIZ-FTH_SIZ-GAM_SIZ-USR_SIZ) .FILL SLACK,00H ; MON_STACK .EQU $ diff --git a/Source/HBIOS/intrtc.asm b/Source/HBIOS/intrtc.asm new file mode 100644 index 00000000..0414a286 --- /dev/null +++ b/Source/HBIOS/intrtc.asm @@ -0,0 +1,237 @@ +; +;================================================================================================== +; SYSTEM TIMER BASED CLOCK DRIVER +;================================================================================================== +; +INTRTC_BUFSIZ .EQU 6 ; SIX BYTE BUFFER (YYMMDDHHMMSS) +; +; RTC DEVICE INITIALIZATION ENTRY +; +INTRTC_INIT: + LD A,(RTC_DISPACT) ; RTC DISPATCHER ALREADY SET? + OR A ; SET FLAGS + RET NZ ; IF ALREADY ACTIVE, ABORT +; + CALL NEWLINE ; FORMATTING + PRTS("INTRTC: $") +; + ; HOOK THE HBIOS SECONDS VECTOR + LD HL,(VEC_SECOND+1) ; GET CUR SECONDS VECTOR + LD (INTRTC_VEC),HL ; SAVE IT INTERNALLY + LD HL,INTRTC_INT ; OUR SECONDS INT ENTRY + LD (VEC_SECOND+1),HL ; REPLACE IT +; + ; DISPLAY CURRENT TIME + CALL INTRTC_GETTIM0 + LD HL,INTRTC_BCDBUF ; POINT TO BCD BUF + CALL PRTDT +; + LD BC,INTRTC_DISPATCH + CALL RTC_SETDISP +; + XOR A ; SIGNAL SUCCESS + RET +; +; RTC DEVICE FUNCTION DISPATCH ENTRY +; A: RESULT (OUT), 0=OK, Z=OK, NZ=ERR +; B: FUNCTION (IN) +; +INTRTC_DISPATCH: + LD A,B ; GET REQUESTED FUNCTION + AND $0F ; ISOLATE SUB-FUNCTION + JP Z,INTRTC_GETTIM ; GET TIME + DEC A + JP Z,INTRTC_SETTIM ; SET TIME + DEC A + JP Z,INTRTC_GETBYT ; GET NVRAM BYTE VALUE + DEC A + JP Z,INTRTC_SETBYT ; SET NVRAM BYTE VALUE + DEC A + JP Z,INTRTC_GETBLK ; GET NVRAM DATA BLOCK VALUES + DEC A + JP Z,INTRTC_SETBLK ; SET NVRAM DATA BLOCK VALUES + CALL PANIC +; +; NVRAM FUNCTIONS ARE NOT AVAILABLE IN SIMULATOR +; +INTRTC_GETBYT: +INTRTC_SETBYT: +INTRTC_GETBLK: +INTRTC_SETBLK: + CALL PANIC +; +; RTC GET TIME +; A: RESULT (OUT), 0=OK, Z=OK, NZ=ERR +; HL: DATE/TIME BUFFER (OUT) +; BUFFER FORMAT IS BCD: YYMMDDHHMMSS +; 24 HOUR TIME FORMAT IS ASSUMED +; +INTRTC_GETTIM: + ; GET THE TIME INTO TEMP BUF + PUSH HL ; SAVE PTR TO CALLERS BUFFER + CALL INTRTC_GETTIM0 ; GET TIME TO WORK BUFFER +; + ; NOW COPY TO REAL DESTINATION (INTERBANK SAFE) + LD A,BID_BIOS ; COPY FROM BIOS BANK + LD (HB_SRCBNK),A ; SET IT + LD A,(HB_INVBNK) ; COPY TO CURRENT USER BANK + LD (HB_DSTBNK),A ; SET IT + LD HL,INTRTC_BCDBUF ; SOURCE ADR + POP DE ; DEST ADR + LD BC,INTRTC_BUFSIZ ; LENGTH + CALL HB_BNKCPY ; COPY THE CLOCK DATA +; + LD DE,60 ; DELAY 60 * 16US = ~1MS + CALL VDELAY ; SLOW DOWN SIMH FOR CLOCK TICKING TEST + XOR A ; SIGNAL SUCCESS + RET ; AND RETURN +; +INTRTC_GETTIM0: + LD HL,INTRTC_BINBUF ; FROM BINARY BUFFER + LD DE,INTRTC_BCDBUF ; TO BCD BUFFER + HB_DI + CALL INTRTC_BIN2BCD ; COPY AND CONVERT + HB_EI + RET +; +; RTC SET TIME +; A: RESULT (OUT), 0=OK, Z=OK, NZ=ERR +; HL: DATE/TIME BUFFER (IN) +; BUFFER FORMAT IS BCD: YYMMDDHHMMSSWW +; 24 HOUR TIME FORMAT IS ASSUMED +; +INTRTC_SETTIM: + ; COPY TO BCD BUF + LD A,(HB_INVBNK) ; COPY FROM CURRENT USER BANK + LD (HB_SRCBNK),A ; SET IT + LD A,BID_BIOS ; COPY TO BIOS BANK + LD (HB_DSTBNK),A ; SET IT + LD DE,INTRTC_BCDBUF ; DEST ADR + LD BC,INTRTC_BUFSIZ ; LENGTH + CALL HB_BNKCPY ; COPY THE CLOCK DATA +; + LD HL,INTRTC_BCDBUF ; FROM BCD BUF + LD DE,INTRTC_BINBUF ; TO BIN BUF + HB_DI + CALL INTRTC_BCD2BIN ; COPY AND CONVERT + HB_EI +; + XOR A ; SIGNAL SUCCESS + RET ; AND RETURN +; +; HANDLER FOR TIMER SECONDS INTERRUPT +; +INTRTC_INT: + LD HL,INTRTC_BINBUF + INTRTC_BUFSIZ - 1 + INC (HL) ; INC SECONDS + LD A,59 ; MAX VALUE + CP (HL) ; OVERFLOW? + JR NC,INTRTC_INTX ; NOPE, DONE + LD (HL),0 ; BACK TO ZERO + + DEC HL ; POINT TO MINUTES + INC (HL) ; INCREMENT MINUTE + CP (HL) ; OVERFLOW? + JR NC,INTRTC_INTX ; NOPE, DONE + LD (HL),0 ; BACK TO ZERO + + DEC HL ; POINT TO HOURS + INC (HL) ; INCREMENT HOURS + LD A,23 ; MAX VALUE + CP (HL) ; OVERFLOW? + JR NC,INTRTC_INTX ; NOPE, DONE + LD (HL),0 ; BACK TO ZERO + + DEC HL ; POINT TO DATE + LD A,(INTRTC_MO) ; GET CURRENT MONTH + DEC A ; ZERO OFFSET + LD DE,INTRTC_MONTBL ; POINT TO DAYS IN MON TBL + ADD A,E ; ADD OFFSET + LD E,A ; BACK TO E + JR NC,INTRTC_INT1 ; NO CARRY, SKIP + INC D ; HANDLE CARRY +INTRTC_INT1: + LD A,(DE) ; A := DAYS IN MONTH + LD C,A ; COPY TO C FOR LATER + LD A,(INTRTC_MO) ; GET CURRENT MONTH + CP 2 ; FEBRUARY? + JR NZ,INTRTC_INT2 ; IF NOT, NOT LEAY, SKIP + LD A,(INTRTC_YR) ; GET CURRENT YEAR + AND $03 ; CHECK FOR LEAP + JR NZ,INTRTC_INT2 ; IF NOT LEAP, SKIP AHEAD + INC C ; BUMP DAYS IN FEB FOR LEAP +INTRTC_INT2: + INC (HL) ; INCREMENT DATE + LD A,C ; A := TRUE DAYS IN MONTH + 1 + CP (HL) ; OVERFLOW? + JR NZ,INTRTC_INTX ; NOPE, DONE + LD (HL),1 ; BACK TO DAY ONE + + DEC HL ; POINT TO MONTH + INC (HL) ; INCREMENT MONTH + LD A,13 ; PAST MAX? + CP (HL) ; OVERFLOW? + JR NZ,INTRTC_INTX ; NOPE, DONE + LD (HL),1 ; BACK TO MONTH ONE + + DEC HL ; POINT TO YEAR + INC (HL) ; INCREMENT YEAR + LD A,100 ; PAST MAX? + CP (HL) ; OVERFLOW? + JR NZ,INTRTC_INTX ; NOPE, DONE + LD (HL),0 ; BACK TO YEAR ZERO +INTRTC_INTX: + JP PANIC +INTRTC_VEC .EQU $-2 +; +; CONVERT FROM BINARY BUF (HL) TO BCD BUF (DE) +; +INTRTC_BIN2BCD: + LD B,INTRTC_BUFSIZ +INTRTC_BIN2BCD1: + LD A,(HL) + CALL BYTE2BCD + LD (DE),A + INC HL + INC DE + DJNZ INTRTC_BIN2BCD1 + RET +; +; CONVERT FROM BCD BUF (HL) TO BINARY BUF (DE) +; +INTRTC_BCD2BIN + LD B,INTRTC_BUFSIZ +INTRTC_BCD2BIN1: + LD A,(HL) + CALL BCD2BYTE + LD (DE),A + INC HL + INC DE + DJNZ INTRTC_BCD2BIN1 + RET +; +; WORKING VARIABLES +; +INTRTC_BINBUF: ; ALL IN BINARY +INTRTC_YR .DB 20 +INTRTC_MO .DB 01 +INTRTC_DT .DB 01 +INTRTC_HH .DB 00 +INTRTC_MM .DB 00 +INTRTC_SS .DB 00 +; +INTRTC_BCDBUF .FILL INTRTC_BUFSIZ +; +INTRTC_MONTBL: ; DAYS IN MONTH + 1 + .DB 32 ; JANUARY + .DB 29 ; FEBRUARY (NON-LEAP) + .DB 32 ; MARCH + .DB 31 ; APRIL + .DB 32 ; MAY + .DB 31 ; JUNE + .DB 32 ; JULY + .DB 32 ; AUGUST + .DB 31 ; SEPTEMBER + .DB 32 ; OCTOBER + .DB 31 ; NOVEMBER + .DB 32 ; DECEMBER diff --git a/Source/HBIOS/kbd.asm b/Source/HBIOS/kbd.asm index 2db34136..2d39197c 100644 --- a/Source/HBIOS/kbd.asm +++ b/Source/HBIOS/kbd.asm @@ -674,10 +674,11 @@ KBD_DECNEW: ; START NEW KEYPRESS (CLEAR ALL STATUS BITS) LD (KBD_STATUS),A ; CLEAR STATUS JP KBD_DEC1 ; RESTART THE ENGINE ; -;__________________________________________________________________________________________________ -; MAPPING TABLES +#IF (KBDKBLOUT == KBD_US) ;__________________________________________________________________________________________________ ; +; MAPPING TABLES US/ENGLISH +;__________________________________________________________________________________________________ KBD_MAPSTD: ; SCANCODE IS INDEX INTO TABLE TO RESULTANT LOOKUP KEYCODE .DB $FF,$E8,$FF,$E4,$E2,$E0,$E1,$EB,$FF,$E9,$E7,$E5,$E3,$09,'`',$FF .DB $FF,$B4,$B0,$FF,$B2,'q','1',$FF,$FF,$FF,'z','s','a','w','2',$FF @@ -713,6 +714,54 @@ KBD_MAPEXT: ; PAIRS ARE [SCANCODE,KEYCODE] FOR EXTENDED SCANCODES KBD_MAPNUMPAD: ; KEYCODE TRANSLATION FROM NUMPAD RANGE TO STD ASCII/KEYCODES .DB $F3,$F7,$F5,$F8,$FF,$F9,$F2,$F6,$F4,$F0,$F1,$2F,$2A,$2D,$2B,$0D .DB $31,$32,$33,$34,$35,$36,$37,$38,$39,$30,$2E,$2F,$2A,$2D,$2B,$0D +#ENDIF +#IF (KBDKBLOUT == KBD_DE) +;__________________________________________________________________________________________________ +; +; MAPPING TABLES GERMAN +;__________________________________________________________________________________________________ +; +KBD_MAPSTD: ; SCANCODE IS INDEX INTO TABLE TO RESULTANT LOOKUP KEYCODE ROW + +; Column 0 1 2 3 4 5 6 7 8 9 A B C D E F ; Special adjustments listed below + .DB $FF,$E8,$FF,$E4,$E2,$E0,$E1,$EB,$FF,$E9,$E7,$E5,$E3,$09,'^',$FF ;0 for German keyboard keys that give + .DB $FF,$B4,$B0,$FF,$B2,'q','1',$FF,$FF,$FF,'y','s','a','w','2',$FF ;1 different characters than are printed + .DB $FF,'c','x','d','e','4','3',$FF,$FF,' ','v','f','t','r','5',$FF ;2 on the keys. + .DB $FF,'n','b','h','g','z','6',$FF,$FF,$FF,'m','j','u','7','8',$FF ;3 'german key' --> 'new occupied with' + .DB $FF,',','k','i','o','0','9',$FF,$FF,'.','-','l','[','p',$5C,$FF ;4 Assembler ERROR: '\'-->$5C ; 'ö'-->'[' + .DB $FF,$FF,'@',$FF,']','|',$FF,$FF,$BC,$B1,$0D,'+',$FF,'#',$FF,$FF ;5 'ä'-->'@' ; 'ü'-->']' + .DB $FF,'<',$FF,$FF,$FF,$FF,$08,$FF,$FF,$C0,$FF,$C3,$C6,'<',$FF,$FF ;6 + .DB $C9,$CA,$C1,$C4,$C5,$C7,$1B,$BD,$FA,$CE,$C2,$CD,$CC,$C8,$BE,$FF ;7 + .DB $FF,$FF,$FF,$E6,$EC ;8 + +KBD_MAPSIZ .EQU ($ - KBD_MAPSTD) +; +KBD_MAPSHIFT: ; SCANCODE IS INDEX INTO TABLE TO RESULTANT LOOKUP KEYCODE WHEN SHIFT ACTIVE + + .DB $FF,$E8,$FF,$E4,$E2,$E0,$E1,$EB,$FF,$E9,$E7,$E5,$E3,$09,'~',$FF ; '°' --> '~' + .DB $FF,$B4,$B0,$FF,$B2,'Q','!',$FF,$FF,$FF,'Y','S','A','W',$22,$FF + .DB $FF,'C','X','D','E','$',$20,$FF,$FF,' ','V','F','T','R','%',$FF ; '§'-->$20; '§'=Paragraph not used in CP/M + .DB $FF,'N','B','H','G','Z','&',$FF,$FF,$FF,'M','J','U','/','(',$FF + .DB $FF,';','K','I','O','=',')',$FF,$FF,':','_','L','{','P','?',$FF ; 'Ö'-->'{' + .DB $FF,$FF,'@',$FF,'}','`',$FF,$FF,$BC,$B1,$0D,'*',$FF,$27,$FF,$FF ; 'Ä'-->'@' ; 'Ü'-->'}' + .DB $FF,'>',$FF,$FF,$FF,$FF,$08,$FF,$FF,$D0,$FF,$D3,$D6,'>',$FF,$FF + .DB $D9,$DA,$D1,$D4,$D5,$D7,$1B,$BD,$FA,$DE,$D2,$DD,$DC,$D8,$BE,$FF + .DB $FF,$FF,$FF,$E6,$EC + +KBD_MAPEXT: ; PAIRS ARE [SCANCODE,KEYCODE] FOR EXTENDED SCANCODES + .DB $11,$B5, $14,$B3, $1F,$B6, $27,$B7 + .DB $2F,$EF, $37,$FA, $3F,$FB, $4A,$CB ; All keys listed below are customized for Wordstar. + .DB $5A,$CF, $5E,$FC, $69,$06, $6B,$13 ; n.a , n.a , word right , n.a. + .DB $6C,$01, $70,$16, $71,$07, $72,$18 ; Word left , Toggle Insert/Overwrite , Del Char , Cursor down + .DB $74,$04, $75,$05, $7A,$1A, $7C,$ED ; Cursor right , Cursor up , Page down + .DB $7D,$17, $7E,$FD, $00,$00 ; Page up , n.a. , END KBD_MAPEXT (Pairs end) +; +KBD_MAPNUMPAD: ; KEYCODE TRANSLATION FROM NUMPAD RANGE TO STD ASCII/KEYCODES + + .DB $F3,$F7,$F5,$F8,$FF,$F9,$F2,$F6,$F4,$F0,$F1,$2F,$2A,$2D,$2B,$0D + .DB $31,$32,$33,$34,$35,$36,$37,$38,$39,$30,$2E,$2F,$2A,$2D,$2B,$0D +; +#ENDIF ; ;__________________________________________________________________________________________________ ; KEYCODE VALUES RETURNED BY THE DECODER diff --git a/Source/HBIOS/md.asm b/Source/HBIOS/md.asm index 6eb27aad..2b9c9c0a 100644 --- a/Source/HBIOS/md.asm +++ b/Source/HBIOS/md.asm @@ -174,6 +174,7 @@ MD_SEEK: ; ; MD_READ: + CALL HB_DSKREAD ; HOOK HBIOS DISK READ SUPERVISOR LD BC,MD_RDSEC ; GET ADR OF SECTOR READ FUNC LD (MD_RWFNADR),BC ; SAVE IT AS PENDING IO FUNC JR MD_RW ; CONTINUE TO GENERIC R/W ROUTINE @@ -182,6 +183,7 @@ MD_READ: ; ; MD_WRITE: + CALL HB_DSKWRITE ; HOOK HBIOS DISK WRITE SUPERVISOR LD BC,MD_WRSEC ; GET ADR OF SECTOR WRITE FUNC LD (MD_RWFNADR),BC ; SAVE IT AS PENDING IO FUNC LD A,(IY+MD_DEV) ; GET DEVICE NUMBER diff --git a/Source/HBIOS/mio.asm b/Source/HBIOS/mio.asm new file mode 100644 index 00000000..850a6195 --- /dev/null +++ b/Source/HBIOS/mio.asm @@ -0,0 +1,49 @@ +;___MIO________________________________________________________________________________________________________________ +; +; MEMORY MAPPED I/O +; +; PROVIDES AN INTERFACE TO BUFFER OUTPUT FROM PRE-INITIALIZATION +; FUNCTIONS PRIOR TO OTHER OUTPUT METHODS BEING AVAILABLE +;______________________________________________________________________________________________________________________ +; +; $ CODE NOT STRICTLY REQUIRED. +; +MIOOUTPTR .EQU BNKTOP +; +MIO_INIT: ; MINIMAL INIT + PUSH HL + LD HL,MIOOUTPTR+2 + LD (MIOOUTPTR),HL + LD (HL),'$' + POP HL + RET +; +MIO_OUTC: ; OUTPUT BYTE IN A + PUSH HL + PUSH DE + LD HL,MIOOUTPTR + LD E,(HL) + INC HL + LD D,(HL) + LD H,D + LD L,E + LD (HL),A + INC HL + LD (MIOOUTPTR),HL + LD (HL),'$' + POP DE + POP HL + RET +; +; NOT USED AT THE MOMENT +; +MIO_INC: ; INPUT BYTE TO A + LD A,'$' + RET +; +; +MIO_IST: ; INPUT STATUS TO A (NUM CHARS WAITING) + LD A,1 + OR A + RET ; DONE +; \ No newline at end of file diff --git a/Source/HBIOS/nascom.asm b/Source/HBIOS/nascom.asm index 6752614c..1449c741 100644 --- a/Source/HBIOS/nascom.asm +++ b/Source/HBIOS/nascom.asm @@ -19,4406 +19,4989 @@ ; Adapted for the freeware Zilog Macro Assembler 2.10 to produce ; the original ROM code (checksum A934H). PA ; -; SBC V2 BOOTROM VERSION 27/10/2018 -; difficultylevelhigh@gmail.com +;================================================================================== +; SBC V2 BOOTROM VERSION +; +; 20181027 - Initial retrobrewcomputer SBC V2 version - difficultylevelhigh@gmail.com +; 20191012 - Add PLAY command for SBC-V2-004 Sound support. +; 20191013 - Add option for long error messages. +; - Add option to use VT100 escape codes for screen controls. +; 20200308 - Add ECB-VDU Graphics support for set, reset and point ; #INCLUDE "std.asm" ; +; CUSTOMIZATION +; +ABBRERR .EQU FALSE ; Choose between long error message and abbreviated error messages. +VT100 .EQU TRUE ; Use VT100 escape codes for CLS +VDUGFX .EQU FALSE ; Option to enable ECB-VDU graphics support using SET, RESET and POINT. +; +;================================================================================== +; SBC V2 + ECB-VDU GRAPHICS CUSTOMIZATION 160X75 BLOCK GRAPHICS ON AN 80X25 DISPLAY +; REQUIRES ECB-VDU WITH 256 CHARACTER MOD AND 12X8GFX1 FONT INSTALLED, VDU MODE SET TO 80X25B/24B. +; SWITCHES LONG ERROR MESSAGES OFF FOR SPACE +; +#IF VDUGFX + #IF (VDUSIZ=V80X25B) +VDUROWS .EQU 25 +VDUCOLS .EQU 80 + #ENDIF + #IF (VDUSIZ=V80X24B) +VDUROWS .EQU 24 +VDUCOLS .EQU 80 + #ENDIF +ABBRERR .SET TRUE +VDUREG .EQU 0F2H ; ECB-VDU +VDUSTS .EQU 0F2H ; +VDUDTA .EQU 0F3H ; PORT +VDURWR .EQU 0F1H ; REGISTER +VDURRD .EQU 0F0H ; ADDRESSES +#ENDIF +; ; GENERAL EQUATES - -CTRLC .EQU 03H ; Control "C" -CTRLG .EQU 07H ; Control "G" -BKSP .EQU 08H ; Back space -LF .EQU 0AH ; Line feed -CS .EQU 0CH ; Clear screen -CR .EQU 0DH ; Carriage return -CTRLO .EQU 0FH ; Control "O" -CTRLQ .EQU 11H ; Control "Q" -CTRLR .EQU 12H ; Control "R" -CTRLS .EQU 13H ; Control "S" -CTRLU .EQU 15H ; Control "U" -ESC .EQU 1BH ; Escape -DEL .EQU 7FH ; Delete - +; +CTRLC .EQU 03H ; Control "C" +CTRLG .EQU 07H ; Control "G" +BKSP .EQU 08H ; Back space +LF .EQU 0AH ; Line feed +CS .EQU 0CH ; Clear screen +CR .EQU 0DH ; Carriage return +CTRLO .EQU 0FH ; Control "O" +CTRLQ .EQU 11H ; Control "Q" +CTRLR .EQU 12H ; Control "R" +CTRLS .EQU 13H ; Control "S" +CTRLU .EQU 15H ; Control "U" +ESC .EQU 1BH ; Escape +DEL .EQU 7FH ; Delete +; ; BASIC WORK SPACE LOCATIONS - -WRKSPC .EQU BAS_END+90H ; WAS 4090H ; BASIC Work space -USR .EQU WRKSPC+3H ; "USR (x)" jump -OUTSUB .EQU WRKSPC+6H ; "OUT p,n" -OTPORT .EQU WRKSPC+7H ; Port (p) -DIVSUP .EQU WRKSPC+9H ; Division support routine -DIV1 .EQU WRKSPC+0AH ; <- Values -DIV2 .EQU WRKSPC+0EH ; <- to -DIV3 .EQU WRKSPC+12H ; <- be -DIV4 .EQU WRKSPC+15H ; <-inserted -SEED .EQU WRKSPC+17H ; Random number seed -LSTRND .EQU WRKSPC+3AH ; Last random number -INPSUB .EQU WRKSPC+3EH ; #INP (x)" Routine -INPORT .EQU WRKSPC+3FH ; PORT (x) -NULLS .EQU WRKSPC+41H ; Number of nulls -LWIDTH .EQU WRKSPC+42H ; Terminal width -COMMAN .EQU WRKSPC+43H ; Width for commas -NULFLG .EQU WRKSPC+44H ; Null after input byte flag -CTLOFG .EQU WRKSPC+45H ; Control "O" flag -LINESC .EQU WRKSPC+46H ; Lines counter -LINESN .EQU WRKSPC+48H ; Lines number -CHKSUM .EQU WRKSPC+4AH ; Array load/save check sum -NMIFLG .EQU WRKSPC+4CH ; Flag for NMI break routine -BRKFLG .EQU WRKSPC+4DH ; Break flag -RINPUT .EQU WRKSPC+4EH ; Input reflection -POINT .EQU WRKSPC+51H ; "POINT" reflection (unused) -PSET .EQU WRKSPC+54H ; "SET" reflection -RESET .EQU WRKSPC+57H ; "RESET" reflection -STRSPC .EQU WRKSPC+5AH ; Bottom of string space -LINEAT .EQU WRKSPC+5CH ; Current line number -BASTXT .EQU WRKSPC+5EH ; Pointer to start of program -BUFFER .EQU WRKSPC+61H ; Input buffer -STACK .EQU WRKSPC+66H ; Initial stack -CURPOS .EQU WRKSPC+0ABH ; Character position on line -LCRFLG .EQU WRKSPC+0ACH ; Locate/Create flag -TYPE .EQU WRKSPC+0ADH ; Data type flag -DATFLG .EQU WRKSPC+0AEH ; Literal statement flag -LSTRAM .EQU WRKSPC+0AFH ; Last available RAM -TMSTPT .EQU WRKSPC+0B1H ; Temporary string pointer -TMSTPL .EQU WRKSPC+0B3H ; Temporary string pool -TMPSTR .EQU WRKSPC+0BFH ; Temporary string -STRBOT .EQU WRKSPC+0C3H ; Bottom of string space -CUROPR .EQU WRKSPC+0C5H ; Current operator in EVAL -LOOPST .EQU WRKSPC+0C7H ; First statement of loop -DATLIN .EQU WRKSPC+0C9H ; Line of current DATA item -FORFLG .EQU WRKSPC+0CBH ; "FOR" loop flag -LSTBIN .EQU WRKSPC+0CCH ; Last byte entered -READFG .EQU WRKSPC+0CDH ; Read/Input flag -BRKLIN .EQU WRKSPC+0CEH ; Line of break -NXTOPR .EQU WRKSPC+0D0H ; Next operator in EVAL -ERRLIN .EQU WRKSPC+0D2H ; Line of error -CONTAD .EQU WRKSPC+0D4H ; Where to CONTinue -PROGND .EQU WRKSPC+0D6H ; End of program -VAREND .EQU WRKSPC+0D8H ; End of variables -ARREND .EQU WRKSPC+0DAH ; End of arrays -NXTDAT .EQU WRKSPC+0DCH ; Next data item -FNRGNM .EQU WRKSPC+0DEH ; Name of FN argument -FNARG .EQU WRKSPC+0E0H ; FN argument value -FPREG .EQU WRKSPC+0E4H ; Floating point register -FPEXP .EQU FPREG+3 ; Floating point exponent -SGNRES .EQU WRKSPC+0E8H ; Sign of result -PBUFF .EQU WRKSPC+0E9H ; Number print buffer -MULVAL .EQU WRKSPC+0F6H ; Multiplier -PROGST .EQU WRKSPC+0F9H ; Start of program text area -STLOOK .EQU WRKSPC+15DH ; Start of memory test - - +; +; 0200H - 2000H BASIC EXECUTABLE +; 2000H - 2090H STACK +; 2090H - 20F8H BASIC EXECUTABLE VARAIABLES / WORKSPACE +; 20F9H - BASIC PROGRAM START + +WRKSPC .EQU BAS_END+90H ; BASIC Work space +USR .EQU WRKSPC+3H ; "USR (x)" jump +OUTSUB .EQU WRKSPC+6H ; "OUT p,n" +OTPORT .EQU WRKSPC+7H ; Port (p) +DIVSUP .EQU WRKSPC+9H ; Division support routine +DIV1 .EQU WRKSPC+0AH ; <- Values +DIV2 .EQU WRKSPC+0EH ; <- to +DIV3 .EQU WRKSPC+12H ; <- be +DIV4 .EQU WRKSPC+15H ; <-inserted +SEED .EQU WRKSPC+17H ; Random number seed +LSTRND .EQU WRKSPC+3AH ; Last random number +INPSUB .EQU WRKSPC+3EH ; #INP (x)" Routine +INPORT .EQU WRKSPC+3FH ; PORT (x) +NULLS .EQU WRKSPC+41H ; Number of nulls +LWIDTH .EQU WRKSPC+42H ; Terminal width +COMMAN .EQU WRKSPC+43H ; Width for commas +NULFLG .EQU WRKSPC+44H ; Null after input byte flag +CTLOFG .EQU WRKSPC+45H ; Control "O" flag +LINESC .EQU WRKSPC+46H ; Lines counter +LINESN .EQU WRKSPC+48H ; Lines number +CHKSUM .EQU WRKSPC+4AH ; Array load/save check sum +NMIFLG .EQU WRKSPC+4CH ; Flag for NMI break routine +BRKFLG .EQU WRKSPC+4DH ; Break flag +RINPUT .EQU WRKSPC+4EH ; Input reflection +POINT .EQU WRKSPC+51H ; "POINT" reflection +PSET .EQU WRKSPC+54H ; "SET" reflection +RESET .EQU WRKSPC+57H ; "RESET" reflection +STRSPC .EQU WRKSPC+5AH ; Bottom of string space +LINEAT .EQU WRKSPC+5CH ; Current line number +BASTXT .EQU WRKSPC+5EH ; Pointer to start of program +BUFFER .EQU WRKSPC+61H ; Input buffer +STACK .EQU WRKSPC+66H ; Initial stack +CURPOS .EQU WRKSPC+0ABH ; Character position on line +LCRFLG .EQU WRKSPC+0ACH ; Locate/Create flag +TYPE .EQU WRKSPC+0ADH ; Data type flag +DATFLG .EQU WRKSPC+0AEH ; Literal statement flag +LSTRAM .EQU WRKSPC+0AFH ; Last available RAM +TMSTPT .EQU WRKSPC+0B1H ; Temporary string pointer +TMSTPL .EQU WRKSPC+0B3H ; Temporary string pool +TMPSTR .EQU WRKSPC+0BFH ; Temporary string +STRBOT .EQU WRKSPC+0C3H ; Bottom of string space +CUROPR .EQU WRKSPC+0C5H ; Current operator in EVAL +LOOPST .EQU WRKSPC+0C7H ; First statement of loop +DATLIN .EQU WRKSPC+0C9H ; Line of current DATA item +FORFLG .EQU WRKSPC+0CBH ; "FOR" loop flag +LSTBIN .EQU WRKSPC+0CCH ; Last byte entered +READFG .EQU WRKSPC+0CDH ; Read/Input flag +BRKLIN .EQU WRKSPC+0CEH ; Line of break +NXTOPR .EQU WRKSPC+0D0H ; Next operator in EVAL +ERRLIN .EQU WRKSPC+0D2H ; Line of error +CONTAD .EQU WRKSPC+0D4H ; Where to CONTinue +PROGND .EQU WRKSPC+0D6H ; End of program +VAREND .EQU WRKSPC+0D8H ; End of variables +ARREND .EQU WRKSPC+0DAH ; End of arrays +NXTDAT .EQU WRKSPC+0DCH ; Next data item +FNRGNM .EQU WRKSPC+0DEH ; Name of FN argument +FNARG .EQU WRKSPC+0E0H ; FN argument value +FPREG .EQU WRKSPC+0E4H ; Floating point register +FPEXP .EQU FPREG+3 ; Floating point exponent +SGNRES .EQU WRKSPC+0E8H ; Sign of result +PBUFF .EQU WRKSPC+0E9H ; Number print buffer +MULVAL .EQU WRKSPC+0F6H ; Multiplier +PROGST .EQU WRKSPC+0F9H ; Start of program text area +STLOOK .EQU WRKSPC+15DH ; Start of memory test +; ; BASIC ERROR CODE VALUES - -NF .EQU 00H ; NEXT without FOR -SN .EQU 02H ; Syntax error -RG .EQU 04H ; RETURN without GOSUB -OD .EQU 06H ; Out of DATA -FC .EQU 08H ; Function call error -OV .EQU 0AH ; Overflow -OM .EQU 0CH ; Out of memory -UL .EQU 0EH ; Undefined line number -BS .EQU 10H ; Bad subscript -DD .EQU 12H ; Re-DIMensioned array -DZ .EQU 14H ; Division by zero (/0) -ID .EQU 16H ; Illegal direct -TM .EQU 18H ; Type miss-match -OS .EQU 1AH ; Out of string space -LS .EQU 1CH ; String too long -ST .EQU 1EH ; String formula too complex -CN .EQU 20H ; Can't CONTinue -UF .EQU 22H ; UnDEFined FN function -MO .EQU 24H ; Missing operand -HX .EQU 26H ; HEX error -BN .EQU 28H ; BIN error - - .ORG BAS_LOC ; WAS 02000H - -COLD: JP STARTB ; Jump for cold start -WARM: JP WARMST ; Jump for warm start -STARTB: - LD IX,0 ; Flag cold start - JP CSTART ; Jump to initialise - - .WORD DEINT ; Get integer -32768 to 32767 - .WORD ABPASS ; Return integer in AB - - -CSTART: LD HL,WRKSPC ; Start of workspace RAM - LD SP,HL ; Set up a temporary stack - JP INITST ; Go to initialise - -INIT: LD DE,INITAB ; Initialise workspace - LD B,INITBE-INITAB+3; Bytes to copy - LD HL,WRKSPC ; Into workspace RAM -COPY: LD A,(DE) ; Get source - LD (HL),A ; To destination - INC HL ; Next destination - INC DE ; Next source - DEC B ; Count bytes - JP NZ,COPY ; More to move - LD SP,HL ; Temporary stack - - CALL CLREG ; Clear registers and stack - CALL PRNTCRLF ; Output CRLF - LD (BUFFER+72+1),A ; Mark end of buffer - LD (PROGST),A ; Initialise program area -MSIZE: LD HL,MEMMSG ; Point to message - CALL PRS ; Output "Memory size" - CALL PROMPT ; Get input with '?' - CALL GETCHR ; Get next character - OR A ; Set flags - JP NZ,TSTMEM ; If number - Test if RAM there - LD HL,STLOOK ; Point to start of RAM -MLOOP: INC HL ; Next byte -; LD A,H ; Above address FFFF ? -; OR L - LD A,H ; Memory top set below HBIOS Proxy @ FE00 - CP $FD - JP Z,SETTOP ; Yes - 64K RAM - LD A,(HL) ; Get contents - LD B,A ; Save it - CPL ; Flip all bits - LD (HL),A ; Put it back - CP (HL) ; RAM there if same - LD (HL),B ; Restore old contents - JP Z,MLOOP ; If RAM - test next byte - JP SETTOP ; Top of RAM found - -TSTMEM: CALL ATOH ; Get high memory into DE - OR A ; Set flags on last byte - JP NZ,SNERR ; ?SN Error if bad character - EX DE,HL ; Address into HL - DEC HL ; Back one byte - LD A,11011001B ; Test byte - LD B,(HL) ; Get old contents - LD (HL),A ; Load test byte - CP (HL) ; RAM there if same - LD (HL),B ; Restore old contents - JP NZ,MSIZE ; Ask again if no RAM - -SETTOP: DEC HL ; Back one byte - LD DE,STLOOK-1 ; See if enough RAM - CALL CPDEHL ; Compare DE with HL - JP C,MSIZE ; Ask again if not enough RAM - LD DE,0-50 ; 50 Bytes string space - LD (LSTRAM),HL ; Save last available RAM - ADD HL,DE ; Allocate string space - LD (STRSPC),HL ; Save string space - CALL CLRPTR ; Clear program area - LD HL,(STRSPC) ; Get end of memory - LD DE,0-17 ; Offset for free bytes - ADD HL,DE ; Adjust HL - LD DE,PROGST ; Start of program text - LD A,L ; Get LSB - SUB E ; Adjust it - LD L,A ; Re-save - LD A,H ; Get MSB - SBC A,D ; Adjust it - LD H,A ; Re-save - PUSH HL ; Save bytes free - LD HL,SIGNON ; Sign-on message - CALL PRS ; Output string - POP HL ; Get bytes free back - CALL PRNTHL ; Output amount of free memory - LD HL,BFREE ; " Bytes free" message - CALL PRS ; Output string - -WARMST: LD SP,STACK ; Temporary stack -BRKRET: CALL CLREG ; Clear registers and stack - JP PRNTOK ; Go to get command line - +; +NF .EQU 00H ; NEXT without FOR +SN .EQU 02H ; Syntax error +RG .EQU 04H ; RETURN without GOSUB +OD .EQU 06H ; Out of DATA +FC .EQU 08H ; Function call error +OV .EQU 0AH ; Overflow +OM .EQU 0CH ; Out of memory +UL .EQU 0EH ; Undefined line number +BS .EQU 10H ; Bad subscript +DD .EQU 12H ; Re-DIMensioned array +DZ .EQU 14H ; Division by zero (/0) +ID .EQU 16H ; Illegal direct +TM .EQU 18H ; Type miss-match +OS .EQU 1AH ; Out of string space +LS .EQU 1CH ; String too long +ST .EQU 1EH ; String formula too complex +CN .EQU 20H ; Can't CONTinue +UF .EQU 22H ; UnDEFined FN function +MO .EQU 24H ; Missing operand +HX .EQU 26H ; HEX error +BN .EQU 28H ; BIN error +; + .ORG BAS_LOC ; WAS 02000H +; +COLD: JP STARTB ; Jump for cold start +WARM: JP WARMST ; Jump for warm start +STARTB: + LD IX,0 ; Flag cold start + JP CSTART ; Jump to initialise +; + .WORD DEINT ; Get integer -32768 to 32767 + .WORD ABPASS ; Return integer in AB +; +CSTART: LD HL,WRKSPC ; Start of workspace RAM + LD SP,HL ; Set up a temporary stack + JP INITST ; Go to initialise +; +INIT: LD DE,INITAB ; Initialise workspace + LD B,INITBE-INITAB+3; Bytes to copy + LD HL,WRKSPC ; Into workspace RAM +COPY: LD A,(DE) ; Get source + LD (HL),A ; To destination + INC HL ; Next destination + INC DE ; Next source + DEC B ; Count bytes + JP NZ,COPY ; More to move + LD SP,HL ; Temporary stack +; + CALL CLREG ; Clear registers and stack + CALL PRNTCRLF ; Output CRLF + LD (BUFFER+72+1),A ; Mark end of buffer + LD (PROGST),A ; Initialise program area +MSIZE: LD HL,MEMMSG ; Point to message + CALL PRS ; Output "Memory size" + CALL PROMPT ; Get input with '?' + CALL GETCHR ; Get next character + OR A ; Set flags + JP NZ,TSTMEM ; If number - Test if RAM there + LD HL,STLOOK ; Point to start of RAM +MLOOP: INC HL ; Next byte +; LD A,H ; Above address FFFF ? +; OR L + LD A,H ; Memory top set below HBIOS Proxy @ FE00 + CP $FD + JP Z,SETTOP ; Yes - 64K RAM + LD A,(HL) ; Get contents + LD B,A ; Save it + CPL ; Flip all bits + LD (HL),A ; Put it back + CP (HL) ; RAM there if same + LD (HL),B ; Restore old contents + JP Z,MLOOP ; If RAM - test next byte + JP SETTOP ; Top of RAM found +; +TSTMEM: CALL ATOH ; Get high memory into DE + OR A ; Set flags on last byte + JP NZ,SNERR ; ?SN Error if bad character + EX DE,HL ; Address into HL + DEC HL ; Back one byte + LD A,11011001B ; Test byte + LD B,(HL) ; Get old contents + LD (HL),A ; Load test byte + CP (HL) ; RAM there if same + LD (HL),B ; Restore old contents + JP NZ,MSIZE ; Ask again if no RAM + +SETTOP: DEC HL ; Back one byte + LD DE,STLOOK-1 ; See if enough RAM + CALL CPDEHL ; Compare DE with HL + JP C,MSIZE ; Ask again if not enough RAM + LD DE,0-50 ; 50 Bytes string space + LD (LSTRAM),HL ; Save last available RAM + ADD HL,DE ; Allocate string space + LD (STRSPC),HL ; Save string space + CALL CLRPTR ; Clear program area + LD HL,(STRSPC) ; Get end of memory + LD DE,0-17 ; Offset for free bytes + ADD HL,DE ; Adjust HL + LD DE,PROGST ; Start of program text + LD A,L ; Get LSB + SUB E ; Adjust it + LD L,A ; Re-save + LD A,H ; Get MSB + SBC A,D ; Adjust it + LD H,A ; Re-save + PUSH HL ; Save bytes free + LD HL,SIGNON ; Sign-on message + CALL PRS ; Output string + POP HL ; Get bytes free back + CALL PRNTHL ; Output amount of free memory + LD HL,BFREE ; " Bytes free" message + CALL PRS ; Output string + +WARMST: LD SP,STACK ; Temporary stack +BRKRET: CALL CLREG ; Clear registers and stack + JP PRNTOK ; Go to get command line +; BFREE: .BYTE " Bytes free",CR,LF,0,0 - -SIGNON: .BYTE "Z80 BASIC Ver 4.7b",CR,LF - .BYTE "Copyright ",40,"C",41 - .BYTE " 1978 by Microsoft",CR,LF,0,0 - +; +SIGNON: .BYTE "Z80 BASIC Ver 4.7b" +#IF VDUGFX + .BYTE "/vdu" +#ENDIF + .BYTE CR,LF + .BYTE "Copyright ",40,"C",41 + .BYTE " 1978 by Microsoft",CR,LF,0,0 +; MEMMSG: .BYTE "Memory top",0 - +; ; FUNCTION ADDRESS TABLE - +; FNCTAB: .WORD SGN - .WORD INT - .WORD ABS - .WORD USR - .WORD FRE - .WORD INP - .WORD POS - .WORD SQR - .WORD RND - .WORD LOG - .WORD EXP - .WORD COS - .WORD SIN - .WORD TAN - .WORD ATN - .WORD PEEK - .WORD DEEK - .WORD POINT - .WORD LEN - .WORD STR - .WORD VAL - .WORD ASC - .WORD CHR - .WORD HEX - .WORD BIN - .WORD LEFT - .WORD RIGHT - .WORD MID + .WORD INT + .WORD ABS + .WORD USR + .WORD FRE + .WORD INP + .WORD POS + .WORD SQR + .WORD RND + .WORD LOG + .WORD EXP + .WORD COS + .WORD SIN + .WORD TAN + .WORD ATN + .WORD PEEK + .WORD DEEK + .WORD POINT + .WORD LEN + .WORD STR + .WORD VAL + .WORD ASC + .WORD CHR + .WORD HEX + .WORD BIN + .WORD LEFT + .WORD RIGHT + .WORD MID ; RESERVED WORD LIST -WORDS: .BYTE 'E'+80H,"ND" - .BYTE 'F'+80H,"OR" - .BYTE 'N'+80H,"EXT" - .BYTE 'D'+80H,"ATA" - .BYTE 'I'+80H,"NPUT" - .BYTE 'D'+80H,"IM" - .BYTE 'R'+80H,"EAD" - .BYTE 'L'+80H,"ET" - .BYTE 'G'+80H,"OTO" - .BYTE 'R'+80H,"UN" - .BYTE 'I'+80H,"F" - .BYTE 'R'+80H,"ESTORE" - .BYTE 'G'+80H,"OSUB" - .BYTE 'R'+80H,"ETURN" - .BYTE 'R'+80H,"EM" - .BYTE 'S'+80H,"TOP" - .BYTE 'O'+80H,"UT" - .BYTE 'O'+80H,"N" - .BYTE 'N'+80H,"ULL" - .BYTE 'W'+80H,"AIT" - .BYTE 'D'+80H,"EF" - .BYTE 'P'+80H,"OKE" - .BYTE 'D'+80H,"OKE" - .BYTE 'S'+80H,"CREEN" - .BYTE 'L'+80H,"INES" - .BYTE 'C'+80H,"LS" - .BYTE 'W'+80H,"IDTH" - .BYTE 'B'+80H,"YE" - .BYTE 'S'+80H,"ET" - .BYTE 'R'+80H,"ESET" - .BYTE 'P'+80H,"RINT" - .BYTE 'C'+80H,"ONT" - .BYTE 'L'+80H,"IST" - .BYTE 'C'+80H,"LEAR" - .BYTE 'C'+80H,"LOAD" - .BYTE 'C'+80H,"SAVE" - .BYTE 'N'+80H,"EW" - - .BYTE 'T'+80H,"AB(" - .BYTE 'T'+80H,"O" - .BYTE 'F'+80H,"N" - .BYTE 'S'+80H,"PC(" - .BYTE 'T'+80H,"HEN" - .BYTE 'N'+80H,"OT" - .BYTE 'S'+80H,"TEP" - - .BYTE '+'+80H - .BYTE '-'+80H - .BYTE '*'+80H - .BYTE '/'+80H - .BYTE '^'+80H - .BYTE 'A'+80H,"ND" - .BYTE 'O'+80H,"R" - .BYTE '>'+80H - .BYTE '='+80H - .BYTE '<'+80H - - .BYTE 'S'+80H,"GN" - .BYTE 'I'+80H,"NT" - .BYTE 'A'+80H,"BS" - .BYTE 'U'+80H,"SR" - .BYTE 'F'+80H,"RE" - .BYTE 'I'+80H,"NP" - .BYTE 'P'+80H,"OS" - .BYTE 'S'+80H,"QR" - .BYTE 'R'+80H,"ND" - .BYTE 'L'+80H,"OG" - .BYTE 'E'+80H,"XP" - .BYTE 'C'+80H,"OS" - .BYTE 'S'+80H,"IN" - .BYTE 'T'+80H,"AN" - .BYTE 'A'+80H,"TN" - .BYTE 'P'+80H,"EEK" - .BYTE 'D'+80H,"EEK" - .BYTE 'P'+80H,"OINT" - .BYTE 'L'+80H,"EN" - .BYTE 'S'+80H,"TR$" - .BYTE 'V'+80H,"AL" - .BYTE 'A'+80H,"SC" - .BYTE 'C'+80H,"HR$" - .BYTE 'H'+80H,"EX$" - .BYTE 'B'+80H,"IN$" - .BYTE 'L'+80H,"EFT$" - .BYTE 'R'+80H,"IGHT$" - .BYTE 'M'+80H,"ID$" - .BYTE 80H ; End of list marker - +WORDS: .BYTE 'E'+80H,"ND" ; PEND: + .BYTE 'F'+80H,"OR" ; FOR: + .BYTE 'N'+80H,"EXT" ; NEXT: + .BYTE 'D'+80H,"ATA" ; DATA: + .BYTE 'I'+80H,"NPUT" ; INPUT: + .BYTE 'D'+80H,"IM" ; DIM: + .BYTE 'R'+80H,"EAD" ; READ: + .BYTE 'L'+80H,"ET" ; SET: + .BYTE 'G'+80H,"OTO" ; GOTO: + .BYTE 'R'+80H,"UN" ; RUN: + .BYTE 'I'+80H,"F" ; IF: + .BYTE 'R'+80H,"ESTORE"; RESTOR: + .BYTE 'G'+80H,"OSUB" ; GOSUB: + .BYTE 'R'+80H,"ETURN" ; RETURN: + .BYTE 'R'+80H,"EM" ; REM: + .BYTE 'S'+80H,"TOP" ; STOP: + .BYTE 'O'+80H,"UT" ; POUT: + .BYTE 'O'+80H,"N" ; ON: + .BYTE 'N'+80H,"ULL" ; NULL: + .BYTE 'W'+80H,"AIT" ; WAIT: + .BYTE 'D'+80H,"EF" ; DEF: + .BYTE 'P'+80H,"OKE" ; POKE: + .BYTE 'D'+80H,"OKE" ; DOKE: + .BYTE 'S'+80H,"CREEN" ; REM: NOT IMPLEMENTED + .BYTE 'L'+80H,"INES" ; LINES + .BYTE 'C'+80H,"LS" ; CLS: + .BYTE 'W'+80H,"IDTH" ; WIDTH: + .BYTE 'B'+80H,"YE" ; MONITR: + .BYTE 'S'+80H,"ET" ; PSET: + .BYTE 'R'+80H,"ESET" ; RESET: + .BYTE 'P'+80H,"RINT" ; PRINT: + .BYTE 'C'+80H,"ONT" ; CONT: + .BYTE 'L'+80H,"IST" ; LIST: + .BYTE 'C'+80H,"LEAR" ; CLEAR: + .BYTE 'P'+80H,"LAY" ; PLAY: WAS CLOAD + .BYTE 'C'+80H,"SAVE" ; REM: NOT IMPLEMENTED + .BYTE 'N'+80H,"EW" ; NEW + + .BYTE 'T'+80H,"AB(" ; TAB( + .BYTE 'T'+80H,"O" ; TO + .BYTE 'F'+80H,"N" ; FN( + .BYTE 'S'+80H,"PC(" ; SPC( + .BYTE 'T'+80H,"HEN" ; THEN + .BYTE 'N'+80H,"OT" ; NOT + .BYTE 'S'+80H,"TEP" ; STEP + + .BYTE '+'+80H + .BYTE '-'+80H + .BYTE '*'+80H + .BYTE '/'+80H + .BYTE '^'+80H + .BYTE 'A'+80H,"ND" ; AND + .BYTE 'O'+80H,"R" ; OR + .BYTE '>'+80H + .BYTE '='+80H + .BYTE '<'+80H + + .BYTE 'S'+80H,"GN" ; SGN + .BYTE 'I'+80H,"NT" ; INT + .BYTE 'A'+80H,"BS" ; ABS + .BYTE 'U'+80H,"SR" ; USR + .BYTE 'F'+80H,"RE" ; FRE + .BYTE 'I'+80H,"NP" ; INP + .BYTE 'P'+80H,"OS" ; POS + .BYTE 'S'+80H,"QR" ; SQR + .BYTE 'R'+80H,"ND" ; RND + .BYTE 'L'+80H,"OG" ; LOG + .BYTE 'E'+80H,"XP" ; EXP + .BYTE 'C'+80H,"OS" ; COS + .BYTE 'S'+80H,"IN" ; SIN + .BYTE 'T'+80H,"AN" ; TAN + .BYTE 'A'+80H,"TN" ; ATN + .BYTE 'P'+80H,"EEK" ; PEEK + .BYTE 'D'+80H,"EEK" ; DEEK + .BYTE 'P'+80H,"OINT" ; POINT + .BYTE 'L'+80H,"EN" ; LEN + .BYTE 'S'+80H,"TR$" ; STR$ + .BYTE 'V'+80H,"AL" ; VAL + .BYTE 'A'+80H,"SC" ; ASC + .BYTE 'C'+80H,"HR$" ; CHR$ + .BYTE 'H'+80H,"EX$" ; HEX$ + .BYTE 'B'+80H,"IN$" ; BIN$ + .BYTE 'L'+80H,"EFT$" ; LEFT$ + .BYTE 'R'+80H,"IGHT$" ; RIGHT$ + .BYTE 'M'+80H,"ID$" ; MID$ + .BYTE 80H ; End of list marker +; ; KEYWORD ADDRESS TABLE - +; WORDTB: .WORD PEND - .WORD FOR - .WORD NEXT - .WORD DATA - .WORD INPUT - .WORD DIM - .WORD READ - .WORD LET - .WORD GOTO - .WORD RUN - .WORD IF - .WORD RESTOR - .WORD GOSUB - .WORD RETURN - .WORD REM - .WORD STOP - .WORD POUT - .WORD ON - .WORD NULL - .WORD WAIT - .WORD DEF - .WORD POKE - .WORD DOKE - .WORD REM - .WORD LINES - .WORD CLS - .WORD WIDTH - .WORD MONITR ; BYE - .WORD PSET - .WORD RESET - .WORD PRINT - .WORD CONT - .WORD LIST - .WORD CLEAR - .WORD REM - .WORD REM - .WORD NEW - + .WORD FOR + .WORD NEXT + .WORD DATA + .WORD INPUT + .WORD DIM + .WORD READ + .WORD LET + .WORD GOTO + .WORD RUN + .WORD IF + .WORD RESTOR + .WORD GOSUB + .WORD RETURN + .WORD REM + .WORD STOP + .WORD POUT + .WORD ON + .WORD NULL + .WORD WAIT + .WORD DEF + .WORD POKE + .WORD DOKE + .WORD REM + .WORD LINES + .WORD CLS + .WORD WIDTH + .WORD MONITR ; BYE + .WORD PSET + .WORD RESET + .WORD PRINT + .WORD CONT + .WORD LIST + .WORD CLEAR + .WORD PLAY + .WORD REM + .WORD NEW +; ; RESERVED WORD TOKEN VALUES - -ZEND .EQU 080H ; END -ZFOR .EQU 081H ; FOR -ZDATA .EQU 083H ; DATA -ZGOTO .EQU 088H ; GOTO -ZGOSUB .EQU 08CH ; GOSUB -ZREM .EQU 08EH ; REM -ZPRINT .EQU 09EH ; PRINT -ZNEW .EQU 0A4H ; NEW - -ZTAB .EQU 0A5H ; TAB -ZTO .EQU 0A6H ; TO -ZFN .EQU 0A7H ; FN -ZSPC .EQU 0A8H ; SPC -ZTHEN .EQU 0A9H ; THEN -ZNOT .EQU 0AAH ; NOT -ZSTEP .EQU 0ABH ; STEP - -ZPLUS .EQU 0ACH ; + -ZMINUS .EQU 0ADH ; - -ZTIMES .EQU 0AEH ; * -ZDIV .EQU 0AFH ; / -ZOR .EQU 0B2H ; OR -ZGTR .EQU 0B3H ; > -ZEQUAL .EQU 0B4H ; M -ZLTH .EQU 0B5H ; < -ZSGN .EQU 0B6H ; SGN -ZPOINT .EQU 0C7H ; POINT -ZLEFT .EQU 0CDH +2 ; LEFT$ - +; +ZEND .EQU 080H ; END +ZFOR .EQU 081H ; FOR +ZDATA .EQU 083H ; DATA +ZGOTO .EQU 088H ; GOTO +ZGOSUB .EQU 08CH ; GOSUB +ZREM .EQU 08EH ; REM +ZPRINT .EQU 09EH ; PRINT +ZNEW .EQU 0A4H ; NEW +; +ZTAB .EQU 0A5H ; TAB +ZTO .EQU 0A6H ; TO +ZFN .EQU 0A7H ; FN +ZSPC .EQU 0A8H ; SPC +ZTHEN .EQU 0A9H ; THEN +ZNOT .EQU 0AAH ; NOT +ZSTEP .EQU 0ABH ; STEP +; +ZPLUS .EQU 0ACH ; + +ZMINUS .EQU 0ADH ; - +ZTIMES .EQU 0AEH ; * +ZDIV .EQU 0AFH ; / +ZOR .EQU 0B2H ; OR +ZGTR .EQU 0B3H ; > +ZEQUAL .EQU 0B4H ; = +ZLTH .EQU 0B5H ; < +ZSGN .EQU 0B6H ; SGN +ZPOINT .EQU 0C7H ; POINT +ZLEFT .EQU 0CDH +2 ; LEFT$ +; ; ARITHMETIC PRECEDENCE TABLE - -PRITAB: .BYTE 79H ; Precedence value - .WORD PADD ; FPREG = + FPREG - - .BYTE 79H ; Precedence value - .WORD PSUB ; FPREG = - FPREG - - .BYTE 7CH ; Precedence value - .WORD MULT ; PPREG = * FPREG - - .BYTE 7CH ; Precedence value - .WORD DIV ; FPREG = / FPREG - - .BYTE 7FH ; Precedence value - .WORD POWER ; FPREG = ^ FPREG - - .BYTE 50H ; Precedence value - .WORD PAND ; FPREG = AND FPREG - - .BYTE 46H ; Precedence value - .WORD POR ; FPREG = OR FPREG - +; +PRITAB: .BYTE 79H ; Precedence value + .WORD PADD ; FPREG = + FPREG +; + .BYTE 79H ; Precedence value + .WORD PSUB ; FPREG = - FPREG +; + .BYTE 7CH ; Precedence value + .WORD MULT ; PPREG = * FPREG +; + .BYTE 7CH ; Precedence value + .WORD DIV ; FPREG = / FPREG +; + .BYTE 7FH ; Precedence value + .WORD POWER ; FPREG = ^ FPREG +; + .BYTE 50H ; Precedence value + .WORD PAND ; FPREG = AND FPREG +; + .BYTE 46H ; Precedence value + .WORD POR ; FPREG = OR FPREG +; ; BASIC ERROR CODE LIST - -ERRORS: .BYTE "NF" ; NEXT without FOR - .BYTE "SN" ; Syntax error - .BYTE "RG" ; RETURN without GOSUB - .BYTE "OD" ; Out of DATA - .BYTE "FC" ; Illegal function call - .BYTE "OV" ; Overflow error - .BYTE "OM" ; Out of memory - .BYTE "UL" ; Undefined line - .BYTE "BS" ; Bad subscript - .BYTE "DD" ; Re-DIMensioned array - .BYTE "/0" ; Division by zero - .BYTE "ID" ; Illegal direct - .BYTE "TM" ; Type mis-match - .BYTE "OS" ; Out of string space - .BYTE "LS" ; String too long - .BYTE "ST" ; String formula too complex - .BYTE "CN" ; Can't CONTinue - .BYTE "UF" ; Undefined FN function - .BYTE "MO" ; Missing operand - .BYTE "HX" ; HEX error - .BYTE "BN" ; BIN error - +; +#IF ABBRERR +ERRORS: .BYTE "NF" ; NEXT without FOR + .BYTE "SN" ; Syntax error + .BYTE "RG" ; RETURN without GOSUB + .BYTE "OD" ; Out of DATA + .BYTE "FC" ; Illegal function call + .BYTE "OV" ; Overflow error + .BYTE "OM" ; Out of memory + .BYTE "UL" ; Undefined line + .BYTE "BS" ; Bad subscript + .BYTE "DD" ; Re-DIMensioned array + .BYTE "/0" ; Division by zero + .BYTE "ID" ; Illegal direct + .BYTE "TM" ; Type mismatch + .BYTE "OS" ; Out of string space + .BYTE "LS" ; String too long + .BYTE "ST" ; String formula too complex + .BYTE "CN" ; Can't CONTinue + .BYTE "UF" ; Undefined FN function + .BYTE "MO" ; Missing operand + .BYTE "HX" ; HEX error + .BYTE "BN" ; BIN error +#ELSE +ERRORS: .BYTE "NEXT without FOR",0 + .BYTE "Syntax",0 + .BYTE "RETURN without GOSUB",0 + .BYTE "Out of DATA",0 + .BYTE "Illegal function call",0 + .BYTE "Overflow",0 + .BYTE "Out of memory",0 + .BYTE "Undefined line",0 + .BYTE "Bad subscript",0 + .BYTE "Re-DIMensioned array",0 + .BYTE "Division by zero",0 + .BYTE "Illegal direct",0 + .BYTE "Type mismatch",0 + .BYTE "Out of string space",0 + .BYTE "String too long",0 + .BYTE "String formula too complex",0 + .BYTE "Can",$27,"t CONTinue",0 + .BYTE "Undefined FN function",0 + .BYTE "Missing operand",0 + .BYTE "HEX",0 + .BYTE "BIN",0 +#ENDIF +; ; INITIALISATION TABLE ------------------------------------------------------- - -INITAB: JP WARMST ; Warm start jump - JP FCERR ; "USR (X)" jump (Set to Error) - OUT (0),A ; "OUT p,n" skeleton - RET - SUB 0 ; Division support routine - LD L,A - LD A,H - SBC A,0 - LD H,A - LD A,B - SBC A,0 - LD B,A - LD A,0 - RET - .BYTE 0,0,0 ; Random number seed table used by RND - .BYTE 035H,04AH,0CAH,099H ;-2.65145E+07 - .BYTE 039H,01CH,076H,098H ; 1.61291E+07 - .BYTE 022H,095H,0B3H,098H ;-1.17691E+07 - .BYTE 00AH,0DDH,047H,098H ; 1.30983E+07 - .BYTE 053H,0D1H,099H,099H ;-2-01612E+07 - .BYTE 00AH,01AH,09FH,098H ;-1.04269E+07 - .BYTE 065H,0BCH,0CDH,098H ;-1.34831E+07 - .BYTE 0D6H,077H,03EH,098H ; 1.24825E+07 - .BYTE 052H,0C7H,04FH,080H ; Last random number - IN A,(0) ; INP (x) skeleton - RET - .BYTE 1 ; POS (x) number (1) - .BYTE 255 ; Terminal width (255 = no auto CRLF) - .BYTE 28 ; Width for commas (3 columns) - .BYTE 0 ; No nulls after input bytes - .BYTE 0 ; Output enabled (^O off) - .WORD 20 ; Initial lines counter - .WORD 20 ; Initial lines number - .WORD 0 ; Array load/save check sum - .BYTE 0 ; Break not by NMI - .BYTE 0 ; Break flag - JP TTYLIN ; Input reflection (set to TTY) - JP $0000 ; POINT reflection unused - JP $0000 ; SET reflection - JP $0000 ; RESET reflection - .WORD STLOOK ; Temp string space - .WORD -2 ; Current line number (cold) - .WORD PROGST+1 ; Start of program text -INITBE: - +; +INITAB: JP WARMST ; Warm start jump + JP FCERR ; "USR (X)" jump (Set to Error) + OUT (0),A ; "OUT p,n" skeleton + RET + SUB 0 ; Division support routine + LD L,A + LD A,H + SBC A,0 + LD H,A + LD A,B + SBC A,0 + LD B,A + LD A,0 + RET + .BYTE 0,0,0 ; Random number seed table used by RND + .BYTE 035H,04AH,0CAH,099H ;-2.65145E+07 + .BYTE 039H,01CH,076H,098H ; 1.61291E+07 + .BYTE 022H,095H,0B3H,098H ;-1.17691E+07 + .BYTE 00AH,0DDH,047H,098H ; 1.30983E+07 + .BYTE 053H,0D1H,099H,099H ;-2-01612E+07 + .BYTE 00AH,01AH,09FH,098H ;-1.04269E+07 + .BYTE 065H,0BCH,0CDH,098H ;-1.34831E+07 + .BYTE 0D6H,077H,03EH,098H ; 1.24825E+07 + .BYTE 052H,0C7H,04FH,080H ; Last random number + IN A,(0) ; INP (x) skeleton + RET + .BYTE 1 ; POS (x) number (1) + .BYTE 255 ; Terminal width (255 = no auto CRLF) + .BYTE 28 ; Width for commas (3 columns) + .BYTE 0 ; No nulls after input bytes + .BYTE 0 ; Output enabled (^O off) + .WORD 20 ; Initial lines counter + .WORD 20 ; Initial lines number + .WORD 0 ; Array load/save check sum + .BYTE 0 ; Break not by NMI + .BYTE 0 ; Break flag + JP TTYLIN ; Input reflection (set to TTY) +#IF VDUGFX + JP POINTB ; POINT reflection unused + JP SETB ; SET reflection + JP RESETB ; RESET reflection +#ELSE + JP REM + JP REM + JP REM +#ENDIF + .WORD STLOOK ; Temp string space + .WORD -2 ; Current line number (cold) + .WORD PROGST+1 ; Start of program text +INITBE: +; ; END OF INITIALISATION TABLE --------------------------------------------------- - +; ERRMSG: .BYTE " Error",0 INMSG: .BYTE " in ",0 -ZERBYT .EQU $-1 ; A zero byte +ZERBYT .EQU $-1 ; A zero byte OKMSG: .BYTE "Ok",CR,LF,0,0 BRKMSG: .BYTE "Break",0 -BAKSTK: LD HL,4 ; Look for "FOR" block with - ADD HL,SP ; same index as specified -LOKFOR: LD A,(HL) ; Get block ID - INC HL ; Point to index address - CP ZFOR ; Is it a "FOR" token - RET NZ ; No - exit - LD C,(HL) ; BC = Address of "FOR" index - INC HL - LD B,(HL) - INC HL ; Point to sign of STEP - PUSH HL ; Save pointer to sign - LD L,C ; HL = address of "FOR" index - LD H,B - LD A,D ; See if an index was specified - OR E ; DE = 0 if no index specified - EX DE,HL ; Specified index into HL - JP Z,INDFND ; Skip if no index given - EX DE,HL ; Index back into DE - CALL CPDEHL ; Compare index with one given -INDFND: LD BC,16-3 ; Offset to next block - POP HL ; Restore pointer to sign - RET Z ; Return if block found - ADD HL,BC ; Point to next block - JP LOKFOR ; Keep on looking - -MOVUP: CALL ENFMEM ; See if enough memory -MOVSTR: PUSH BC ; Save end of source - EX (SP),HL ; Swap source and dest" end - POP BC ; Get end of destination -MOVLP: CALL CPDEHL ; See if list moved - LD A,(HL) ; Get byte - LD (BC),A ; Move it - RET Z ; Exit if all done - DEC BC ; Next byte to move to - DEC HL ; Next byte to move - JP MOVLP ; Loop until all bytes moved - -CHKSTK: PUSH HL ; Save code string address - LD HL,(ARREND) ; Lowest free memory - LD B,0 ; BC = Number of levels to test - ADD HL,BC ; 2 Bytes for each level - ADD HL,BC - .BYTE 3EH ; Skip "PUSH HL" -ENFMEM: PUSH HL ; Save code string address - LD A,0D0H ;LOW -48 ; 48 Bytes minimum RAM - SUB L - LD L,A - LD A,0FFH; HIGH (-48) ; 48 Bytes minimum RAM - SBC A,H - JP C,OMERR ; Not enough - ?OM Error - LD H,A - ADD HL,SP ; Test if stack is overflowed - POP HL ; Restore code string address - RET C ; Return if enough mmory -OMERR: LD E,OM ; ?OM Error - JP ERROR - -DATSNR: LD HL,(DATLIN) ; Get line of current DATA item - LD (LINEAT),HL ; Save as current line -SNERR: LD E,SN ; ?SN Error - .BYTE 01H ; Skip "LD E,DZ" -DZERR: LD E,DZ ; ?/0 Error - .BYTE 01H ; Skip "LD E,NF" -NFERR: LD E,NF ; ?NF Error - .BYTE 01H ; Skip "LD E,DD" -DDERR: LD E,DD ; ?DD Error - .BYTE 01H ; Skip "LD E,UF" -UFERR: LD E,UF ; ?UF Error - .BYTE 01H ; Skip "LD E,OV -OVERR: LD E,OV ; ?OV Error - .BYTE 01H ; Skip "LD E,TM" -TMERR: LD E,TM ; ?TM Error - -ERROR: CALL CLREG ; Clear registers and stack - LD (CTLOFG),A ; Enable output (A is 0) - CALL STTLIN ; Start new line - LD HL,ERRORS ; Point to error codes - LD D,A ; D = 0 (A is 0) - LD A,'?' - CALL OUTC ; Output '?' - ADD HL,DE ; Offset to correct error code - LD A,(HL) ; First character - CALL OUTC ; Output it - CALL GETCHR ; Get next character - CALL OUTC ; Output it - LD HL,ERRMSG ; "Error" message -ERRIN: CALL PRS ; Output message - LD HL,(LINEAT) ; Get line of error - LD DE,-2 ; Cold start error if -2 - CALL CPDEHL ; See if cold start error - JP Z,CSTART ; Cold start error - Restart - LD A,H ; Was it a direct error? - AND L ; Line = -1 if direct error - INC A - CALL NZ,LINEIN ; No - output line of error - .BYTE 3EH ; Skip "POP BC" -POPNOK: POP BC ; Drop address in input buffer - -PRNTOK: XOR A ; Output "Ok" and get command - LD (CTLOFG),A ; Enable output - CALL STTLIN ; Start new line - LD HL,OKMSG ; "Ok" message - CALL PRS ; Output "Ok" -GETCMD: LD HL,-1 ; Flag direct mode - LD (LINEAT),HL ; Save as current line - CALL GETLIN ; Get an input line - JP C,GETCMD ; Get line again if break - CALL GETCHR ; Get first character - INC A ; Test if end of line - DEC A ; Without affecting Carry - JP Z,GETCMD ; Nothing entered - Get another - PUSH AF ; Save Carry status - CALL ATOH ; Get line number into DE - PUSH DE ; Save line number - CALL CRUNCH ; Tokenise rest of line - LD B,A ; Length of tokenised line - POP DE ; Restore line number - POP AF ; Restore Carry - JP NC,EXCUTE ; No line number - Direct mode - PUSH DE ; Save line number - PUSH BC ; Save length of tokenised line - XOR A - LD (LSTBIN),A ; Clear last byte input - CALL GETCHR ; Get next character - OR A ; Set flags - PUSH AF ; And save them - CALL SRCHLN ; Search for line number in DE - JP C,LINFND ; Jump if line found - POP AF ; Get status - PUSH AF ; And re-save - JP Z,ULERR ; Nothing after number - Error - OR A ; Clear Carry -LINFND: PUSH BC ; Save address of line in prog - JP NC,INEWLN ; Line not found - Insert new - EX DE,HL ; Next line address in DE - LD HL,(PROGND) ; End of program -SFTPRG: LD A,(DE) ; Shift rest of program down - LD (BC),A - INC BC ; Next destination - INC DE ; Next source - CALL CPDEHL ; All done? - JP NZ,SFTPRG ; More to do - LD H,B ; HL - New end of program - LD L,C - LD (PROGND),HL ; Update end of program - -INEWLN: POP DE ; Get address of line, - POP AF ; Get status - JP Z,SETPTR ; No text - Set up pointers - LD HL,(PROGND) ; Get end of program - EX (SP),HL ; Get length of input line - POP BC ; End of program to BC - ADD HL,BC ; Find new end - PUSH HL ; Save new end - CALL MOVUP ; Make space for line - POP HL ; Restore new end - LD (PROGND),HL ; Update end of program pointer - EX DE,HL ; Get line to move up in HL - LD (HL),H ; Save MSB - POP DE ; Get new line number - INC HL ; Skip pointer - INC HL - LD (HL),E ; Save LSB of line number - INC HL - LD (HL),D ; Save MSB of line number - INC HL ; To first byte in line - LD DE,BUFFER ; Copy buffer to program -MOVBUF: LD A,(DE) ; Get source - LD (HL),A ; Save destinations - INC HL ; Next source - INC DE ; Next destination - OR A ; Done? - JP NZ,MOVBUF ; No - Repeat -SETPTR: CALL RUNFST ; Set line pointers - INC HL ; To LSB of pointer - EX DE,HL ; Address to DE -PTRLP: LD H,D ; Address to HL - LD L,E - LD A,(HL) ; Get LSB of pointer - INC HL ; To MSB of pointer - OR (HL) ; Compare with MSB pointer - JP Z,GETCMD ; Get command line if end - INC HL ; To LSB of line number - INC HL ; Skip line number - INC HL ; Point to first byte in line - XOR A ; Looking for 00 byte -FNDEND: CP (HL) ; Found end of line? - INC HL ; Move to next byte - JP NZ,FNDEND ; No - Keep looking - EX DE,HL ; Next line address to HL - LD (HL),E ; Save LSB of pointer - INC HL - LD (HL),D ; Save MSB of pointer - JP PTRLP ; Do next line - -SRCHLN: LD HL,(BASTXT) ; Start of program text -SRCHLP: LD B,H ; BC = Address to look at - LD C,L - LD A,(HL) ; Get address of next line - INC HL - OR (HL) ; End of program found? - DEC HL - RET Z ; Yes - Line not found - INC HL - INC HL - LD A,(HL) ; Get LSB of line number - INC HL - LD H,(HL) ; Get MSB of line number - LD L,A - CALL CPDEHL ; Compare with line in DE - LD H,B ; HL = Start of this line - LD L,C - LD A,(HL) ; Get LSB of next line address - INC HL - LD H,(HL) ; Get MSB of next line address - LD L,A ; Next line to HL - CCF - RET Z ; Lines found - Exit - CCF - RET NC ; Line not found,at line after - JP SRCHLP ; Keep looking - -NEW: RET NZ ; Return if any more on line -CLRPTR: LD HL,(BASTXT) ; Point to start of program - XOR A ; Set program area to empty - LD (HL),A ; Save LSB = 00 - INC HL - LD (HL),A ; Save MSB = 00 - INC HL - LD (PROGND),HL ; Set program end - -RUNFST: LD HL,(BASTXT) ; Clear all variables - DEC HL - -INTVAR: LD (BRKLIN),HL ; Initialise RUN variables - LD HL,(LSTRAM) ; Get end of RAM - LD (STRBOT),HL ; Clear string space - XOR A - CALL RESTOR ; Reset DATA pointers - LD HL,(PROGND) ; Get end of program - LD (VAREND),HL ; Clear variables - LD (ARREND),HL ; Clear arrays - -CLREG: POP BC ; Save return address - LD HL,(STRSPC) ; Get end of working RAN - LD SP,HL ; Set stack - LD HL,TMSTPL ; Temporary string pool - LD (TMSTPT),HL ; Reset temporary string ptr - XOR A ; A = 00 - LD L,A ; HL = 0000 - LD H,A - LD (CONTAD),HL ; No CONTinue - LD (FORFLG),A ; Clear FOR flag - LD (FNRGNM),HL ; Clear FN argument - PUSH HL ; HL = 0000 - PUSH BC ; Put back return -DOAGN: LD HL,(BRKLIN) ; Get address of code to RUN - RET ; Return to execution driver - -PROMPT: LD A,'?' ; '?' - CALL OUTC ; Output character - LD A,' ' ; Space - CALL OUTC ; Output character - JP RINPUT ; Get input line - -CRUNCH: XOR A ; Tokenise line @ HL to BUFFER - LD (DATFLG),A ; Reset literal flag - LD C,2+3 ; 2 byte number and 3 nulls - LD DE,BUFFER ; Start of input buffer -CRNCLP: LD A,(HL) ; Get byte - CP ' ' ; Is it a space? - JP Z,MOVDIR ; Yes - Copy direct - LD B,A ; Save character - CP '"' ; Is it a quote? - JP Z,CPYLIT ; Yes - Copy literal string - OR A ; Is it end of buffer? - JP Z,ENDBUF ; Yes - End buffer - LD A,(DATFLG) ; Get data type - OR A ; Literal? - LD A,(HL) ; Get byte to copy - JP NZ,MOVDIR ; Literal - Copy direct - CP '?' ; Is it '?' short for PRINT - LD A,ZPRINT ; "PRINT" token - JP Z,MOVDIR ; Yes - replace it - LD A,(HL) ; Get byte again - CP '0' ; Is it less than '0' - JP C,FNDWRD ; Yes - Look for reserved words - CP 60; ";"+1 ; Is it "0123456789:;" ? - JP C,MOVDIR ; Yes - copy it direct -FNDWRD: PUSH DE ; Look for reserved words - LD DE,WORDS-1 ; Point to table - PUSH BC ; Save count - LD BC,RETNAD ; Where to return to - PUSH BC ; Save return address - LD B,ZEND-1 ; First token value -1 - LD A,(HL) ; Get byte - CP 'a' ; Less than 'a' ? - JP C,SEARCH ; Yes - search for words - CP 'z'+1 ; Greater than 'z' ? - JP NC,SEARCH ; Yes - search for words - AND 01011111B ; Force upper case - LD (HL),A ; Replace byte -SEARCH: LD C,(HL) ; Search for a word - EX DE,HL -GETNXT: INC HL ; Get next reserved word - OR (HL) ; Start of word? - JP P,GETNXT ; No - move on - INC B ; Increment token value - LD A, (HL) ; Get byte from table - AND 01111111B ; Strip bit 7 - RET Z ; Return if end of list - CP C ; Same character as in buffer? - JP NZ,GETNXT ; No - get next word - EX DE,HL - PUSH HL ; Save start of word - -NXTBYT: INC DE ; Look through rest of word - LD A,(DE) ; Get byte from table - OR A ; End of word ? - JP M,MATCH ; Yes - Match found - LD C,A ; Save it - LD A,B ; Get token value - CP ZGOTO ; Is it "GOTO" token ? - JP NZ,NOSPC ; No - Don't allow spaces - CALL GETCHR ; Get next character - DEC HL ; Cancel increment from GETCHR -NOSPC: INC HL ; Next byte - LD A,(HL) ; Get byte - CP 'a' ; Less than 'a' ? - JP C,NOCHNG ; Yes - don't change - AND 01011111B ; Make upper case -NOCHNG: CP C ; Same as in buffer ? - JP Z,NXTBYT ; Yes - keep testing - POP HL ; Get back start of word - JP SEARCH ; Look at next word - -MATCH: LD C,B ; Word found - Save token value - POP AF ; Throw away return - EX DE,HL - RET ; Return to "RETNAD" -RETNAD: EX DE,HL ; Get address in string - LD A,C ; Get token value - POP BC ; Restore buffer length - POP DE ; Get destination address -MOVDIR: INC HL ; Next source in buffer - LD (DE),A ; Put byte in buffer - INC DE ; Move up buffer - INC C ; Increment length of buffer - SUB ':' ; End of statement? - JP Z,SETLIT ; Jump if multi-statement line - CP ZDATA-3AH ; Is it DATA statement ? - JP NZ,TSTREM ; No - see if REM -SETLIT: LD (DATFLG),A ; Set literal flag -TSTREM: SUB ZREM-3AH ; Is it REM? - JP NZ,CRNCLP ; No - Leave flag - LD B,A ; Copy rest of buffer -NXTCHR: LD A,(HL) ; Get byte - OR A ; End of line ? - JP Z,ENDBUF ; Yes - Terminate buffer - CP B ; End of statement ? - JP Z,MOVDIR ; Yes - Get next one -CPYLIT: INC HL ; Move up source string - LD (DE),A ; Save in destination - INC C ; Increment length - INC DE ; Move up destination - JP NXTCHR ; Repeat - -ENDBUF: LD HL,BUFFER-1 ; Point to start of buffer - LD (DE),A ; Mark end of buffer (A = 00) - INC DE - LD (DE),A ; A = 00 - INC DE - LD (DE),A ; A = 00 - RET - -DODEL: LD A,(NULFLG) ; Get null flag status - OR A ; Is it zero? - LD A,0 ; Zero A - Leave flags - LD (NULFLG),A ; Zero null flag - JP NZ,ECHDEL ; Set - Echo it - DEC B ; Decrement length - JP Z,GETLIN ; Get line again if empty - CALL OUTC ; Output null character - .BYTE 3EH ; Skip "DEC B" -ECHDEL: DEC B ; Count bytes in buffer - DEC HL ; Back space buffer - JP Z,OTKLN ; No buffer - Try again - LD A,(HL) ; Get deleted byte - CALL OUTC ; Echo it - JP MORINP ; Get more input - -DELCHR: DEC B ; Count bytes in buffer - DEC HL ; Back space buffer - CALL OUTC ; Output character in A - JP NZ,MORINP ; Not end - Get more -OTKLN: CALL OUTC ; Output character in A -KILIN: CALL PRNTCRLF ; Output CRLF - JP TTYLIN ; Get line again +BAKSTK: LD HL,4 ; Look for "FOR" block with + ADD HL,SP ; same index as specified +LOKFOR: LD A,(HL) ; Get block ID + INC HL ; Point to index address + CP ZFOR ; Is it a "FOR" token + RET NZ ; No - exit + LD C,(HL) ; BC = Address of "FOR" index + INC HL + LD B,(HL) + INC HL ; Point to sign of STEP + PUSH HL ; Save pointer to sign + LD L,C ; HL = address of "FOR" index + LD H,B + LD A,D ; See if an index was specified + OR E ; DE = 0 if no index specified + EX DE,HL ; Specified index into HL + JP Z,INDFND ; Skip if no index given + EX DE,HL ; Index back into DE + CALL CPDEHL ; Compare index with one given +INDFND: LD BC,16-3 ; Offset to next block + POP HL ; Restore pointer to sign + RET Z ; Return if block found + ADD HL,BC ; Point to next block + JP LOKFOR ; Keep on looking + +MOVUP: CALL ENFMEM ; See if enough memory +MOVSTR: PUSH BC ; Save end of source + EX (SP),HL ; Swap source and dest" end + POP BC ; Get end of destination +MOVLP: CALL CPDEHL ; See if list moved + LD A,(HL) ; Get byte + LD (BC),A ; Move it + RET Z ; Exit if all done + DEC BC ; Next byte to move to + DEC HL ; Next byte to move + JP MOVLP ; Loop until all bytes moved + +CHKSTK: PUSH HL ; Save code string address + LD HL,(ARREND) ; Lowest free memory + LD B,0 ; BC = Number of levels to test + ADD HL,BC ; 2 Bytes for each level + ADD HL,BC + .BYTE 3EH ; Skip "PUSH HL" +ENFMEM: PUSH HL ; Save code string address + LD A,0D0H ;LOW -48 ; 48 Bytes minimum RAM + SUB L + LD L,A + LD A,0FFH ; HIGH (-48) ; 48 Bytes minimum RAM + SBC A,H + JP C,OMERR ; Not enough - ?OM Error + LD H,A + ADD HL,SP ; Test if stack is overflowed + POP HL ; Restore code string address + RET C ; Return if enough mmory +OMERR: LD E,OM ; ?OM Error + JP ERROR + +DATSNR: LD HL,(DATLIN) ; Get line of current DATA item + LD (LINEAT),HL ; Save as current line +SNERR: LD E,SN ; ?SN Error + .BYTE 01H ; Skip "LD E,DZ" +DZERR: LD E,DZ ; ?/0 Error + .BYTE 01H ; Skip "LD E,NF" +NFERR: LD E,NF ; ?NF Error + .BYTE 01H ; Skip "LD E,DD" +DDERR: LD E,DD ; ?DD Error + .BYTE 01H ; Skip "LD E,UF" +UFERR: LD E,UF ; ?UF Error + .BYTE 01H ; Skip "LD E,OV +OVERR: LD E,OV ; ?OV Error + .BYTE 01H ; Skip "LD E,TM" +TMERR: LD E,TM ; ?TM Error + +ERROR: CALL CLREG ; Clear registers and stack + LD (CTLOFG),A ; Enable output (A is 0) + CALL STTLIN ; Start new line + LD HL,ERRORS ; Point to error codes + LD D,A ; D = 0 (A is 0) + LD A,'?' + CALL OUTC ; Output '?' +#IF ABBRERR + ADD HL,DE ; Offset to correct error code + LD A,(HL) ; First character + CALL OUTC ; Output it + CALL GETCHR ; Get next character + CALL OUTC ; Output it +#ELSE + PUSH BC ; Count through + LD B,E ; the error list + SRL B ; until we get + JR Z,CHRZRO ; error message +NXCHR: LD A,(HL) ; + OR A ; E/2 = entry + INC HL ; number in the + JR NZ,NXCHR ; list. + DJNZ NXCHR +CHRZRO: CALL PRS ; Display message. + POP BC +#ENDIF + LD HL,ERRMSG ; "Error" message +ERRIN: CALL PRS ; Output message + LD HL,(LINEAT) ; Get line of error + LD DE,-2 ; Cold start error if -2 + CALL CPDEHL ; See if cold start error + JP Z,CSTART ; Cold start error - Restart + LD A,H ; Was it a direct error? + AND L ; Line = -1 if direct error + INC A + CALL NZ,LINEIN ; No - output line of error + .BYTE 3EH ; Skip "POP BC" +POPNOK: POP BC ; Drop address in input buffer + +PRNTOK: XOR A ; Output "Ok" and get command + LD (CTLOFG),A ; Enable output + CALL STTLIN ; Start new line + LD HL,OKMSG ; "Ok" message + CALL PRS ; Output "Ok" +GETCMD: LD HL,-1 ; Flag direct mode + LD (LINEAT),HL ; Save as current line + CALL GETLIN ; Get an input line + JP C,GETCMD ; Get line again if break + CALL GETCHR ; Get first character + INC A ; Test if end of line + DEC A ; Without affecting Carry + JP Z,GETCMD ; Nothing entered - Get another + PUSH AF ; Save Carry status + CALL ATOH ; Get line number into DE + PUSH DE ; Save line number + CALL CRUNCH ; Tokenise rest of line + LD B,A ; Length of tokenised line + POP DE ; Restore line number + POP AF ; Restore Carry + JP NC,EXCUTE ; No line number - Direct mode + PUSH DE ; Save line number + PUSH BC ; Save length of tokenised line + XOR A + LD (LSTBIN),A ; Clear last byte input + CALL GETCHR ; Get next character + OR A ; Set flags + PUSH AF ; And save them + CALL SRCHLN ; Search for line number in DE + JP C,LINFND ; Jump if line found + POP AF ; Get status + PUSH AF ; And re-save + JP Z,ULERR ; Nothing after number - Error + OR A ; Clear Carry +LINFND: PUSH BC ; Save address of line in prog + JP NC,INEWLN ; Line not found - Insert new + EX DE,HL ; Next line address in DE + LD HL,(PROGND) ; End of program +SFTPRG: LD A,(DE) ; Shift rest of program down + LD (BC),A + INC BC ; Next destination + INC DE ; Next source + CALL CPDEHL ; All done? + JP NZ,SFTPRG ; More to do + LD H,B ; HL - New end of program + LD L,C + LD (PROGND),HL ; Update end of program + +INEWLN: POP DE ; Get address of line, + POP AF ; Get status + JP Z,SETPTR ; No text - Set up pointers + LD HL,(PROGND) ; Get end of program + EX (SP),HL ; Get length of input line + POP BC ; End of program to BC + ADD HL,BC ; Find new end + PUSH HL ; Save new end + CALL MOVUP ; Make space for line + POP HL ; Restore new end + LD (PROGND),HL ; Update end of program pointer + EX DE,HL ; Get line to move up in HL + LD (HL),H ; Save MSB + POP DE ; Get new line number + INC HL ; Skip pointer + INC HL + LD (HL),E ; Save LSB of line number + INC HL + LD (HL),D ; Save MSB of line number + INC HL ; To first byte in line + LD DE,BUFFER ; Copy buffer to program +MOVBUF: LD A,(DE) ; Get source + LD (HL),A ; Save destinations + INC HL ; Next source + INC DE ; Next destination + OR A ; Done? + JP NZ,MOVBUF ; No - Repeat +SETPTR: CALL RUNFST ; Set line pointers + INC HL ; To LSB of pointer + EX DE,HL ; Address to DE +PTRLP: LD H,D ; Address to HL + LD L,E + LD A,(HL) ; Get LSB of pointer + INC HL ; To MSB of pointer + OR (HL) ; Compare with MSB pointer + JP Z,GETCMD ; Get command line if end + INC HL ; To LSB of line number + INC HL ; Skip line number + INC HL ; Point to first byte in line + XOR A ; Looking for 00 byte +FNDEND: CP (HL) ; Found end of line? + INC HL ; Move to next byte + JP NZ,FNDEND ; No - Keep looking + EX DE,HL ; Next line address to HL + LD (HL),E ; Save LSB of pointer + INC HL + LD (HL),D ; Save MSB of pointer + JP PTRLP ; Do next line + +SRCHLN: LD HL,(BASTXT) ; Start of program text +SRCHLP: LD B,H ; BC = Address to look at + LD C,L + LD A,(HL) ; Get address of next line + INC HL + OR (HL) ; End of program found? + DEC HL + RET Z ; Yes - Line not found + INC HL + INC HL + LD A,(HL) ; Get LSB of line number + INC HL + LD H,(HL) ; Get MSB of line number + LD L,A + CALL CPDEHL ; Compare with line in DE + LD H,B ; HL = Start of this line + LD L,C + LD A,(HL) ; Get LSB of next line address + INC HL + LD H,(HL) ; Get MSB of next line address + LD L,A ; Next line to HL + CCF + RET Z ; Lines found - Exit + CCF + RET NC ; Line not found,at line after + JP SRCHLP ; Keep looking + +NEW: RET NZ ; Return if any more on line +CLRPTR: LD HL,(BASTXT) ; Point to start of program + XOR A ; Set program area to empty + LD (HL),A ; Save LSB = 00 + INC HL + LD (HL),A ; Save MSB = 00 + INC HL + LD (PROGND),HL ; Set program end + +RUNFST: LD HL,(BASTXT) ; Clear all variables + DEC HL + +INTVAR: LD (BRKLIN),HL ; Initialise RUN variables + LD HL,(LSTRAM) ; Get end of RAM + LD (STRBOT),HL ; Clear string space + XOR A + CALL RESTOR ; Reset DATA pointers + LD HL,(PROGND) ; Get end of program + LD (VAREND),HL ; Clear variables + LD (ARREND),HL ; Clear arrays + +CLREG: POP BC ; Save return address + LD HL,(STRSPC) ; Get end of working RAN + LD SP,HL ; Set stack + LD HL,TMSTPL ; Temporary string pool + LD (TMSTPT),HL ; Reset temporary string ptr + XOR A ; A = 00 + LD L,A ; HL = 0000 + LD H,A + LD (CONTAD),HL ; No CONTinue + LD (FORFLG),A ; Clear FOR flag + LD (FNRGNM),HL ; Clear FN argument + PUSH HL ; HL = 0000 + PUSH BC ; Put back return +DOAGN: LD HL,(BRKLIN) ; Get address of code to RUN + RET ; Return to execution driver + +PROMPT: LD A,'?' ; '?' + CALL OUTC ; Output character + LD A,' ' ; Space + CALL OUTC ; Output character + JP RINPUT ; Get input line + +CRUNCH: XOR A ; Tokenise line @ HL to BUFFER + LD (DATFLG),A ; Reset literal flag + LD C,2+3 ; 2 byte number and 3 nulls + LD DE,BUFFER ; Start of input buffer +CRNCLP: LD A,(HL) ; Get byte + CP ' ' ; Is it a space? + JP Z,MOVDIR ; Yes - Copy direct + LD B,A ; Save character + CP '"' ; Is it a quote? + JP Z,CPYLIT ; Yes - Copy literal string + OR A ; Is it end of buffer? + JP Z,ENDBUF ; Yes - End buffer + LD A,(DATFLG) ; Get data type + OR A ; Literal? + LD A,(HL) ; Get byte to copy + JP NZ,MOVDIR ; Literal - Copy direct + CP '?' ; Is it '?' short for PRINT + LD A,ZPRINT ; "PRINT" token + JP Z,MOVDIR ; Yes - replace it + LD A,(HL) ; Get byte again + CP '0' ; Is it less than '0' + JP C,FNDWRD ; Yes - Look for reserved words + CP 60; ";"+1 ; Is it "0123456789:;" ? + JP C,MOVDIR ; Yes - copy it direct +FNDWRD: PUSH DE ; Look for reserved words + LD DE,WORDS-1 ; Point to table + PUSH BC ; Save count + LD BC,RETNAD ; Where to return to + PUSH BC ; Save return address + LD B,ZEND-1 ; First token value -1 + LD A,(HL) ; Get byte + CP 'a' ; Less than 'a' ? + JP C,SEARCH ; Yes - search for words + CP 'z'+1 ; Greater than 'z' ? + JP NC,SEARCH ; Yes - search for words + AND 01011111B ; Force upper case + LD (HL),A ; Replace byte +SEARCH: LD C,(HL) ; Search for a word + EX DE,HL +GETNXT: INC HL ; Get next reserved word + OR (HL) ; Start of word? + JP P,GETNXT ; No - move on + INC B ; Increment token value + LD A, (HL) ; Get byte from table + AND 01111111B ; Strip bit 7 + RET Z ; Return if end of list + CP C ; Same character as in buffer? + JP NZ,GETNXT ; No - get next word + EX DE,HL + PUSH HL ; Save start of word + +NXTBYT: INC DE ; Look through rest of word + LD A,(DE) ; Get byte from table + OR A ; End of word ? + JP M,MATCH ; Yes - Match found + LD C,A ; Save it + LD A,B ; Get token value + CP ZGOTO ; Is it "GOTO" token ? + JP NZ,NOSPC ; No - Don't allow spaces + CALL GETCHR ; Get next character + DEC HL ; Cancel increment from GETCHR +NOSPC: INC HL ; Next byte + LD A,(HL) ; Get byte + CP 'a' ; Less than 'a' ? + JP C,NOCHNG ; Yes - don't change + AND 01011111B ; Make upper case +NOCHNG: CP C ; Same as in buffer ? + JP Z,NXTBYT ; Yes - keep testing + POP HL ; Get back start of word + JP SEARCH ; Look at next word + +MATCH: LD C,B ; Word found - Save token value + POP AF ; Throw away return + EX DE,HL + RET ; Return to "RETNAD" +RETNAD: EX DE,HL ; Get address in string + LD A,C ; Get token value + POP BC ; Restore buffer length + POP DE ; Get destination address +MOVDIR: INC HL ; Next source in buffer + LD (DE),A ; Put byte in buffer + INC DE ; Move up buffer + INC C ; Increment length of buffer + SUB ':' ; End of statement? + JP Z,SETLIT ; Jump if multi-statement line + CP ZDATA-3AH ; Is it DATA statement ? + JP NZ,TSTREM ; No - see if REM +SETLIT: LD (DATFLG),A ; Set literal flag +TSTREM: SUB ZREM-3AH ; Is it REM? + JP NZ,CRNCLP ; No - Leave flag + LD B,A ; Copy rest of buffer +NXTCHR: LD A,(HL) ; Get byte + OR A ; End of line ? + JP Z,ENDBUF ; Yes - Terminate buffer + CP B ; End of statement ? + JP Z,MOVDIR ; Yes - Get next one +CPYLIT: INC HL ; Move up source string + LD (DE),A ; Save in destination + INC C ; Increment length + INC DE ; Move up destination + JP NXTCHR ; Repeat + +ENDBUF: LD HL,BUFFER-1 ; Point to start of buffer + LD (DE),A ; Mark end of buffer (A = 00) + INC DE + LD (DE),A ; A = 00 + INC DE + LD (DE),A ; A = 00 + RET + +DODEL: LD A,(NULFLG) ; Get null flag status + OR A ; Is it zero? + LD A,0 ; Zero A - Leave flags + LD (NULFLG),A ; Zero null flag + JP NZ,ECHDEL ; Set - Echo it + DEC B ; Decrement length + JP Z,GETLIN ; Get line again if empty + CALL OUTC ; Output null character + .BYTE 3EH ; Skip "DEC B" +ECHDEL: DEC B ; Count bytes in buffer + DEC HL ; Back space buffer + JP Z,OTKLN ; No buffer - Try again + LD A,(HL) ; Get deleted byte + CALL OUTC ; Echo it + JP MORINP ; Get more input + +DELCHR: DEC B ; Count bytes in buffer + DEC HL ; Back space buffer + CALL OUTC ; Output character in A + JP NZ,MORINP ; Not end - Get more +OTKLN: CALL OUTC ; Output character in A +KILIN: CALL PRNTCRLF ; Output CRLF + JP TTYLIN ; Get line again GETLIN: -TTYLIN: LD HL,BUFFER ; Get a line by character - LD B,1 ; Set buffer as empty - XOR A - LD (NULFLG),A ; Clear null flag -MORINP: CALL CLOTST ; Get character and test ^O - LD C,A ; Save character in C - CP DEL ; Delete character? - JP Z,DODEL ; Yes - Process it - LD A,(NULFLG) ; Get null flag - OR A ; Test null flag status - JP Z,PROCES ; Reset - Process character - LD A,0 ; Set a null - CALL OUTC ; Output null - XOR A ; Clear A - LD (NULFLG),A ; Reset null flag -PROCES: LD A,C ; Get character - CP CTRLG ; Bell? - JP Z,PUTCTL ; Yes - Save it - CP CTRLC ; Is it control "C"? - CALL Z,PRNTCRLF ; Yes - Output CRLF - SCF ; Flag break - RET Z ; Return if control "C" - CP CR ; Is it enter? - JP Z,ENDINP ; Yes - Terminate input - CP CTRLU ; Is it control "U"? - JP Z,KILIN ; Yes - Get another line - CP '@' ; Is it "kill line"? - JP Z,OTKLN ; Yes - Kill line - CP '_' ; Is it delete? - JP Z,DELCHR ; Yes - Delete character - CP BKSP ; Is it backspace? - JP Z,DELCHR ; Yes - Delete character - CP CTRLR ; Is it control "R"? - JP NZ,PUTBUF ; No - Put in buffer - PUSH BC ; Save buffer length - PUSH DE ; Save DE - PUSH HL ; Save buffer address - LD (HL),0 ; Mark end of buffer - CALL OUTNCR ; Output and do CRLF - LD HL,BUFFER ; Point to buffer start - CALL PRS ; Output buffer - POP HL ; Restore buffer address - POP DE ; Restore DE - POP BC ; Restore buffer length - JP MORINP ; Get another character - -PUTBUF: CP ' ' ; Is it a control code? - JP C,MORINP ; Yes - Ignore -PUTCTL: LD A,B ; Get number of bytes in buffer - CP 72+1 ; Test for line overflow - LD A,CTRLG ; Set a bell - JP NC,OUTNBS ; Ring bell if buffer full - LD A,C ; Get character - LD (HL),C ; Save in buffer - LD (LSTBIN),A ; Save last input byte - INC HL ; Move up buffer - INC B ; Increment length -OUTIT: CALL OUTC ; Output the character entered - JP MORINP ; Get another character - -OUTNBS: CALL OUTC ; Output bell and back over it - LD A,BKSP ; Set back space - JP OUTIT ; Output it and get more - -CPDEHL: LD A,H ; Get H - SUB D ; Compare with D - RET NZ ; Different - Exit - LD A,L ; Get L - SUB E ; Compare with E - RET ; Return status - -CHKSYN: LD A,(HL) ; Check syntax of character - EX (SP),HL ; Address of test byte - CP (HL) ; Same as in code string? - INC HL ; Return address - EX (SP),HL ; Put it back - JP Z,GETCHR ; Yes - Get next character - JP SNERR ; Different - ?SN Error - -OUTC: PUSH AF ; Save character - LD A,(CTLOFG) ; Get control "O" flag - OR A ; Is it set? - JP NZ,POPAF ; Yes - don't output - POP AF ; Restore character - PUSH BC ; Save buffer length - PUSH AF ; Save character - CP ' ' ; Is it a control code? - JP C,DINPOS ; Yes - Don't INC POS(X) - LD A,(LWIDTH) ; Get line width - LD B,A ; To B - LD A,(CURPOS) ; Get cursor position - INC B ; Width 255? - JP Z,INCLEN ; Yes - No width limit - DEC B ; Restore width - CP B ; At end of line? - CALL Z,PRNTCRLF ; Yes - output CRLF -INCLEN: INC A ; Move on one character - LD (CURPOS),A ; Save new position -DINPOS: POP AF ; Restore character - POP BC ; Restore buffer length - CALL MONOUT ; Send it - RET - -CLOTST: CALL GETINP ; Get input character - AND 01111111B ; Strip bit 7 - CP CTRLO ; Is it control "O"? - RET NZ ; No don't flip flag - LD A,(CTLOFG) ; Get flag - CPL ; Flip it - LD (CTLOFG),A ; Put it back - XOR A ; Null character - RET - -LIST: CALL ATOH ; ASCII number to DE - RET NZ ; Return if anything extra - POP BC ; Rubbish - Not needed - CALL SRCHLN ; Search for line number in DE - PUSH BC ; Save address of line - CALL SETLIN ; Set up lines counter -LISTLP: POP HL ; Restore address of line - LD C,(HL) ; Get LSB of next line - INC HL - LD B,(HL) ; Get MSB of next line - INC HL - LD A,B ; BC = 0 (End of program)? - OR C - JP Z,PRNTOK ; Yes - Go to command mode - CALL COUNT ; Count lines - CALL TSTBRK ; Test for break key - PUSH BC ; Save address of next line - CALL PRNTCRLF ; Output CRLF - LD E,(HL) ; Get LSB of line number - INC HL - LD D,(HL) ; Get MSB of line number - INC HL - PUSH HL ; Save address of line start - EX DE,HL ; Line number to HL - CALL PRNTHL ; Output line number in decimal - LD A,' ' ; Space after line number - POP HL ; Restore start of line address -LSTLP2: CALL OUTC ; Output character in A -LSTLP3: LD A,(HL) ; Get next byte in line - OR A ; End of line? - INC HL ; To next byte in line - JP Z,LISTLP ; Yes - get next line - JP P,LSTLP2 ; No token - output it - SUB ZEND-1 ; Find and output word - LD C,A ; Token offset+1 to C - LD DE,WORDS ; Reserved word list -FNDTOK: LD A,(DE) ; Get character in list - INC DE ; Move on to next - OR A ; Is it start of word? - JP P,FNDTOK ; No - Keep looking for word - DEC C ; Count words - JP NZ,FNDTOK ; Not there - keep looking -OUTWRD: AND 01111111B ; Strip bit 7 - CALL OUTC ; Output first character - LD A,(DE) ; Get next character - INC DE ; Move on to next - OR A ; Is it end of word? - JP P,OUTWRD ; No - output the rest - JP LSTLP3 ; Next byte in line - -SETLIN: PUSH HL ; Set up LINES counter - LD HL,(LINESN) ; Get LINES number - LD (LINESC),HL ; Save in LINES counter - POP HL - RET - -COUNT: PUSH HL ; Save code string address - PUSH DE - LD HL,(LINESC) ; Get LINES counter - LD DE,-1 - ADC HL,DE ; Decrement - LD (LINESC),HL ; Put it back - POP DE - POP HL ; Restore code string address - RET P ; Return if more lines to go - PUSH HL ; Save code string address - LD HL,(LINESN) ; Get LINES number - LD (LINESC),HL ; Reset LINES counter - CALL GETINP ; Get input character - CP CTRLC ; Is it control "C"? - JP Z,RSLNBK ; Yes - Reset LINES and break - POP HL ; Restore code string address - JP COUNT ; Keep on counting - -RSLNBK: LD HL,(LINESN) ; Get LINES number - LD (LINESC),HL ; Reset LINES counter - JP BRKRET ; Go and output "Break" - -FOR: LD A,64H ; Flag "FOR" assignment - LD (FORFLG),A ; Save "FOR" flag - CALL LET ; Set up initial index - POP BC ; Drop RETurn address - PUSH HL ; Save code string address - CALL DATA ; Get next statement address - LD (LOOPST),HL ; Save it for start of loop - LD HL,2 ; Offset for "FOR" block - ADD HL,SP ; Point to it -FORSLP: CALL LOKFOR ; Look for existing "FOR" block - POP DE ; Get code string address - JP NZ,FORFND ; No nesting found - ADD HL,BC ; Move into "FOR" block - PUSH DE ; Save code string address - DEC HL - LD D,(HL) ; Get MSB of loop statement - DEC HL - LD E,(HL) ; Get LSB of loop statement - INC HL - INC HL - PUSH HL ; Save block address - LD HL,(LOOPST) ; Get address of loop statement - CALL CPDEHL ; Compare the FOR loops - POP HL ; Restore block address - JP NZ,FORSLP ; Different FORs - Find another - POP DE ; Restore code string address - LD SP,HL ; Remove all nested loops - -FORFND: EX DE,HL ; Code string address to HL - LD C,8 - CALL CHKSTK ; Check for 8 levels of stack - PUSH HL ; Save code string address - LD HL,(LOOPST) ; Get first statement of loop - EX (SP),HL ; Save and restore code string - PUSH HL ; Re-save code string address - LD HL,(LINEAT) ; Get current line number - EX (SP),HL ; Save and restore code string - CALL TSTNUM ; Make sure it's a number - CALL CHKSYN ; Make sure "TO" is next - .BYTE ZTO ; "TO" token - CALL GETNUM ; Get "TO" expression value - PUSH HL ; Save code string address - CALL BCDEFP ; Move "TO" value to BCDE - POP HL ; Restore code string address - PUSH BC ; Save "TO" value in block - PUSH DE - LD BC,8100H ; BCDE - 1 (default STEP) - LD D,C ; C=0 - LD E,D ; D=0 - LD A,(HL) ; Get next byte in code string - CP ZSTEP ; See if "STEP" is stated - LD A,1 ; Sign of step = 1 - JP NZ,SAVSTP ; No STEP given - Default to 1 - CALL GETCHR ; Jump over "STEP" token - CALL GETNUM ; Get step value - PUSH HL ; Save code string address - CALL BCDEFP ; Move STEP to BCDE - CALL TSTSGN ; Test sign of FPREG - POP HL ; Restore code string address -SAVSTP: PUSH BC ; Save the STEP value in block - PUSH DE - PUSH AF ; Save sign of STEP - INC SP ; Don't save flags - PUSH HL ; Save code string address - LD HL,(BRKLIN) ; Get address of index variable - EX (SP),HL ; Save and restore code string -PUTFID: LD B,ZFOR ; "FOR" block marker - PUSH BC ; Save it - INC SP ; Don't save C - -RUNCNT: CALL TSTBRK ; Execution driver - Test break - LD (BRKLIN),HL ; Save code address for break - LD A,(HL) ; Get next byte in code string - CP ':' ; Multi statement line? - JP Z,EXCUTE ; Yes - Execute it - OR A ; End of line? - JP NZ,SNERR ; No - Syntax error - INC HL ; Point to address of next line - LD A,(HL) ; Get LSB of line pointer - INC HL - OR (HL) ; Is it zero (End of prog)? - JP Z,ENDPRG ; Yes - Terminate execution - INC HL ; Point to line number - LD E,(HL) ; Get LSB of line number - INC HL - LD D,(HL) ; Get MSB of line number - EX DE,HL ; Line number to HL - LD (LINEAT),HL ; Save as current line number - EX DE,HL ; Line number back to DE -EXCUTE: CALL GETCHR ; Get key word - LD DE,RUNCNT ; Where to RETurn to - PUSH DE ; Save for RETurn -IFJMP: RET Z ; Go to RUNCNT if end of STMT -ONJMP: SUB ZEND ; Is it a token? - JP C,LET ; No - try to assign it - CP ZNEW+1-ZEND ; END to NEW ? - JP NC,SNERR ; Not a key word - ?SN Error - RLCA ; Double it - LD C,A ; BC = Offset into table - LD B,0 - EX DE,HL ; Save code string address - LD HL,WORDTB ; Keyword address table - ADD HL,BC ; Point to routine address - LD C,(HL) ; Get LSB of routine address - INC HL - LD B,(HL) ; Get MSB of routine address - PUSH BC ; Save routine address - EX DE,HL ; Restore code string address - -GETCHR: INC HL ; Point to next character - LD A,(HL) ; Get next code string byte - CP ':' ; Z if ':' - RET NC ; NC if > "9" - CP ' ' - JP Z,GETCHR ; Skip over spaces - CP '0' - CCF ; NC if < '0' - INC A ; Test for zero - Leave carry - DEC A ; Z if Null - RET - -RESTOR: EX DE,HL ; Save code string address - LD HL,(BASTXT) ; Point to start of program - JP Z,RESTNL ; Just RESTORE - reset pointer - EX DE,HL ; Restore code string address - CALL ATOH ; Get line number to DE - PUSH HL ; Save code string address - CALL SRCHLN ; Search for line number in DE - LD H,B ; HL = Address of line - LD L,C - POP DE ; Restore code string address - JP NC,ULERR ; ?UL Error if not found -RESTNL: DEC HL ; Byte before DATA statement -UPDATA: LD (NXTDAT),HL ; Update DATA pointer - EX DE,HL ; Restore code string address - RET - - -TSTBRK: - ; SAVE INCOMING REGISTERS (AF IS OUTPUT) - PUSH BC - PUSH DE - PUSH HL - ; GET CONSOLE INPUT STATUS VIA HBIOS - LD C,CIODEV_CONSOLE; CONSOLE UNIT TO C - LD B,BF_CIOIST ; HBIOS FUNC: INPUT STATUS - RST 08 ; HBIOS RETURNS STATUS IN A - ; RESTORE REGISTERS (AF IS OUTPUT) - POP HL - POP DE - POP BC - RET Z ; No key, go back - PUSH BC - PUSH DE - PUSH HL - ; INPUT CHARACTER FROM CONSOLE VIA HBIOS - LD C,CIODEV_CONSOLE; CONSOLE UNIT TO C - LD B,BF_CIOIN ; HBIOS FUNC: INPUT CHAR - RST 08 ; HBIOS READS CHARACTDR - LD A,E ; MOVE CHARACTER TO A FOR RETURN +TTYLIN: LD HL,BUFFER ; Get a line by character + LD B,1 ; Set buffer as empty + XOR A + LD (NULFLG),A ; Clear null flag +MORINP: CALL CLOTST ; Get character and test ^O + LD C,A ; Save character in C + CP DEL ; Delete character? + JP Z,DODEL ; Yes - Process it + LD A,(NULFLG) ; Get null flag + OR A ; Test null flag status + JP Z,PROCES ; Reset - Process character + LD A,0 ; Set a null + CALL OUTC ; Output null + XOR A ; Clear A + LD (NULFLG),A ; Reset null flag +PROCES: LD A,C ; Get character + CP CTRLG ; Bell? + JP Z,PUTCTL ; Yes - Save it + CP CTRLC ; Is it control "C"? + CALL Z,PRNTCRLF ; Yes - Output CRLF + SCF ; Flag break + RET Z ; Return if control "C" + CP CR ; Is it enter? + JP Z,ENDINP ; Yes - Terminate input + CP CTRLU ; Is it control "U"? + JP Z,KILIN ; Yes - Get another line + CP '@' ; Is it "kill line"? + JP Z,OTKLN ; Yes - Kill line + CP '_' ; Is it delete? + JP Z,DELCHR ; Yes - Delete character + CP BKSP ; Is it backspace? + JP Z,DELCHR ; Yes - Delete character + CP CTRLR ; Is it control "R"? + JP NZ,PUTBUF ; No - Put in buffer + PUSH BC ; Save buffer length + PUSH DE ; Save DE + PUSH HL ; Save buffer address + LD (HL),0 ; Mark end of buffer + CALL OUTNCR ; Output and do CRLF + LD HL,BUFFER ; Point to buffer start + CALL PRS ; Output buffer + POP HL ; Restore buffer address + POP DE ; Restore DE + POP BC ; Restore buffer length + JP MORINP ; Get another character + +PUTBUF: CP ' ' ; Is it a control code? + JP C,MORINP ; Yes - Ignore +PUTCTL: LD A,B ; Get number of bytes in buffer + CP 72+1 ; Test for line overflow + LD A,CTRLG ; Set a bell + JP NC,OUTNBS ; Ring bell if buffer full + LD A,C ; Get character + LD (HL),C ; Save in buffer + LD (LSTBIN),A ; Save last input byte + INC HL ; Move up buffer + INC B ; Increment length +OUTIT: CALL OUTC ; Output the character entered + JP MORINP ; Get another character + +OUTNBS: CALL OUTC ; Output bell and back over it + LD A,BKSP ; Set back space + JP OUTIT ; Output it and get more + +CPDEHL: LD A,H ; Get H + SUB D ; Compare with D + RET NZ ; Different - Exit + LD A,L ; Get L + SUB E ; Compare with E + RET ; Return status + +CHKSYN: LD A,(HL) ; Check syntax of character + EX (SP),HL ; Address of test byte + CP (HL) ; Same as in code string? + INC HL ; Return address + EX (SP),HL ; Put it back + JP Z,GETCHR ; Yes - Get next character + JP SNERR ; Different - ?SN Error + +OUTC: PUSH AF ; Save character + LD A,(CTLOFG) ; Get control "O" flag + OR A ; Is it set? + JP NZ,POPAF ; Yes - don't output + POP AF ; Restore character + PUSH BC ; Save buffer length + PUSH AF ; Save character + CP ' ' ; Is it a control code? + JP C,DINPOS ; Yes - Don't INC POS(X) + LD A,(LWIDTH) ; Get line width + LD B,A ; To B + LD A,(CURPOS) ; Get cursor position + INC B ; Width 255? + JP Z,INCLEN ; Yes - No width limit + DEC B ; Restore width + CP B ; At end of line? + CALL Z,PRNTCRLF ; Yes - output CRLF +INCLEN: INC A ; Move on one character + LD (CURPOS),A ; Save new position +DINPOS: POP AF ; Restore character + POP BC ; Restore buffer length + CALL MONOUT ; Send it + RET + +CLOTST: CALL GETINP ; Get input character + AND 01111111B ; Strip bit 7 + CP CTRLO ; Is it control "O"? + RET NZ ; No don't flip flag + LD A,(CTLOFG) ; Get flag + CPL ; Flip it + LD (CTLOFG),A ; Put it back + XOR A ; Null character + RET + +LIST: CALL ATOH ; ASCII number to DE + RET NZ ; Return if anything extra + POP BC ; Rubbish - Not needed + CALL SRCHLN ; Search for line number in DE + PUSH BC ; Save address of line + CALL SETLIN ; Set up lines counter +LISTLP: POP HL ; Restore address of line + LD C,(HL) ; Get LSB of next line + INC HL + LD B,(HL) ; Get MSB of next line + INC HL + LD A,B ; BC = 0 (End of program)? + OR C + JP Z,PRNTOK ; Yes - Go to command mode + CALL COUNT ; Count lines + CALL TSTBRK ; Test for break key + PUSH BC ; Save address of next line + CALL PRNTCRLF ; Output CRLF + LD E,(HL) ; Get LSB of line number + INC HL + LD D,(HL) ; Get MSB of line number + INC HL + PUSH HL ; Save address of line start + EX DE,HL ; Line number to HL + CALL PRNTHL ; Output line number in decimal + LD A,' ' ; Space after line number + POP HL ; Restore start of line address +LSTLP2: CALL OUTC ; Output character in A +LSTLP3: LD A,(HL) ; Get next byte in line + OR A ; End of line? + INC HL ; To next byte in line + JP Z,LISTLP ; Yes - get next line + JP P,LSTLP2 ; No token - output it + SUB ZEND-1 ; Find and output word + LD C,A ; Token offset+1 to C + LD DE,WORDS ; Reserved word list +FNDTOK: LD A,(DE) ; Get character in list + INC DE ; Move on to next + OR A ; Is it start of word? + JP P,FNDTOK ; No - Keep looking for word + DEC C ; Count words + JP NZ,FNDTOK ; Not there - keep looking +OUTWRD: AND 01111111B ; Strip bit 7 + CALL OUTC ; Output first character + LD A,(DE) ; Get next character + INC DE ; Move on to next + OR A ; Is it end of word? + JP P,OUTWRD ; No - output the rest + JP LSTLP3 ; Next byte in line + +SETLIN: PUSH HL ; Set up LINES counter + LD HL,(LINESN) ; Get LINES number + LD (LINESC),HL ; Save in LINES counter + POP HL + RET + +COUNT: PUSH HL ; Save code string address + PUSH DE + LD HL,(LINESC) ; Get LINES counter + LD DE,-1 + ADC HL,DE ; Decrement + LD (LINESC),HL ; Put it back + POP DE + POP HL ; Restore code string address + RET P ; Return if more lines to go + PUSH HL ; Save code string address + LD HL,(LINESN) ; Get LINES number + LD (LINESC),HL ; Reset LINES counter + CALL GETINP ; Get input character + CP CTRLC ; Is it control "C"? + JP Z,RSLNBK ; Yes - Reset LINES and break + POP HL ; Restore code string address + JP COUNT ; Keep on counting + +RSLNBK: LD HL,(LINESN) ; Get LINES number + LD (LINESC),HL ; Reset LINES counter + JP BRKRET ; Go and output "Break" + +FOR: LD A,64H ; Flag "FOR" assignment + LD (FORFLG),A ; Save "FOR" flag + CALL LET ; Set up initial index + POP BC ; Drop RETurn address + PUSH HL ; Save code string address + CALL DATA ; Get next statement address + LD (LOOPST),HL ; Save it for start of loop + LD HL,2 ; Offset for "FOR" block + ADD HL,SP ; Point to it +FORSLP: CALL LOKFOR ; Look for existing "FOR" block + POP DE ; Get code string address + JP NZ,FORFND ; No nesting found + ADD HL,BC ; Move into "FOR" block + PUSH DE ; Save code string address + DEC HL + LD D,(HL) ; Get MSB of loop statement + DEC HL + LD E,(HL) ; Get LSB of loop statement + INC HL + INC HL + PUSH HL ; Save block address + LD HL,(LOOPST) ; Get address of loop statement + CALL CPDEHL ; Compare the FOR loops + POP HL ; Restore block address + JP NZ,FORSLP ; Different FORs - Find another + POP DE ; Restore code string address + LD SP,HL ; Remove all nested loops + +FORFND: EX DE,HL ; Code string address to HL + LD C,8 + CALL CHKSTK ; Check for 8 levels of stack + PUSH HL ; Save code string address + LD HL,(LOOPST) ; Get first statement of loop + EX (SP),HL ; Save and restore code string + PUSH HL ; Re-save code string address + LD HL,(LINEAT) ; Get current line number + EX (SP),HL ; Save and restore code string + CALL TSTNUM ; Make sure it's a number + CALL CHKSYN ; Make sure "TO" is next + .BYTE ZTO ; "TO" token + CALL GETNUM ; Get "TO" expression value + PUSH HL ; Save code string address + CALL BCDEFP ; Move "TO" value to BCDE + POP HL ; Restore code string address + PUSH BC ; Save "TO" value in block + PUSH DE + LD BC,8100H ; BCDE - 1 (default STEP) + LD D,C ; C=0 + LD E,D ; D=0 + LD A,(HL) ; Get next byte in code string + CP ZSTEP ; See if "STEP" is stated + LD A,1 ; Sign of step = 1 + JP NZ,SAVSTP ; No STEP given - Default to 1 + CALL GETCHR ; Jump over "STEP" token + CALL GETNUM ; Get step value + PUSH HL ; Save code string address + CALL BCDEFP ; Move STEP to BCDE + CALL TSTSGN ; Test sign of FPREG + POP HL ; Restore code string address +SAVSTP: PUSH BC ; Save the STEP value in block + PUSH DE + PUSH AF ; Save sign of STEP + INC SP ; Don't save flags + PUSH HL ; Save code string address + LD HL,(BRKLIN) ; Get address of index variable + EX (SP),HL ; Save and restore code string +PUTFID: LD B,ZFOR ; "FOR" block marker + PUSH BC ; Save it + INC SP ; Don't save C + +RUNCNT: CALL TSTBRK ; Execution driver - Test break + LD (BRKLIN),HL ; Save code address for break + LD A,(HL) ; Get next byte in code string + CP ':' ; Multi statement line? + JP Z,EXCUTE ; Yes - Execute it + OR A ; End of line? + JP NZ,SNERR ; No - Syntax error + INC HL ; Point to address of next line + LD A,(HL) ; Get LSB of line pointer + INC HL + OR (HL) ; Is it zero (End of prog)? + JP Z,ENDPRG ; Yes - Terminate execution + INC HL ; Point to line number + LD E,(HL) ; Get LSB of line number + INC HL + LD D,(HL) ; Get MSB of line number + EX DE,HL ; Line number to HL + LD (LINEAT),HL ; Save as current line number + EX DE,HL ; Line number back to DE +EXCUTE: CALL GETCHR ; Get key word + LD DE,RUNCNT ; Where to RETurn to + PUSH DE ; Save for RETurn +IFJMP: RET Z ; Go to RUNCNT if end of STMT +ONJMP: SUB ZEND ; Is it a token? + JP C,LET ; No - try to assign it + CP ZNEW+1-ZEND ; END to NEW ? + JP NC,SNERR ; Not a key word - ?SN Error + RLCA ; Double it + LD C,A ; BC = Offset into table + LD B,0 + EX DE,HL ; Save code string address + LD HL,WORDTB ; Keyword address table + ADD HL,BC ; Point to routine address + LD C,(HL) ; Get LSB of routine address + INC HL + LD B,(HL) ; Get MSB of routine address + PUSH BC ; Save routine address + EX DE,HL ; Restore code string address + +GETCHR: INC HL ; Point to next character + LD A,(HL) ; Get next code string byte + CP ':' ; Z if ':' + RET NC ; NC if > "9" + CP ' ' + JP Z,GETCHR ; Skip over spaces + CP '0' + CCF ; NC if < '0' + INC A ; Test for zero - Leave carry + DEC A ; Z if Null + RET + +RESTOR: EX DE,HL ; Save code string address + LD HL,(BASTXT) ; Point to start of program + JP Z,RESTNL ; Just RESTORE - reset pointer + EX DE,HL ; Restore code string address + CALL ATOH ; Get line number to DE + PUSH HL ; Save code string address + CALL SRCHLN ; Search for line number in DE + LD H,B ; HL = Address of line + LD L,C + POP DE ; Restore code string address + JP NC,ULERR ; ?UL Error if not found +RESTNL: DEC HL ; Byte before DATA statement +UPDATA: LD (NXTDAT),HL ; Update DATA pointer + EX DE,HL ; Restore code string address + RET + +; GET CONSOLE INPUT STATUS VIA HBIOS + +TSTBRK: + PUSH BC ; SAVE INCOMING REGISTERS (AF IS OUTPUT) + PUSH DE + PUSH HL + + LD C,CIO_CONSOLE ; CONSOLE UNIT TO C + LD B,BF_CIOIST ; HBIOS FUNC: INPUT STATUS + RST 08 ; HBIOS RETURNS STATUS IN A + + POP HL ; RESTORE REGISTERS (AF IS OUTPUT) + POP DE + POP BC + RET Z ; No key, go back + PUSH BC + PUSH DE + PUSH HL + +; INPUT CHARACTER FROM CONSOLE VIA HBIOS + + LD C,CIO_CONSOLE ; CONSOLE UNIT TO C + LD B,BF_CIOIN ; HBIOS FUNC: INPUT CHAR + RST 08 ; HBIOS READS CHARACTDR + LD A,E ; MOVE CHARACTER TO A FOR RETURN ; - ; RESTORE REGISTERS (AF IS OUTPUT) - POP HL - POP DE - POP BC - - CP ESC ; Escape key? - JR Z,BRK ; Yes, break - CP CTRLC ; - JR Z,BRK ; Yes, break - CP CTRLS ; Stop scrolling? - RET NZ ; Other key, ignore -STALL: ; Wait for key - ; SAVE INCOMING REGISTERS (AF IS OUTPUT) - PUSH BC - PUSH DE - PUSH HL + + POP HL ; RESTORE REGISTERS (AF IS OUTPUT) + POP DE + POP BC + + CP ESC ; Escape key? + JR Z,BRK ; Yes, break + CP CTRLC ; + JR Z,BRK ; Yes, break + CP CTRLS ; Stop scrolling? + RET NZ ; Other key, ignore +STALL: ; Wait for key + + PUSH BC ; SAVE INCOMING REGISTERS (AF IS OUTPUT) + PUSH DE + PUSH HL +; +; INPUT CHARACTER FROM CONSOLE VIA HBIOS ; - ; INPUT CHARACTER FROM CONSOLE VIA HBIOS - LD C,CIODEV_CONSOLE ; CONSOLE UNIT TO C - LD B,BF_CIOIN ; HBIOS FUNC: INPUT CHAR - RST 08 ; HBIOS READS CHARACTDR - LD A,E ; MOVE CHARACTER TO A FOR RETURN + LD C,CIO_CONSOLE ; CONSOLE UNIT TO C + LD B,BF_CIOIN ; HBIOS FUNC: INPUT CHAR + RST 08 ; HBIOS READS CHARACTDR + LD A,E ; MOVE CHARACTER TO A FOR RETURN ; - ; RESTORE REGISTERS (AF IS OUTPUT) - POP HL - POP DE - POP BC - CP CTRLQ ; Resume scrolling? - RET Z ; Release the chokehold - CP CTRLC ; Second break? - JR Z,STOP ; Break during hold exits prog - JR STALL ; Loop until or - -BRK LD A,$FF ; Set BRKFLG - LD (BRKFLG),A ; Store it - -STOP: RET NZ ; Exit if anything else - .BYTE 0F6H ; Flag "STOP" -PEND: RET NZ ; Exit if anything else - LD (BRKLIN),HL ; Save point of break - .BYTE 21H ; Skip "OR 11111111B" -INPBRK: OR 11111111B ; Flag "Break" wanted - POP BC ; Return not needed and more -ENDPRG: LD HL,(LINEAT) ; Get current line number - PUSH AF ; Save STOP / END status - LD A,L ; Is it direct break? - AND H - INC A ; Line is -1 if direct break - JP Z,NOLIN ; Yes - No line number - LD (ERRLIN),HL ; Save line of break - LD HL,(BRKLIN) ; Get point of break - LD (CONTAD),HL ; Save point to CONTinue -NOLIN: XOR A - LD (CTLOFG),A ; Enable output - CALL STTLIN ; Start a new line - POP AF ; Restore STOP / END status - LD HL,BRKMSG ; "Break" message - JP NZ,ERRIN ; "in line" wanted? - JP PRNTOK ; Go to command mode - -CONT: LD HL,(CONTAD) ; Get CONTinue address - LD A,H ; Is it zero? - OR L - LD E,CN ; ?CN Error - JP Z,ERROR ; Yes - output "?CN Error" - EX DE,HL ; Save code string address - LD HL,(ERRLIN) ; Get line of last break - LD (LINEAT),HL ; Set up current line number - EX DE,HL ; Restore code string address - RET ; CONTinue where left off - -NULL: CALL GETINT ; Get integer 0-255 - RET NZ ; Return if bad value - LD (NULLS),A ; Set nulls number - RET - - -ACCSUM: PUSH HL ; Save address in array - LD HL,(CHKSUM) ; Get check sum - LD B,0 ; BC - Value of byte - LD C,A - ADD HL,BC ; Add byte to check sum - LD (CHKSUM),HL ; Re-save check sum - POP HL ; Restore address in array - RET - -CHKLTR: LD A,(HL) ; Get byte - CP 'A' ; < 'a' ? - RET C ; Carry set if not letter - CP 'Z'+1 ; > 'z' ? - CCF - RET ; Carry set if not letter - -FPSINT: CALL GETCHR ; Get next character -POSINT: CALL GETNUM ; Get integer 0 to 32767 -DEPINT: CALL TSTSGN ; Test sign of FPREG - JP M,FCERR ; Negative - ?FC Error -DEINT: LD A,(FPEXP) ; Get integer value to DE - CP 80H+16 ; Exponent in range (16 bits)? - JP C,FPINT ; Yes - convert it - LD BC,9080H ; BCDE = -32768 - LD DE,0000 - PUSH HL ; Save code string address - CALL CMPNUM ; Compare FPREG with BCDE - POP HL ; Restore code string address - LD D,C ; MSB to D - RET Z ; Return if in range -FCERR: LD E,FC ; ?FC Error - JP ERROR ; Output error- - -ATOH: DEC HL ; ASCII number to DE binary -GETLN: LD DE,0 ; Get number to DE -GTLNLP: CALL GETCHR ; Get next character - RET NC ; Exit if not a digit - PUSH HL ; Save code string address - PUSH AF ; Save digit - LD HL,65529/10 ; Largest number 65529 - CALL CPDEHL ; Number in range? - JP C,SNERR ; No - ?SN Error - LD H,D ; HL = Number - LD L,E - ADD HL,DE ; Times 2 - ADD HL,HL ; Times 4 - ADD HL,DE ; Times 5 - ADD HL,HL ; Times 10 - POP AF ; Restore digit - SUB '0' ; Make it 0 to 9 - LD E,A ; DE = Value of digit - LD D,0 - ADD HL,DE ; Add to number - EX DE,HL ; Number to DE - POP HL ; Restore code string address - JP GTLNLP ; Go to next character - -CLEAR: JP Z,INTVAR ; Just "CLEAR" Keep parameters - CALL POSINT ; Get integer 0 to 32767 to DE - DEC HL ; Cancel increment - CALL GETCHR ; Get next character - PUSH HL ; Save code string address - LD HL,(LSTRAM) ; Get end of RAM - JP Z,STORED ; No value given - Use stored - POP HL ; Restore code string address - CALL CHKSYN ; Check for comma - .BYTE ',' - PUSH DE ; Save number - CALL POSINT ; Get integer 0 to 32767 - DEC HL ; Cancel increment - CALL GETCHR ; Get next character - JP NZ,SNERR ; ?SN Error if more on line - EX (SP),HL ; Save code string address - EX DE,HL ; Number to DE -STORED: LD A,L ; Get LSB of new RAM top - SUB E ; Subtract LSB of string space - LD E,A ; Save LSB - LD A,H ; Get MSB of new RAM top - SBC A,D ; Subtract MSB of string space - LD D,A ; Save MSB - JP C,OMERR ; ?OM Error if not enough mem - PUSH HL ; Save RAM top - LD HL,(PROGND) ; Get program end - LD BC,40 ; 40 Bytes minimum working RAM - ADD HL,BC ; Get lowest address - CALL CPDEHL ; Enough memory? - JP NC,OMERR ; No - ?OM Error - EX DE,HL ; RAM top to HL - LD (STRSPC),HL ; Set new string space - POP HL ; End of memory to use - LD (LSTRAM),HL ; Set new top of RAM - POP HL ; Restore code string address - JP INTVAR ; Initialise variables - -RUN: JP Z,RUNFST ; RUN from start if just RUN - CALL INTVAR ; Initialise variables - LD BC,RUNCNT ; Execution driver loop - JP RUNLIN ; RUN from line number - -GOSUB: LD C,3 ; 3 Levels of stack needed - CALL CHKSTK ; Check for 3 levels of stack - POP BC ; Get return address - PUSH HL ; Save code string for RETURN - PUSH HL ; And for GOSUB routine - LD HL,(LINEAT) ; Get current line - EX (SP),HL ; Into stack - Code string out - LD A,ZGOSUB ; "GOSUB" token - PUSH AF ; Save token - INC SP ; Don't save flags - -RUNLIN: PUSH BC ; Save return address -GOTO: CALL ATOH ; ASCII number to DE binary - CALL REM ; Get end of line - PUSH HL ; Save end of line - LD HL,(LINEAT) ; Get current line - CALL CPDEHL ; Line after current? - POP HL ; Restore end of line - INC HL ; Start of next line - CALL C,SRCHLP ; Line is after current line - CALL NC,SRCHLN ; Line is before current line - LD H,B ; Set up code string address - LD L,C - DEC HL ; Incremented after - RET C ; Line found -ULERR: LD E,UL ; ?UL Error - JP ERROR ; Output error message - -RETURN: RET NZ ; Return if not just RETURN - LD D,-1 ; Flag "GOSUB" search - CALL BAKSTK ; Look "GOSUB" block - LD SP,HL ; Kill all FORs in subroutine - CP ZGOSUB ; Test for "GOSUB" token - LD E,RG ; ?RG Error - JP NZ,ERROR ; Error if no "GOSUB" found - POP HL ; Get RETURN line number - LD (LINEAT),HL ; Save as current - INC HL ; Was it from direct statement? - LD A,H - OR L ; Return to line - JP NZ,RETLIN ; No - Return to line - LD A,(LSTBIN) ; Any INPUT in subroutine? - OR A ; If so buffer is corrupted - JP NZ,POPNOK ; Yes - Go to command mode -RETLIN: LD HL,RUNCNT ; Execution driver loop - EX (SP),HL ; Into stack - Code string out - .BYTE 3EH ; Skip "POP HL" -NXTDTA: POP HL ; Restore code string address - -DATA: .BYTE 01H,3AH ; ':' End of statement -REM: LD C,0 ; 00 End of statement - LD B,0 -NXTSTL: LD A,C ; Statement and byte - LD C,B - LD B,A ; Statement end byte -NXTSTT: LD A,(HL) ; Get byte - OR A ; End of line? - RET Z ; Yes - Exit - CP B ; End of statement? - RET Z ; Yes - Exit - INC HL ; Next byte - CP '"' ; Literal string? - JP Z,NXTSTL ; Yes - Look for another '"' - JP NXTSTT ; Keep looking - -LET: CALL GETVAR ; Get variable name - CALL CHKSYN ; Make sure "=" follows - .BYTE ZEQUAL ; "=" token - PUSH DE ; Save address of variable - LD A,(TYPE) ; Get data type - PUSH AF ; Save type - CALL EVAL ; Evaluate expression - POP AF ; Restore type - EX (SP),HL ; Save code - Get var addr - LD (BRKLIN),HL ; Save address of variable - RRA ; Adjust type - CALL CHKTYP ; Check types are the same - JP Z,LETNUM ; Numeric - Move value -LETSTR: PUSH HL ; Save address of string var - LD HL,(FPREG) ; Pointer to string entry - PUSH HL ; Save it on stack - INC HL ; Skip over length - INC HL - LD E,(HL) ; LSB of string address - INC HL - LD D,(HL) ; MSB of string address - LD HL,(BASTXT) ; Point to start of program - CALL CPDEHL ; Is string before program? - JP NC,CRESTR ; Yes - Create string entry - LD HL,(STRSPC) ; Point to string space - CALL CPDEHL ; Is string literal in program? - POP DE ; Restore address of string - JP NC,MVSTPT ; Yes - Set up pointer - LD HL,TMPSTR ; Temporary string pool - CALL CPDEHL ; Is string in temporary pool? - JP NC,MVSTPT ; No - Set up pointer - .BYTE 3EH ; Skip "POP DE" -CRESTR: POP DE ; Restore address of string - CALL BAKTMP ; Back to last tmp-str entry - EX DE,HL ; Address of string entry - CALL SAVSTR ; Save string in string area -MVSTPT: CALL BAKTMP ; Back to last tmp-str entry - POP HL ; Get string pointer - CALL DETHL4 ; Move string pointer to var - POP HL ; Restore code string address - RET - -LETNUM: PUSH HL ; Save address of variable - CALL FPTHL ; Move value to variable - POP DE ; Restore address of variable - POP HL ; Restore code string address - RET - -ON: CALL GETINT ; Get integer 0-255 - LD A,(HL) ; Get "GOTO" or "GOSUB" token - LD B,A ; Save in B - CP ZGOSUB ; "GOSUB" token? - JP Z,ONGO ; Yes - Find line number - CALL CHKSYN ; Make sure it's "GOTO" - .BYTE ZGOTO ; "GOTO" token - DEC HL ; Cancel increment -ONGO: LD C,E ; Integer of branch value -ONGOLP: DEC C ; Count branches - LD A,B ; Get "GOTO" or "GOSUB" token - JP Z,ONJMP ; Go to that line if right one - CALL GETLN ; Get line number to DE - CP ',' ; Another line number? - RET NZ ; No - Drop through - JP ONGOLP ; Yes - loop - -IF: CALL EVAL ; Evaluate expression - LD A,(HL) ; Get token - CP ZGOTO ; "GOTO" token? - JP Z,IFGO ; Yes - Get line - CALL CHKSYN ; Make sure it's "THEN" - .BYTE ZTHEN ; "THEN" token - DEC HL ; Cancel increment -IFGO: CALL TSTNUM ; Make sure it's numeric - CALL TSTSGN ; Test state of expression - JP Z,REM ; False - Drop through - CALL GETCHR ; Get next character - JP C,GOTO ; Number - GOTO that line - JP IFJMP ; Otherwise do statement - -MRPRNT: DEC HL ; DEC 'cos GETCHR INCs - CALL GETCHR ; Get next character -PRINT: JP Z,PRNTCRLF ; CRLF if just PRINT -PRNTLP: RET Z ; End of list - Exit - CP ZTAB ; "TAB(" token? - JP Z,DOTAB ; Yes - Do TAB routine - CP ZSPC ; "SPC(" token? - JP Z,DOTAB ; Yes - Do SPC routine - PUSH HL ; Save code string address - CP ',' ; Comma? - JP Z,DOCOM ; Yes - Move to next zone - CP 59 ;";" ; Semi-colon? - JP Z,NEXITM ; Do semi-colon routine - POP BC ; Code string address to BC - CALL EVAL ; Evaluate expression - PUSH HL ; Save code string address - LD A,(TYPE) ; Get variable type - OR A ; Is it a string variable? - JP NZ,PRNTST ; Yes - Output string contents - CALL NUMASC ; Convert number to text - CALL CRTST ; Create temporary string - LD (HL),' ' ; Followed by a space - LD HL,(FPREG) ; Get length of output - INC (HL) ; Plus 1 for the space - LD HL,(FPREG) ; < Not needed > - LD A,(LWIDTH) ; Get width of line - LD B,A ; To B - INC B ; Width 255 (No limit)? - JP Z,PRNTNB ; Yes - Output number string - INC B ; Adjust it - LD A,(CURPOS) ; Get cursor position - ADD A,(HL) ; Add length of string - DEC A ; Adjust it - CP B ; Will output fit on this line? - CALL NC,PRNTCRLF ; No - CRLF first -PRNTNB: CALL PRS1 ; Output string at (HL) - XOR A ; Skip CALL by setting 'z' flag -PRNTST: CALL NZ,PRS1 ; Output string at (HL) - POP HL ; Restore code string address - JP MRPRNT ; See if more to PRINT - -STTLIN: LD A,(CURPOS) ; Make sure on new line - OR A ; Already at start? - RET Z ; Yes - Do nothing - JP PRNTCRLF ; Start a new line - -ENDINP: LD (HL),0 ; Mark end of buffer - LD HL,BUFFER-1 ; Point to buffer -PRNTCRLF: LD A,CR ; Load a CR - CALL OUTC ; Output character - LD A,LF ; Load a LF - CALL OUTC ; Output character -DONULL: XOR A ; Set to position 0 - LD (CURPOS),A ; Store it - LD A,(NULLS) ; Get number of nulls -NULLP: DEC A ; Count them - RET Z ; Return if done - PUSH AF ; Save count - XOR A ; Load a null - CALL OUTC ; Output it - POP AF ; Restore count - JP NULLP ; Keep counting - -DOCOM: LD A,(COMMAN) ; Get comma width - LD B,A ; Save in B - LD A,(CURPOS) ; Get current position - CP B ; Within the limit? - CALL NC,PRNTCRLF ; No - output CRLF - JP NC,NEXITM ; Get next item -ZONELP: SUB 14 ; Next zone of 14 characters - JP NC,ZONELP ; Repeat if more zones - CPL ; Number of spaces to output - JP ASPCS ; Output them - -DOTAB: PUSH AF ; Save token - CALL FNDNUM ; Evaluate expression - CALL CHKSYN ; Make sure ")" follows - .BYTE ")" - DEC HL ; Back space on to ")" - POP AF ; Restore token - SUB ZSPC ; Was it "SPC(" ? - PUSH HL ; Save code string address - JP Z,DOSPC ; Yes - Do 'E' spaces - LD A,(CURPOS) ; Get current position -DOSPC: CPL ; Number of spaces to print to - ADD A,E ; Total number to print - JP NC,NEXITM ; TAB < Current POS(X) -ASPCS: INC A ; Output A spaces - LD B,A ; Save number to print - LD A,' ' ; Space -SPCLP: CALL OUTC ; Output character in A - DEC B ; Count them - JP NZ,SPCLP ; Repeat if more -NEXITM: POP HL ; Restore code string address - CALL GETCHR ; Get next character - JP PRNTLP ; More to print + POP HL ; RESTORE REGISTERS (AF IS OUTPUT) + POP DE + POP BC + CP CTRLQ ; Resume scrolling? + RET Z ; Release the chokehold + CP CTRLC ; Second break? + JR Z,STOP ; Break during hold exits prog + JR STALL ; Loop until or + +BRK LD A,$FF ; Set BRKFLG + LD (BRKFLG),A ; Store it + +STOP: RET NZ ; Exit if anything else + .BYTE 0F6H ; Flag "STOP" +PEND: RET NZ ; Exit if anything else + LD (BRKLIN),HL ; Save point of break + .BYTE 21H ; Skip "OR 11111111B" +INPBRK: OR 11111111B ; Flag "Break" wanted + POP BC ; Return not needed and more +ENDPRG: LD HL,(LINEAT) ; Get current line number + PUSH AF ; Save STOP / END status + LD A,L ; Is it direct break? + AND H + INC A ; Line is -1 if direct break + JP Z,NOLIN ; Yes - No line number + LD (ERRLIN),HL ; Save line of break + LD HL,(BRKLIN) ; Get point of break + LD (CONTAD),HL ; Save point to CONTinue +NOLIN: XOR A + LD (CTLOFG),A ; Enable output + CALL STTLIN ; Start a new line + POP AF ; Restore STOP / END status + LD HL,BRKMSG ; "Break" message + JP NZ,ERRIN ; "in line" wanted? + JP PRNTOK ; Go to command mode + +CONT: LD HL,(CONTAD) ; Get CONTinue address + LD A,H ; Is it zero? + OR L + LD E,CN ; ?CN Error + JP Z,ERROR ; Yes - output "?CN Error" + EX DE,HL ; Save code string address + LD HL,(ERRLIN) ; Get line of last break + LD (LINEAT),HL ; Set up current line number + EX DE,HL ; Restore code string address + RET ; CONTinue where left off + +NULL: CALL GETINT ; Get integer 0-255 + RET NZ ; Return if bad value + LD (NULLS),A ; Set nulls number + RET + +ACCSUM: PUSH HL ; Save address in array + LD HL,(CHKSUM) ; Get check sum + LD B,0 ; BC - Value of byte + LD C,A + ADD HL,BC ; Add byte to check sum + LD (CHKSUM),HL ; Re-save check sum + POP HL ; Restore address in array + RET + +CHKLTR: LD A,(HL) ; Get byte + CP 'A' ; < 'a' ? + RET C ; Carry set if not letter + CP 'Z'+1 ; > 'z' ? + CCF + RET ; Carry set if not letter + +FPSINT: CALL GETCHR ; Get next character +POSINT: CALL GETNUM ; Get integer 0 to 32767 +DEPINT: CALL TSTSGN ; Test sign of FPREG + JP M,FCERR ; Negative - ?FC Error +DEINT: LD A,(FPEXP) ; Get integer value to DE + CP 80H+16 ; Exponent in range (16 bits)? + JP C,FPINT ; Yes - convert it + LD BC,9080H ; BCDE = -32768 + LD DE,0000 + PUSH HL ; Save code string address + CALL CMPNUM ; Compare FPREG with BCDE + POP HL ; Restore code string address + LD D,C ; MSB to D + RET Z ; Return if in range +FCERR: LD E,FC ; ?FC Error + JP ERROR ; Output error- + +ATOH: DEC HL ; ASCII number to DE binary +GETLN: LD DE,0 ; Get number to DE +GTLNLP: CALL GETCHR ; Get next character + RET NC ; Exit if not a digit + PUSH HL ; Save code string address + PUSH AF ; Save digit + LD HL,65529/10 ; Largest number 65529 + CALL CPDEHL ; Number in range? + JP C,SNERR ; No - ?SN Error + LD H,D ; HL = Number + LD L,E + ADD HL,DE ; Times 2 + ADD HL,HL ; Times 4 + ADD HL,DE ; Times 5 + ADD HL,HL ; Times 10 + POP AF ; Restore digit + SUB '0' ; Make it 0 to 9 + LD E,A ; DE = Value of digit + LD D,0 + ADD HL,DE ; Add to number + EX DE,HL ; Number to DE + POP HL ; Restore code string address + JP GTLNLP ; Go to next character + +CLEAR: JP Z,INTVAR ; Just "CLEAR" Keep parameters + CALL POSINT ; Get integer 0 to 32767 to DE + DEC HL ; Cancel increment + CALL GETCHR ; Get next character + PUSH HL ; Save code string address + LD HL,(LSTRAM) ; Get end of RAM + JP Z,STORED ; No value given - Use stored + POP HL ; Restore code string address + CALL CHKSYN ; Check for comma + .BYTE ',' + PUSH DE ; Save number + CALL POSINT ; Get integer 0 to 32767 + DEC HL ; Cancel increment + CALL GETCHR ; Get next character + JP NZ,SNERR ; ?SN Error if more on line + EX (SP),HL ; Save code string address + EX DE,HL ; Number to DE +STORED: LD A,L ; Get LSB of new RAM top + SUB E ; Subtract LSB of string space + LD E,A ; Save LSB + LD A,H ; Get MSB of new RAM top + SBC A,D ; Subtract MSB of string space + LD D,A ; Save MSB + JP C,OMERR ; ?OM Error if not enough mem + PUSH HL ; Save RAM top + LD HL,(PROGND) ; Get program end + LD BC,40 ; 40 Bytes minimum working RAM + ADD HL,BC ; Get lowest address + CALL CPDEHL ; Enough memory? + JP NC,OMERR ; No - ?OM Error + EX DE,HL ; RAM top to HL + LD (STRSPC),HL ; Set new string space + POP HL ; End of memory to use + LD (LSTRAM),HL ; Set new top of RAM + POP HL ; Restore code string address + JP INTVAR ; Initialise variables + +RUN: JP Z,RUNFST ; RUN from start if just RUN + CALL INTVAR ; Initialise variables + LD BC,RUNCNT ; Execution driver loop + JP RUNLIN ; RUN from line number + +GOSUB: LD C,3 ; 3 Levels of stack needed + CALL CHKSTK ; Check for 3 levels of stack + POP BC ; Get return address + PUSH HL ; Save code string for RETURN + PUSH HL ; And for GOSUB routine + LD HL,(LINEAT) ; Get current line + EX (SP),HL ; Into stack - Code string out + LD A,ZGOSUB ; "GOSUB" token + PUSH AF ; Save token + INC SP ; Don't save flags + +RUNLIN: PUSH BC ; Save return address +GOTO: CALL ATOH ; ASCII number to DE binary + CALL REM ; Get end of line + PUSH HL ; Save end of line + LD HL,(LINEAT) ; Get current line + CALL CPDEHL ; Line after current? + POP HL ; Restore end of line + INC HL ; Start of next line + CALL C,SRCHLP ; Line is after current line + CALL NC,SRCHLN ; Line is before current line + LD H,B ; Set up code string address + LD L,C + DEC HL ; Incremented after + RET C ; Line found +ULERR: LD E,UL ; ?UL Error + JP ERROR ; Output error message + +RETURN: RET NZ ; Return if not just RETURN + LD D,-1 ; Flag "GOSUB" search + CALL BAKSTK ; Look "GOSUB" block + LD SP,HL ; Kill all FORs in subroutine + CP ZGOSUB ; Test for "GOSUB" token + LD E,RG ; ?RG Error + JP NZ,ERROR ; Error if no "GOSUB" found + POP HL ; Get RETURN line number + LD (LINEAT),HL ; Save as current + INC HL ; Was it from direct statement? + LD A,H + OR L ; Return to line + JP NZ,RETLIN ; No - Return to line + LD A,(LSTBIN) ; Any INPUT in subroutine? + OR A ; If so buffer is corrupted + JP NZ,POPNOK ; Yes - Go to command mode +RETLIN: LD HL,RUNCNT ; Execution driver loop + EX (SP),HL ; Into stack - Code string out + .BYTE 3EH ; Skip "POP HL" +NXTDTA: POP HL ; Restore code string address + +DATA: .BYTE 01H,3AH ; ':' End of statement +REM: LD C,0 ; 00 End of statement + LD B,0 +NXTSTL: LD A,C ; Statement and byte + LD C,B + LD B,A ; Statement end byte +NXTSTT: LD A,(HL) ; Get byte + OR A ; End of line? + RET Z ; Yes - Exit + CP B ; End of statement? + RET Z ; Yes - Exit + INC HL ; Next byte + CP '"' ; Literal string? + JP Z,NXTSTL ; Yes - Look for another '"' + JP NXTSTT ; Keep looking + +LET: CALL GETVAR ; Get variable name + CALL CHKSYN ; Make sure "=" follows + .BYTE ZEQUAL ; "=" token + PUSH DE ; Save address of variable + LD A,(TYPE) ; Get data type + PUSH AF ; Save type + CALL EVAL ; Evaluate expression + POP AF ; Restore type + EX (SP),HL ; Save code - Get var addr + LD (BRKLIN),HL ; Save address of variable + RRA ; Adjust type + CALL CHKTYP ; Check types are the same + JP Z,LETNUM ; Numeric - Move value +LETSTR: PUSH HL ; Save address of string var + LD HL,(FPREG) ; Pointer to string entry + PUSH HL ; Save it on stack + INC HL ; Skip over length + INC HL + LD E,(HL) ; LSB of string address + INC HL + LD D,(HL) ; MSB of string address + LD HL,(BASTXT) ; Point to start of program + CALL CPDEHL ; Is string before program? + JP NC,CRESTR ; Yes - Create string entry + LD HL,(STRSPC) ; Point to string space + CALL CPDEHL ; Is string literal in program? + POP DE ; Restore address of string + JP NC,MVSTPT ; Yes - Set up pointer + LD HL,TMPSTR ; Temporary string pool + CALL CPDEHL ; Is string in temporary pool? + JP NC,MVSTPT ; No - Set up pointer + .BYTE 3EH ; Skip "POP DE" +CRESTR: POP DE ; Restore address of string + CALL BAKTMP ; Back to last tmp-str entry + EX DE,HL ; Address of string entry + CALL SAVSTR ; Save string in string area +MVSTPT: CALL BAKTMP ; Back to last tmp-str entry + POP HL ; Get string pointer + CALL DETHL4 ; Move string pointer to var + POP HL ; Restore code string address + RET + +LETNUM: PUSH HL ; Save address of variable + CALL FPTHL ; Move value to variable + POP DE ; Restore address of variable + POP HL ; Restore code string address + RET + +ON: CALL GETINT ; Get integer 0-255 + LD A,(HL) ; Get "GOTO" or "GOSUB" token + LD B,A ; Save in B + CP ZGOSUB ; "GOSUB" token? + JP Z,ONGO ; Yes - Find line number + CALL CHKSYN ; Make sure it's "GOTO" + .BYTE ZGOTO ; "GOTO" token + DEC HL ; Cancel increment +ONGO: LD C,E ; Integer of branch value +ONGOLP: DEC C ; Count branches + LD A,B ; Get "GOTO" or "GOSUB" token + JP Z,ONJMP ; Go to that line if right one + CALL GETLN ; Get line number to DE + CP ',' ; Another line number? + RET NZ ; No - Drop through + JP ONGOLP ; Yes - loop + +IF: CALL EVAL ; Evaluate expression + LD A,(HL) ; Get token + CP ZGOTO ; "GOTO" token? + JP Z,IFGO ; Yes - Get line + CALL CHKSYN ; Make sure it's "THEN" + .BYTE ZTHEN ; "THEN" token + DEC HL ; Cancel increment +IFGO: CALL TSTNUM ; Make sure it's numeric + CALL TSTSGN ; Test state of expression + JP Z,REM ; False - Drop through + CALL GETCHR ; Get next character + JP C,GOTO ; Number - GOTO that line + JP IFJMP ; Otherwise do statement + +MRPRNT: DEC HL ; DEC 'cos GETCHR INCs + CALL GETCHR ; Get next character +PRINT: JP Z,PRNTCRLF ; CRLF if just PRINT +PRNTLP: RET Z ; End of list - Exit + CP ZTAB ; "TAB(" token? + JP Z,DOTAB ; Yes - Do TAB routine + CP ZSPC ; "SPC(" token? + JP Z,DOTAB ; Yes - Do SPC routine + PUSH HL ; Save code string address + CP ',' ; Comma? + JP Z,DOCOM ; Yes - Move to next zone + CP 59 ; ";" ; Semi-colon? + JP Z,NEXITM ; Do semi-colon routine + POP BC ; Code string address to BC + CALL EVAL ; Evaluate expression + PUSH HL ; Save code string address + LD A,(TYPE) ; Get variable type + OR A ; Is it a string variable? + JP NZ,PRNTST ; Yes - Output string contents + CALL NUMASC ; Convert number to text + CALL CRTST ; Create temporary string + LD (HL),' ' ; Followed by a space + LD HL,(FPREG) ; Get length of output + INC (HL) ; Plus 1 for the space + LD HL,(FPREG) ; < Not needed > + LD A,(LWIDTH) ; Get width of line + LD B,A ; To B + INC B ; Width 255 (No limit)? + JP Z,PRNTNB ; Yes - Output number string + INC B ; Adjust it + LD A,(CURPOS) ; Get cursor position + ADD A,(HL) ; Add length of string + DEC A ; Adjust it + CP B ; Will output fit on this line? + CALL NC,PRNTCRLF ; No - CRLF first +PRNTNB: CALL PRS1 ; Output string at (HL) + XOR A ; Skip CALL by setting 'z' flag +PRNTST: CALL NZ,PRS1 ; Output string at (HL) + POP HL ; Restore code string address + JP MRPRNT ; See if more to PRINT + +STTLIN: LD A,(CURPOS) ; Make sure on new line + OR A ; Already at start? + RET Z ; Yes - Do nothing + JP PRNTCRLF ; Start a new line + +ENDINP: LD (HL),0 ; Mark end of buffer + LD HL,BUFFER-1 ; Point to buffer +PRNTCRLF: LD A,CR ; Load a CR + CALL OUTC ; Output character + LD A,LF ; Load a LF + CALL OUTC ; Output character +DONULL: XOR A ; Set to position 0 + LD (CURPOS),A ; Store it + LD A,(NULLS) ; Get number of nulls +NULLP: DEC A ; Count them + RET Z ; Return if done + PUSH AF ; Save count + XOR A ; Load a null + CALL OUTC ; Output it + POP AF ; Restore count + JP NULLP ; Keep counting + +DOCOM: LD A,(COMMAN) ; Get comma width + LD B,A ; Save in B + LD A,(CURPOS) ; Get current position + CP B ; Within the limit? + CALL NC,PRNTCRLF ; No - output CRLF + JP NC,NEXITM ; Get next item +ZONELP: SUB 14 ; Next zone of 14 characters + JP NC,ZONELP ; Repeat if more zones + CPL ; Number of spaces to output + JP ASPCS ; Output them + +DOTAB: PUSH AF ; Save token + CALL FNDNUM ; Evaluate expression + CALL CHKSYN ; Make sure ")" follows + .BYTE ")" + DEC HL ; Back space on to ")" + POP AF ; Restore token + SUB ZSPC ; Was it "SPC(" ? + PUSH HL ; Save code string address + JP Z,DOSPC ; Yes - Do 'E' spaces + LD A,(CURPOS) ; Get current position +DOSPC: CPL ; Number of spaces to print to + ADD A,E ; Total number to print + JP NC,NEXITM ; TAB < Current POS(X) +ASPCS: INC A ; Output A spaces + LD B,A ; Save number to print + LD A,' ' ; Space +SPCLP: CALL OUTC ; Output character in A + DEC B ; Count them + JP NZ,SPCLP ; Repeat if more +NEXITM: POP HL ; Restore code string address + CALL GETCHR ; Get next character + JP PRNTLP ; More to print REDO: .BYTE "?Redo from start",CR,LF,0 -BADINP: LD A,(READFG) ; READ or INPUT? - OR A - JP NZ,DATSNR ; READ - ?SN Error - POP BC ; Throw away code string addr - LD HL,REDO ; "Redo from start" message - CALL PRS ; Output string - JP DOAGN ; Do last INPUT again - -INPUT: CALL IDTEST ; Test for illegal direct - LD A,(HL) ; Get character after "INPUT" - CP '"' ; Is there a prompt string? - LD A,0 ; Clear A and leave flags - LD (CTLOFG),A ; Enable output - JP NZ,NOPMPT ; No prompt - get input - CALL QTSTR ; Get string terminated by '"' - CALL CHKSYN ; Check for ';' after prompt - .BYTE ';' - PUSH HL ; Save code string address - CALL PRS1 ; Output prompt string - .BYTE 3EH ; Skip "PUSH HL" -NOPMPT: PUSH HL ; Save code string address - CALL PROMPT ; Get input with "? " prompt - POP BC ; Restore code string address - JP C,INPBRK ; Break pressed - Exit - INC HL ; Next byte - LD A,(HL) ; Get it - OR A ; End of line? - DEC HL ; Back again - PUSH BC ; Re-save code string address - JP Z,NXTDTA ; Yes - Find next DATA stmt - LD (HL),',' ; Store comma as separator - JP NXTITM ; Get next item - -READ: PUSH HL ; Save code string address - LD HL,(NXTDAT) ; Next DATA statement - .BYTE 0F6H ; Flag "READ" -NXTITM: XOR A ; Flag "INPUT" - LD (READFG),A ; Save "READ"/"INPUT" flag - EX (SP),HL ; Get code str' , Save pointer - JP GTVLUS ; Get values - -NEDMOR: CALL CHKSYN ; Check for comma between items - .BYTE ',' -GTVLUS: CALL GETVAR ; Get variable name - EX (SP),HL ; Save code str" , Get pointer - PUSH DE ; Save variable address - LD A,(HL) ; Get next "INPUT"/"DATA" byte - CP ',' ; Comma? - JP Z,ANTVLU ; Yes - Get another value - LD A,(READFG) ; Is it READ? - OR A - JP NZ,FDTLP ; Yes - Find next DATA stmt - LD A,'?' ; More INPUT needed - CALL OUTC ; Output character - CALL PROMPT ; Get INPUT with prompt - POP DE ; Variable address - POP BC ; Code string address - JP C,INPBRK ; Break pressed - INC HL ; Point to next DATA byte - LD A,(HL) ; Get byte - OR A ; Is it zero (No input) ? - DEC HL ; Back space INPUT pointer - PUSH BC ; Save code string address - JP Z,NXTDTA ; Find end of buffer - PUSH DE ; Save variable address -ANTVLU: LD A,(TYPE) ; Check data type - OR A ; Is it numeric? - JP Z,INPBIN ; Yes - Convert to binary - CALL GETCHR ; Get next character - LD D,A ; Save input character - LD B,A ; Again - CP '"' ; Start of literal sting? - JP Z,STRENT ; Yes - Create string entry - LD A,(READFG) ; "READ" or "INPUT" ? - OR A - LD D,A ; Save 00 if "INPUT" - JP Z,ITMSEP ; "INPUT" - End with 00 - LD D,':' ; "DATA" - End with 00 or ':' -ITMSEP: LD B,',' ; Item separator - DEC HL ; Back space for DTSTR -STRENT: CALL DTSTR ; Get string terminated by D - EX DE,HL ; String address to DE - LD HL,LTSTND ; Where to go after LETSTR - EX (SP),HL ; Save HL , get input pointer - PUSH DE ; Save address of string - JP LETSTR ; Assign string to variable - -INPBIN: CALL GETCHR ; Get next character - CALL ASCTFP ; Convert ASCII to FP number - EX (SP),HL ; Save input ptr, Get var addr - CALL FPTHL ; Move FPREG to variable - POP HL ; Restore input pointer -LTSTND: DEC HL ; DEC 'cos GETCHR INCs - CALL GETCHR ; Get next character - JP Z,MORDT ; End of line - More needed? - CP ',' ; Another value? - JP NZ,BADINP ; No - Bad input -MORDT: EX (SP),HL ; Get code string address - DEC HL ; DEC 'cos GETCHR INCs - CALL GETCHR ; Get next character - JP NZ,NEDMOR ; More needed - Get it - POP DE ; Restore DATA pointer - LD A,(READFG) ; "READ" or "INPUT" ? - OR A - EX DE,HL ; DATA pointer to HL - JP NZ,UPDATA ; Update DATA pointer if "READ" - PUSH DE ; Save code string address - OR (HL) ; More input given? - LD HL,EXTIG ; "?Extra ignored" message - CALL NZ,PRS ; Output string if extra given - POP HL ; Restore code string address - RET +BADINP: LD A,(READFG) ; READ or INPUT? + OR A + JP NZ,DATSNR ; READ - ?SN Error + POP BC ; Throw away code string addr + LD HL,REDO ; "Redo from start" message + CALL PRS ; Output string + JP DOAGN ; Do last INPUT again + +INPUT: CALL IDTEST ; Test for illegal direct + LD A,(HL) ; Get character after "INPUT" + CP '"' ; Is there a prompt string? + LD A,0 ; Clear A and leave flags + LD (CTLOFG),A ; Enable output + JP NZ,NOPMPT ; No prompt - get input + CALL QTSTR ; Get string terminated by '"' + CALL CHKSYN ; Check for ';' after prompt + .BYTE ';' + PUSH HL ; Save code string address + CALL PRS1 ; Output prompt string + .BYTE 3EH ; Skip "PUSH HL" +NOPMPT: PUSH HL ; Save code string address + CALL PROMPT ; Get input with "? " prompt + POP BC ; Restore code string address + JP C,INPBRK ; Break pressed - Exit + INC HL ; Next byte + LD A,(HL) ; Get it + OR A ; End of line? + DEC HL ; Back again + PUSH BC ; Re-save code string address + JP Z,NXTDTA ; Yes - Find next DATA stmt + LD (HL),',' ; Store comma as separator + JP NXTITM ; Get next item + +READ: PUSH HL ; Save code string address + LD HL,(NXTDAT) ; Next DATA statement + .BYTE 0F6H ; Flag "READ" +NXTITM: XOR A ; Flag "INPUT" + LD (READFG),A ; Save "READ"/"INPUT" flag + EX (SP),HL ; Get code str' , Save pointer + JP GTVLUS ; Get values + +NEDMOR: CALL CHKSYN ; Check for comma between items + .BYTE ',' +GTVLUS: CALL GETVAR ; Get variable name + EX (SP),HL ; Save code str" , Get pointer + PUSH DE ; Save variable address + LD A,(HL) ; Get next "INPUT"/"DATA" byte + CP ',' ; Comma? + JP Z,ANTVLU ; Yes - Get another value + LD A,(READFG) ; Is it READ? + OR A + JP NZ,FDTLP ; Yes - Find next DATA stmt + LD A,'?' ; More INPUT needed + CALL OUTC ; Output character + CALL PROMPT ; Get INPUT with prompt + POP DE ; Variable address + POP BC ; Code string address + JP C,INPBRK ; Break pressed + INC HL ; Point to next DATA byte + LD A,(HL) ; Get byte + OR A ; Is it zero (No input) ? + DEC HL ; Back space INPUT pointer + PUSH BC ; Save code string address + JP Z,NXTDTA ; Find end of buffer + PUSH DE ; Save variable address +ANTVLU: LD A,(TYPE) ; Check data type + OR A ; Is it numeric? + JP Z,INPBIN ; Yes - Convert to binary + CALL GETCHR ; Get next character + LD D,A ; Save input character + LD B,A ; Again + CP '"' ; Start of literal sting? + JP Z,STRENT ; Yes - Create string entry + LD A,(READFG) ; "READ" or "INPUT" ? + OR A + LD D,A ; Save 00 if "INPUT" + JP Z,ITMSEP ; "INPUT" - End with 00 + LD D,':' ; "DATA" - End with 00 or ':' +ITMSEP: LD B,',' ; Item separator + DEC HL ; Back space for DTSTR +STRENT: CALL DTSTR ; Get string terminated by D + EX DE,HL ; String address to DE + LD HL,LTSTND ; Where to go after LETSTR + EX (SP),HL ; Save HL , get input pointer + PUSH DE ; Save address of string + JP LETSTR ; Assign string to variable + +INPBIN: CALL GETCHR ; Get next character + CALL ASCTFP ; Convert ASCII to FP number + EX (SP),HL ; Save input ptr, Get var addr + CALL FPTHL ; Move FPREG to variable + POP HL ; Restore input pointer +LTSTND: DEC HL ; DEC 'cos GETCHR INCs + CALL GETCHR ; Get next character + JP Z,MORDT ; End of line - More needed? + CP ',' ; Another value? + JP NZ,BADINP ; No - Bad input +MORDT: EX (SP),HL ; Get code string address + DEC HL ; DEC 'cos GETCHR INCs + CALL GETCHR ; Get next character + JP NZ,NEDMOR ; More needed - Get it + POP DE ; Restore DATA pointer + LD A,(READFG) ; "READ" or "INPUT" ? + OR A + EX DE,HL ; DATA pointer to HL + JP NZ,UPDATA ; Update DATA pointer if "READ" + PUSH DE ; Save code string address + OR (HL) ; More input given? + LD HL,EXTIG ; "?Extra ignored" message + CALL NZ,PRS ; Output string if extra given + POP HL ; Restore code string address + RET EXTIG: .BYTE "?Extra ignored",CR,LF,0 -FDTLP: CALL DATA ; Get next statement - OR A ; End of line? - JP NZ,FANDT ; No - See if DATA statement - INC HL - LD A,(HL) ; End of program? - INC HL - OR (HL) ; 00 00 Ends program - LD E,OD ; ?OD Error - JP Z,ERROR ; Yes - Out of DATA - INC HL - LD E,(HL) ; LSB of line number - INC HL - LD D,(HL) ; MSB of line number - EX DE,HL - LD (DATLIN),HL ; Set line of current DATA item - EX DE,HL -FANDT: CALL GETCHR ; Get next character - CP ZDATA ; "DATA" token - JP NZ,FDTLP ; No "DATA" - Keep looking - JP ANTVLU ; Found - Convert input - -NEXT: LD DE,0 ; In case no index given -NEXT1: CALL NZ,GETVAR ; Get index address - LD (BRKLIN),HL ; Save code string address - CALL BAKSTK ; Look for "FOR" block - JP NZ,NFERR ; No "FOR" - ?NF Error - LD SP,HL ; Clear nested loops - PUSH DE ; Save index address - LD A,(HL) ; Get sign of STEP - INC HL - PUSH AF ; Save sign of STEP - PUSH DE ; Save index address - CALL PHLTFP ; Move index value to FPREG - EX (SP),HL ; Save address of TO value - PUSH HL ; Save address of index - CALL ADDPHL ; Add STEP to index value - POP HL ; Restore address of index - CALL FPTHL ; Move value to index variable - POP HL ; Restore address of TO value - CALL LOADFP ; Move TO value to BCDE - PUSH HL ; Save address of line of FOR - CALL CMPNUM ; Compare index with TO value - POP HL ; Restore address of line num - POP BC ; Address of sign of STEP - SUB B ; Compare with expected sign - CALL LOADFP ; BC = Loop stmt,DE = Line num - JP Z,KILFOR ; Loop finished - Terminate it - EX DE,HL ; Loop statement line number - LD (LINEAT),HL ; Set loop line number - LD L,C ; Set code string to loop - LD H,B - JP PUTFID ; Put back "FOR" and continue - -KILFOR: LD SP,HL ; Remove "FOR" block - LD HL,(BRKLIN) ; Code string after "NEXT" - LD A,(HL) ; Get next byte in code string - CP ',' ; More NEXTs ? - JP NZ,RUNCNT ; No - Do next statement - CALL GETCHR ; Position to index name - CALL NEXT1 ; Re-enter NEXT routine +FDTLP: CALL DATA ; Get next statement + OR A ; End of line? + JP NZ,FANDT ; No - See if DATA statement + INC HL + LD A,(HL) ; End of program? + INC HL + OR (HL) ; 00 00 Ends program + LD E,OD ; ?OD Error + JP Z,ERROR ; Yes - Out of DATA + INC HL + LD E,(HL) ; LSB of line number + INC HL + LD D,(HL) ; MSB of line number + EX DE,HL + LD (DATLIN),HL ; Set line of current DATA item + EX DE,HL +FANDT: CALL GETCHR ; Get next character + CP ZDATA ; "DATA" token + JP NZ,FDTLP ; No "DATA" - Keep looking + JP ANTVLU ; Found - Convert input + +NEXT: LD DE,0 ; In case no index given +NEXT1: CALL NZ,GETVAR ; Get index address + LD (BRKLIN),HL ; Save code string address + CALL BAKSTK ; Look for "FOR" block + JP NZ,NFERR ; No "FOR" - ?NF Error + LD SP,HL ; Clear nested loops + PUSH DE ; Save index address + LD A,(HL) ; Get sign of STEP + INC HL + PUSH AF ; Save sign of STEP + PUSH DE ; Save index address + CALL PHLTFP ; Move index value to FPREG + EX (SP),HL ; Save address of TO value + PUSH HL ; Save address of index + CALL ADDPHL ; Add STEP to index value + POP HL ; Restore address of index + CALL FPTHL ; Move value to index variable + POP HL ; Restore address of TO value + CALL LOADFP ; Move TO value to BCDE + PUSH HL ; Save address of line of FOR + CALL CMPNUM ; Compare index with TO value + POP HL ; Restore address of line num + POP BC ; Address of sign of STEP + SUB B ; Compare with expected sign + CALL LOADFP ; BC = Loop stmt,DE = Line num + JP Z,KILFOR ; Loop finished - Terminate it + EX DE,HL ; Loop statement line number + LD (LINEAT),HL ; Set loop line number + LD L,C ; Set code string to loop + LD H,B + JP PUTFID ; Put back "FOR" and continue + +KILFOR: LD SP,HL ; Remove "FOR" block + LD HL,(BRKLIN) ; Code string after "NEXT" + LD A,(HL) ; Get next byte in code string + CP ',' ; More NEXTs ? + JP NZ,RUNCNT ; No - Do next statement + CALL GETCHR ; Position to index name + CALL NEXT1 ; Re-enter NEXT routine ; < will not RETurn to here , Exit to RUNCNT or Loop > -GETNUM: CALL EVAL ; Get a numeric expression -TSTNUM: .BYTE 0F6H ; Clear carry (numeric) -TSTSTR: SCF ; Set carry (string) -CHKTYP: LD A,(TYPE) ; Check types match - ADC A,A ; Expected + actual - OR A ; Clear carry , set parity - RET PE ; Even parity - Types match - JP TMERR ; Different types - Error - -OPNPAR: CALL CHKSYN ; Make sure "(" follows - .BYTE "(" -EVAL: DEC HL ; Evaluate expression & save - LD D,0 ; Precedence value -EVAL1: PUSH DE ; Save precedence - LD C,1 - CALL CHKSTK ; Check for 1 level of stack - CALL OPRND ; Get next expression value -EVAL2: LD (NXTOPR),HL ; Save address of next operator -EVAL3: LD HL,(NXTOPR) ; Restore address of next opr - POP BC ; Precedence value and operator - LD A,B ; Get precedence value - CP 78H ; "AND" or "OR" ? - CALL NC,TSTNUM ; No - Make sure it's a number - LD A,(HL) ; Get next operator / function - LD D,0 ; Clear Last relation -RLTLP: SUB ZGTR ; ">" Token - JP C,FOPRND ; + - * / ^ AND OR - Test it - CP ZLTH+1-ZGTR ; < = > - JP NC,FOPRND ; Function - Call it - CP ZEQUAL-ZGTR ; "=" - RLA ; <- Test for legal - XOR D ; <- combinations of < = > - CP D ; <- by combining last token - LD D,A ; <- with current one - JP C,SNERR ; Error if "<<' '==" or ">>" - LD (CUROPR),HL ; Save address of current token - CALL GETCHR ; Get next character - JP RLTLP ; Treat the two as one - -FOPRND: LD A,D ; < = > found ? - OR A - JP NZ,TSTRED ; Yes - Test for reduction - LD A,(HL) ; Get operator token - LD (CUROPR),HL ; Save operator address - SUB ZPLUS ; Operator or function? - RET C ; Neither - Exit - CP ZOR+1-ZPLUS ; Is it + - * / ^ AND OR ? - RET NC ; No - Exit - LD E,A ; Coded operator - LD A,(TYPE) ; Get data type - DEC A ; FF = numeric , 00 = string - OR E ; Combine with coded operator - LD A,E ; Get coded operator - JP Z,CONCAT ; String concatenation - RLCA ; Times 2 - ADD A,E ; Times 3 - LD E,A ; To DE (D is 0) - LD HL,PRITAB ; Precedence table - ADD HL,DE ; To the operator concerned - LD A,B ; Last operator precedence - LD D,(HL) ; Get evaluation precedence - CP D ; Compare with eval precedence - RET NC ; Exit if higher precedence - INC HL ; Point to routine address - CALL TSTNUM ; Make sure it's a number - -STKTHS: PUSH BC ; Save last precedence & token - LD BC,EVAL3 ; Where to go on prec' break - PUSH BC ; Save on stack for return - LD B,E ; Save operator - LD C,D ; Save precedence - CALL STAKFP ; Move value to stack - LD E,B ; Restore operator - LD D,C ; Restore precedence - LD C,(HL) ; Get LSB of routine address - INC HL - LD B,(HL) ; Get MSB of routine address - INC HL - PUSH BC ; Save routine address - LD HL,(CUROPR) ; Address of current operator - JP EVAL1 ; Loop until prec' break - -OPRND: XOR A ; Get operand routine - LD (TYPE),A ; Set numeric expected - CALL GETCHR ; Get next character - LD E,MO ; ?MO Error - JP Z,ERROR ; No operand - Error - JP C,ASCTFP ; Number - Get value - CALL CHKLTR ; See if a letter - JP NC,CONVAR ; Letter - Find variable - CP '&' ; &H = HEX, &B = BINARY - JR NZ, NOTAMP - CALL GETCHR ; Get next character - CP 'H' ; Hex number indicated? [function added] - JP Z,HEXTFP ; Convert Hex to FPREG - CP 'B' ; Binary number indicated? [function added] - JP Z,BINTFP ; Convert Bin to FPREG - LD E,SN ; If neither then a ?SN Error - JP Z,ERROR ; -NOTAMP: CP ZPLUS ; '+' Token ? - JP Z,OPRND ; Yes - Look for operand - CP '.' ; '.' ? - JP Z,ASCTFP ; Yes - Create FP number - CP ZMINUS ; '-' Token ? - JP Z,MINUS ; Yes - Do minus - CP '"' ; Literal string ? - JP Z,QTSTR ; Get string terminated by '"' - CP ZNOT ; "NOT" Token ? - JP Z,EVNOT ; Yes - Eval NOT expression - CP ZFN ; "FN" Token ? - JP Z,DOFN ; Yes - Do FN routine - SUB ZSGN ; Is it a function? - JP NC,FNOFST ; Yes - Evaluate function -EVLPAR: CALL OPNPAR ; Evaluate expression in "()" - CALL CHKSYN ; Make sure ")" follows - .BYTE ")" - RET - -MINUS: LD D,7DH ; '-' precedence - CALL EVAL1 ; Evaluate until prec' break - LD HL,(NXTOPR) ; Get next operator address - PUSH HL ; Save next operator address - CALL INVSGN ; Negate value -RETNUM: CALL TSTNUM ; Make sure it's a number - POP HL ; Restore next operator address - RET - -CONVAR: CALL GETVAR ; Get variable address to DE -FRMEVL: PUSH HL ; Save code string address - EX DE,HL ; Variable address to HL - LD (FPREG),HL ; Save address of variable - LD A,(TYPE) ; Get type - OR A ; Numeric? - CALL Z,PHLTFP ; Yes - Move contents to FPREG - POP HL ; Restore code string address - RET - -FNOFST: LD B,0 ; Get address of function - RLCA ; Double function offset - LD C,A ; BC = Offset in function table - PUSH BC ; Save adjusted token value - CALL GETCHR ; Get next character - LD A,C ; Get adjusted token value - CP 2*(ZLEFT-ZSGN)-1; Adj' LEFT$,RIGHT$ or MID$ ? - JP C,FNVAL ; No - Do function - CALL OPNPAR ; Evaluate expression (X,... - CALL CHKSYN ; Make sure ',' follows - .BYTE ',' - CALL TSTSTR ; Make sure it's a string - EX DE,HL ; Save code string address - LD HL,(FPREG) ; Get address of string - EX (SP),HL ; Save address of string - PUSH HL ; Save adjusted token value - EX DE,HL ; Restore code string address - CALL GETINT ; Get integer 0-255 - EX DE,HL ; Save code string address - EX (SP),HL ; Save integer,HL = adj' token - JP GOFUNC ; Jump to string function - -FNVAL: CALL EVLPAR ; Evaluate expression - EX (SP),HL ; HL = Adjusted token value - LD DE,RETNUM ; Return number from function - PUSH DE ; Save on stack -GOFUNC: LD BC,FNCTAB ; Function routine addresses - ADD HL,BC ; Point to right address - LD C,(HL) ; Get LSB of address - INC HL ; - LD H,(HL) ; Get MSB of address - LD L,C ; Address to HL - JP (HL) ; Jump to function - -SGNEXP: DEC D ; Dee to flag negative exponent - CP ZMINUS ; '-' token ? - RET Z ; Yes - Return - CP '-' ; '-' ASCII ? - RET Z ; Yes - Return - INC D ; Inc to flag positive exponent - CP '+' ; '+' ASCII ? - RET Z ; Yes - Return - CP ZPLUS ; '+' token ? - RET Z ; Yes - Return - DEC HL ; DEC 'cos GETCHR INCs - RET ; Return "NZ" - -POR: .BYTE 0F6H ; Flag "OR" -PAND: XOR A ; Flag "AND" - PUSH AF ; Save "AND" / "OR" flag - CALL TSTNUM ; Make sure it's a number - CALL DEINT ; Get integer -32768 to 32767 - POP AF ; Restore "AND" / "OR" flag - EX DE,HL ; <- Get last - POP BC ; <- value - EX (SP),HL ; <- from - EX DE,HL ; <- stack - CALL FPBCDE ; Move last value to FPREG - PUSH AF ; Save "AND" / "OR" flag - CALL DEINT ; Get integer -32768 to 32767 - POP AF ; Restore "AND" / "OR" flag - POP BC ; Get value - LD A,C ; Get LSB - LD HL,ACPASS ; Address of save AC as current - JP NZ,POR1 ; Jump if OR - AND E ; "AND" LSBs - LD C,A ; Save LSB - LD A,B ; Get MBS - AND D ; "AND" MSBs - JP (HL) ; Save AC as current (ACPASS) - -POR1: OR E ; "OR" LSBs - LD C,A ; Save LSB - LD A,B ; Get MSB - OR D ; "OR" MSBs - JP (HL) ; Save AC as current (ACPASS) - -TSTRED: LD HL,CMPLOG ; Logical compare routine - LD A,(TYPE) ; Get data type - RRA ; Carry set = string - LD A,D ; Get last precedence value - RLA ; Times 2 plus carry - LD E,A ; To E - LD D,64H ; Relational precedence - LD A,B ; Get current precedence - CP D ; Compare with last - RET NC ; Eval if last was rel' or log' - JP STKTHS ; Stack this one and get next - -CMPLOG: .WORD CMPLG1 ; Compare two values / strings -CMPLG1: LD A,C ; Get data type - OR A - RRA - POP BC ; Get last expression to BCDE - POP DE - PUSH AF ; Save status - CALL CHKTYP ; Check that types match - LD HL,CMPRES ; Result to comparison - PUSH HL ; Save for RETurn - JP Z,CMPNUM ; Compare values if numeric - XOR A ; Compare two strings - LD (TYPE),A ; Set type to numeric - PUSH DE ; Save string name - CALL GSTRCU ; Get current string - LD A,(HL) ; Get length of string - INC HL - INC HL - LD C,(HL) ; Get LSB of address - INC HL - LD B,(HL) ; Get MSB of address - POP DE ; Restore string name - PUSH BC ; Save address of string - PUSH AF ; Save length of string - CALL GSTRDE ; Get second string - CALL LOADFP ; Get address of second string - POP AF ; Restore length of string 1 - LD D,A ; Length to D - POP HL ; Restore address of string 1 -CMPSTR: LD A,E ; Bytes of string 2 to do - OR D ; Bytes of string 1 to do - RET Z ; Exit if all bytes compared - LD A,D ; Get bytes of string 1 to do - SUB 1 - RET C ; Exit if end of string 1 - XOR A - CP E ; Bytes of string 2 to do - INC A - RET NC ; Exit if end of string 2 - DEC D ; Count bytes in string 1 - DEC E ; Count bytes in string 2 - LD A,(BC) ; Byte in string 2 - CP (HL) ; Compare to byte in string 1 - INC HL ; Move up string 1 - INC BC ; Move up string 2 - JP Z,CMPSTR ; Same - Try next bytes - CCF ; Flag difference (">" or "<") - JP FLGDIF ; "<" gives -1 , ">" gives +1 - -CMPRES: INC A ; Increment current value - ADC A,A ; Double plus carry - POP BC ; Get other value - AND B ; Combine them - ADD A,-1 ; Carry set if different - SBC A,A ; 00 - Equal , FF - Different - JP FLGREL ; Set current value & continue - -EVNOT: LD D,5AH ; Precedence value for "NOT" - CALL EVAL1 ; Eval until precedence break - CALL TSTNUM ; Make sure it's a number - CALL DEINT ; Get integer -32768 - 32767 - LD A,E ; Get LSB - CPL ; Invert LSB - LD C,A ; Save "NOT" of LSB - LD A,D ; Get MSB - CPL ; Invert MSB - CALL ACPASS ; Save AC as current - POP BC ; Clean up stack - JP EVAL3 ; Continue evaluation - -DIMRET: DEC HL ; DEC 'cos GETCHR INCs - CALL GETCHR ; Get next character - RET Z ; End of DIM statement - CALL CHKSYN ; Make sure ',' follows - .BYTE ',' -DIM: LD BC,DIMRET ; Return to "DIMRET" - PUSH BC ; Save on stack - .BYTE 0F6H ; Flag "Create" variable -GETVAR: XOR A ; Find variable address,to DE - LD (LCRFLG),A ; Set locate / create flag - LD B,(HL) ; Get First byte of name -GTFNAM: CALL CHKLTR ; See if a letter - JP C,SNERR ; ?SN Error if not a letter - XOR A - LD C,A ; Clear second byte of name - LD (TYPE),A ; Set type to numeric - CALL GETCHR ; Get next character - JP C,SVNAM2 ; Numeric - Save in name - CALL CHKLTR ; See if a letter - JP C,CHARTY ; Not a letter - Check type -SVNAM2: LD C,A ; Save second byte of name -ENDNAM: CALL GETCHR ; Get next character - JP C,ENDNAM ; Numeric - Get another - CALL CHKLTR ; See if a letter - JP NC,ENDNAM ; Letter - Get another -CHARTY: SUB '$' ; String variable? - JP NZ,NOTSTR ; No - Numeric variable - INC A ; A = 1 (string type) - LD (TYPE),A ; Set type to string - RRCA ; A = 80H , Flag for string - ADD A,C ; 2nd byte of name has bit 7 on - LD C,A ; Resave second byte on name - CALL GETCHR ; Get next character -NOTSTR: LD A,(FORFLG) ; Array name needed ? - DEC A - JP Z,ARLDSV ; Yes - Get array name - JP P,NSCFOR ; No array with "FOR" or "FN" - LD A,(HL) ; Get byte again - SUB '(' ; Subscripted variable? - JP Z,SBSCPT ; Yes - Sort out subscript - -NSCFOR: XOR A ; Simple variable - LD (FORFLG),A ; Clear "FOR" flag - PUSH HL ; Save code string address - LD D,B ; DE = Variable name to find - LD E,C - LD HL,(FNRGNM) ; FN argument name - CALL CPDEHL ; Is it the FN argument? - LD DE,FNARG ; Point to argument value - JP Z,POPHRT ; Yes - Return FN argument value - LD HL,(VAREND) ; End of variables - EX DE,HL ; Address of end of search - LD HL,(PROGND) ; Start of variables address -FNDVAR: CALL CPDEHL ; End of variable list table? - JP Z,CFEVAL ; Yes - Called from EVAL? - LD A,C ; Get second byte of name - SUB (HL) ; Compare with name in list - INC HL ; Move on to first byte - JP NZ,FNTHR ; Different - Find another - LD A,B ; Get first byte of name - SUB (HL) ; Compare with name in list -FNTHR: INC HL ; Move on to LSB of value - JP Z,RETADR ; Found - Return address - INC HL ; <- Skip - INC HL ; <- over - INC HL ; <- F.P. - INC HL ; <- value - JP FNDVAR ; Keep looking - -CFEVAL: POP HL ; Restore code string address - EX (SP),HL ; Get return address - PUSH DE ; Save address of variable - LD DE,FRMEVL ; Return address in EVAL - CALL CPDEHL ; Called from EVAL ? - POP DE ; Restore address of variable - JP Z,RETNUL ; Yes - Return null variable - EX (SP),HL ; Put back return - PUSH HL ; Save code string address - PUSH BC ; Save variable name - LD BC,6 ; 2 byte name plus 4 byte data - LD HL,(ARREND) ; End of arrays - PUSH HL ; Save end of arrays - ADD HL,BC ; Move up 6 bytes - POP BC ; Source address in BC - PUSH HL ; Save new end address - CALL MOVUP ; Move arrays up - POP HL ; Restore new end address - LD (ARREND),HL ; Set new end address - LD H,B ; End of variables to HL - LD L,C - LD (VAREND),HL ; Set new end address - -ZEROLP: DEC HL ; Back through to zero variable - LD (HL),0 ; Zero byte in variable - CALL CPDEHL ; Done them all? - JP NZ,ZEROLP ; No - Keep on going - POP DE ; Get variable name - LD (HL),E ; Store second character - INC HL - LD (HL),D ; Store first character - INC HL -RETADR: EX DE,HL ; Address of variable in DE - POP HL ; Restore code string address - RET - -RETNUL: LD (FPEXP),A ; Set result to zero - LD HL,ZERBYT ; Also set a null string - LD (FPREG),HL ; Save for EVAL - POP HL ; Restore code string address - RET - -SBSCPT: PUSH HL ; Save code string address - LD HL,(LCRFLG) ; Locate/Create and Type - EX (SP),HL ; Save and get code string - LD D,A ; Zero number of dimensions -SCPTLP: PUSH DE ; Save number of dimensions - PUSH BC ; Save array name - CALL FPSINT ; Get subscript (0-32767) - POP BC ; Restore array name - POP AF ; Get number of dimensions - EX DE,HL - EX (SP),HL ; Save subscript value - PUSH HL ; Save LCRFLG and TYPE - EX DE,HL - INC A ; Count dimensions - LD D,A ; Save in D - LD A,(HL) ; Get next byte in code string - CP ',' ; Comma (more to come)? - JP Z,SCPTLP ; Yes - More subscripts - CALL CHKSYN ; Make sure ")" follows - .BYTE ")" - LD (NXTOPR),HL ; Save code string address - POP HL ; Get LCRFLG and TYPE - LD (LCRFLG),HL ; Restore Locate/create & type - LD E,0 ; Flag not CSAVE* or CLOAD* - PUSH DE ; Save number of dimensions (D) - .BYTE 11H ; Skip "PUSH HL" and "PUSH AF' - -ARLDSV: PUSH HL ; Save code string address - PUSH AF ; A = 00 , Flags set = Z,N - LD HL,(VAREND) ; Start of arrays - .BYTE 3EH ; Skip "ADD HL,DE" -FNDARY: ADD HL,DE ; Move to next array start - EX DE,HL - LD HL,(ARREND) ; End of arrays - EX DE,HL ; Current array pointer - CALL CPDEHL ; End of arrays found? - JP Z,CREARY ; Yes - Create array - LD A,(HL) ; Get second byte of name - CP C ; Compare with name given - INC HL ; Move on - JP NZ,NXTARY ; Different - Find next array - LD A,(HL) ; Get first byte of name - CP B ; Compare with name given -NXTARY: INC HL ; Move on - LD E,(HL) ; Get LSB of next array address - INC HL - LD D,(HL) ; Get MSB of next array address - INC HL - JP NZ,FNDARY ; Not found - Keep looking - LD A,(LCRFLG) ; Found Locate or Create it? - OR A - JP NZ,DDERR ; Create - ?DD Error - POP AF ; Locate - Get number of dim'ns - LD B,H ; BC Points to array dim'ns - LD C,L - JP Z,POPHRT ; Jump if array load/save - SUB (HL) ; Same number of dimensions? - JP Z,FINDEL ; Yes - Find element -BSERR: LD E,BS ; ?BS Error - JP ERROR ; Output error - -CREARY: LD DE,4 ; 4 Bytes per entry - POP AF ; Array to save or 0 dim'ns? - JP Z,FCERR ; Yes - ?FC Error - LD (HL),C ; Save second byte of name - INC HL - LD (HL),B ; Save first byte of name - INC HL - LD C,A ; Number of dimensions to C - CALL CHKSTK ; Check if enough memory - INC HL ; Point to number of dimensions - INC HL - LD (CUROPR),HL ; Save address of pointer - LD (HL),C ; Set number of dimensions - INC HL - LD A,(LCRFLG) ; Locate of Create? - RLA ; Carry set = Create - LD A,C ; Get number of dimensions -CRARLP: LD BC,10+1 ; Default dimension size 10 - JP NC,DEFSIZ ; Locate - Set default size - POP BC ; Get specified dimension size - INC BC ; Include zero element -DEFSIZ: LD (HL),C ; Save LSB of dimension size - INC HL - LD (HL),B ; Save MSB of dimension size - INC HL - PUSH AF ; Save num' of dim'ns an status - PUSH HL ; Save address of dim'n size - CALL MLDEBC ; Multiply DE by BC to find - EX DE,HL ; amount of mem needed (to DE) - POP HL ; Restore address of dimension - POP AF ; Restore number of dimensions - DEC A ; Count them - JP NZ,CRARLP ; Do next dimension if more - PUSH AF ; Save locate/create flag - LD B,D ; MSB of memory needed - LD C,E ; LSB of memory needed - EX DE,HL - ADD HL,DE ; Add bytes to array start - JP C,OMERR ; Too big - Error - CALL ENFMEM ; See if enough memory - LD (ARREND),HL ; Save new end of array - -ZERARY: DEC HL ; Back through array data - LD (HL),0 ; Set array element to zero - CALL CPDEHL ; All elements zeroed? - JP NZ,ZERARY ; No - Keep on going - INC BC ; Number of bytes + 1 - LD D,A ; A=0 - LD HL,(CUROPR) ; Get address of array - LD E,(HL) ; Number of dimensions - EX DE,HL ; To HL - ADD HL,HL ; Two bytes per dimension size - ADD HL,BC ; Add number of bytes - EX DE,HL ; Bytes needed to DE - DEC HL - DEC HL - LD (HL),E ; Save LSB of bytes needed - INC HL - LD (HL),D ; Save MSB of bytes needed - INC HL - POP AF ; Locate / Create? - JP C,ENDDIM ; A is 0 , End if create -FINDEL: LD B,A ; Find array element - LD C,A - LD A,(HL) ; Number of dimensions - INC HL - .BYTE 16H ; Skip "POP HL" -FNDELP: POP HL ; Address of next dim' size - LD E,(HL) ; Get LSB of dim'n size - INC HL - LD D,(HL) ; Get MSB of dim'n size - INC HL - EX (SP),HL ; Save address - Get index - PUSH AF ; Save number of dim'ns - CALL CPDEHL ; Dimension too large? - JP NC,BSERR ; Yes - ?BS Error - PUSH HL ; Save index - CALL MLDEBC ; Multiply previous by size - POP DE ; Index supplied to DE - ADD HL,DE ; Add index to pointer - POP AF ; Number of dimensions - DEC A ; Count them - LD B,H ; MSB of pointer - LD C,L ; LSB of pointer - JP NZ,FNDELP ; More - Keep going - ADD HL,HL ; 4 Bytes per element - ADD HL,HL - POP BC ; Start of array - ADD HL,BC ; Point to element - EX DE,HL ; Address of element to DE -ENDDIM: LD HL,(NXTOPR) ; Got code string address - RET - -FRE: LD HL,(ARREND) ; Start of free memory - EX DE,HL ; To DE - LD HL,0 ; End of free memory - ADD HL,SP ; Current stack value - LD A,(TYPE) ; Dummy argument type - OR A - JP Z,FRENUM ; Numeric - Free variable space - CALL GSTRCU ; Current string to pool - CALL GARBGE ; Garbage collection - LD HL,(STRSPC) ; Bottom of string space in use - EX DE,HL ; To DE - LD HL,(STRBOT) ; Bottom of string space -FRENUM: LD A,L ; Get LSB of end - SUB E ; Subtract LSB of beginning - LD C,A ; Save difference if C - LD A,H ; Get MSB of end - SBC A,D ; Subtract MSB of beginning -ACPASS: LD B,C ; Return integer AC -ABPASS: LD D,B ; Return integer AB - LD E,0 - LD HL,TYPE ; Point to type - LD (HL),E ; Set type to numeric - LD B,80H+16 ; 16 bit integer - JP RETINT ; Return the integr - -POS: LD A,(CURPOS) ; Get cursor position -PASSA: LD B,A ; Put A into AB - XOR A ; Zero A - JP ABPASS ; Return integer AB - -DEF: CALL CHEKFN ; Get "FN" and name - CALL IDTEST ; Test for illegal direct - LD BC,DATA ; To get next statement - PUSH BC ; Save address for RETurn - PUSH DE ; Save address of function ptr - CALL CHKSYN ; Make sure "(" follows - .BYTE "(" - CALL GETVAR ; Get argument variable name - PUSH HL ; Save code string address - EX DE,HL ; Argument address to HL - DEC HL - LD D,(HL) ; Get first byte of arg name - DEC HL - LD E,(HL) ; Get second byte of arg name - POP HL ; Restore code string address - CALL TSTNUM ; Make sure numeric argument - CALL CHKSYN ; Make sure ")" follows - .BYTE ")" - CALL CHKSYN ; Make sure "=" follows - .BYTE ZEQUAL ; "=" token - LD B,H ; Code string address to BC - LD C,L - EX (SP),HL ; Save code str , Get FN ptr - LD (HL),C ; Save LSB of FN code string - INC HL - LD (HL),B ; Save MSB of FN code string - JP SVSTAD ; Save address and do function - -DOFN: CALL CHEKFN ; Make sure FN follows - PUSH DE ; Save function pointer address - CALL EVLPAR ; Evaluate expression in "()" - CALL TSTNUM ; Make sure numeric result - EX (SP),HL ; Save code str , Get FN ptr - LD E,(HL) ; Get LSB of FN code string - INC HL - LD D,(HL) ; Get MSB of FN code string - INC HL - LD A,D ; And function DEFined? - OR E - JP Z,UFERR ; No - ?UF Error - LD A,(HL) ; Get LSB of argument address - INC HL - LD H,(HL) ; Get MSB of argument address - LD L,A ; HL = Arg variable address - PUSH HL ; Save it - LD HL,(FNRGNM) ; Get old argument name - EX (SP),HL ; ; Save old , Get new - LD (FNRGNM),HL ; Set new argument name - LD HL,(FNARG+2) ; Get LSB,NLSB of old arg value - PUSH HL ; Save it - LD HL,(FNARG) ; Get MSB,EXP of old arg value - PUSH HL ; Save it - LD HL,FNARG ; HL = Value of argument - PUSH DE ; Save FN code string address - CALL FPTHL ; Move FPREG to argument - POP HL ; Get FN code string address - CALL GETNUM ; Get value from function - DEC HL ; DEC 'cos GETCHR INCs - CALL GETCHR ; Get next character - JP NZ,SNERR ; Bad character in FN - Error - POP HL ; Get MSB,EXP of old arg - LD (FNARG),HL ; Restore it - POP HL ; Get LSB,NLSB of old arg - LD (FNARG+2),HL ; Restore it - POP HL ; Get name of old arg - LD (FNRGNM),HL ; Restore it - POP HL ; Restore code string address - RET - -IDTEST: PUSH HL ; Save code string address - LD HL,(LINEAT) ; Get current line number - INC HL ; -1 means direct statement - LD A,H - OR L - POP HL ; Restore code string address - RET NZ ; Return if in program - LD E,ID ; ?ID Error - JP ERROR - -CHEKFN: CALL CHKSYN ; Make sure FN follows - .BYTE ZFN ; "FN" token - LD A,80H - LD (FORFLG),A ; Flag FN name to find - OR (HL) ; FN name has bit 7 set - LD B,A ; in first byte of name - CALL GTFNAM ; Get FN name - JP TSTNUM ; Make sure numeric function - -STR: CALL TSTNUM ; Make sure it's a number - CALL NUMASC ; Turn number into text -STR1: CALL CRTST ; Create string entry for it - CALL GSTRCU ; Current string to pool - LD BC,TOPOOL ; Save in string pool - PUSH BC ; Save address on stack - -SAVSTR: LD A,(HL) ; Get string length - INC HL - INC HL - PUSH HL ; Save pointer to string - CALL TESTR ; See if enough string space - POP HL ; Restore pointer to string - LD C,(HL) ; Get LSB of address - INC HL - LD B,(HL) ; Get MSB of address - CALL CRTMST ; Create string entry - PUSH HL ; Save pointer to MSB of addr - LD L,A ; Length of string - CALL TOSTRA ; Move to string area - POP DE ; Restore pointer to MSB - RET - -MKTMST: CALL TESTR ; See if enough string space -CRTMST: LD HL,TMPSTR ; Temporary string - PUSH HL ; Save it - LD (HL),A ; Save length of string - INC HL -SVSTAD: INC HL - LD (HL),E ; Save LSB of address - INC HL - LD (HL),D ; Save MSB of address - POP HL ; Restore pointer - RET - -CRTST: DEC HL ; DEC - INCed after -QTSTR: LD B,'"' ; Terminating quote - LD D,B ; Quote to D -DTSTR: PUSH HL ; Save start - LD C,-1 ; Set counter to -1 -QTSTLP: INC HL ; Move on - LD A,(HL) ; Get byte - INC C ; Count bytes - OR A ; End of line? - JP Z,CRTSTE ; Yes - Create string entry - CP D ; Terminator D found? - JP Z,CRTSTE ; Yes - Create string entry - CP B ; Terminator B found? - JP NZ,QTSTLP ; No - Keep looking -CRTSTE: CP '"' ; End with '"'? - CALL Z,GETCHR ; Yes - Get next character - EX (SP),HL ; Starting quote - INC HL ; First byte of string - EX DE,HL ; To DE - LD A,C ; Get length - CALL CRTMST ; Create string entry -TSTOPL: LD DE,TMPSTR ; Temporary string - LD HL,(TMSTPT) ; Temporary string pool pointer - LD (FPREG),HL ; Save address of string ptr - LD A,1 - LD (TYPE),A ; Set type to string - CALL DETHL4 ; Move string to pool - CALL CPDEHL ; Out of string pool? - LD (TMSTPT),HL ; Save new pointer - POP HL ; Restore code string address - LD A,(HL) ; Get next code byte - RET NZ ; Return if pool OK - LD E,ST ; ?ST Error - JP ERROR ; String pool overflow - -PRNUMS: INC HL ; Skip leading space -PRS: CALL CRTST ; Create string entry for it -PRS1: CALL GSTRCU ; Current string to pool - CALL LOADFP ; Move string block to BCDE - INC E ; Length + 1 -PRSLP: DEC E ; Count characters - RET Z ; End of string - LD A,(BC) ; Get byte to output - CALL OUTC ; Output character in A - CP CR ; Return? - CALL Z,DONULL ; Yes - Do nulls - INC BC ; Next byte in string - JP PRSLP ; More characters to output - -TESTR: OR A ; Test if enough room - .BYTE 0EH ; No garbage collection done -GRBDON: POP AF ; Garbage collection done - PUSH AF ; Save status - LD HL,(STRSPC) ; Bottom of string space in use - EX DE,HL ; To DE - LD HL,(STRBOT) ; Bottom of string area - CPL ; Negate length (Top down) - LD C,A ; -Length to BC - LD B,-1 ; BC = -ve length of string - ADD HL,BC ; Add to bottom of space in use - INC HL ; Plus one for 2's complement - CALL CPDEHL ; Below string RAM area? - JP C,TESTOS ; Tidy up if not done else err - LD (STRBOT),HL ; Save new bottom of area - INC HL ; Point to first byte of string - EX DE,HL ; Address to DE -POPAF: POP AF ; Throw away status push - RET - -TESTOS: POP AF ; Garbage collect been done? - LD E,OS ; ?OS Error - JP Z,ERROR ; Yes - Not enough string apace - CP A ; Flag garbage collect done - PUSH AF ; Save status - LD BC,GRBDON ; Garbage collection done - PUSH BC ; Save for RETurn -GARBGE: LD HL,(LSTRAM) ; Get end of RAM pointer -GARBLP: LD (STRBOT),HL ; Reset string pointer - LD HL,0 - PUSH HL ; Flag no string found - LD HL,(STRSPC) ; Get bottom of string space - PUSH HL ; Save bottom of string space - LD HL,TMSTPL ; Temporary string pool -GRBLP: EX DE,HL - LD HL,(TMSTPT) ; Temporary string pool pointer - EX DE,HL - CALL CPDEHL ; Temporary string pool done? - LD BC,GRBLP ; Loop until string pool done - JP NZ,STPOOL ; No - See if in string area - LD HL,(PROGND) ; Start of simple variables -SMPVAR: EX DE,HL - LD HL,(VAREND) ; End of simple variables - EX DE,HL - CALL CPDEHL ; All simple strings done? - JP Z,ARRLP ; Yes - Do string arrays - LD A,(HL) ; Get type of variable - INC HL - INC HL - OR A ; "S" flag set if string - CALL STRADD ; See if string in string area - JP SMPVAR ; Loop until simple ones done - -GNXARY: POP BC ; Scrap address of this array -ARRLP: EX DE,HL - LD HL,(ARREND) ; End of string arrays - EX DE,HL - CALL CPDEHL ; All string arrays done? - JP Z,SCNEND ; Yes - Move string if found - CALL LOADFP ; Get array name to BCDE - LD A,E ; Get type of array - PUSH HL ; Save address of num of dim'ns - ADD HL,BC ; Start of next array - OR A ; Test type of array - JP P,GNXARY ; Numeric array - Ignore it - LD (CUROPR),HL ; Save address of next array - POP HL ; Get address of num of dim'ns - LD C,(HL) ; BC = Number of dimensions - LD B,0 - ADD HL,BC ; Two bytes per dimension size - ADD HL,BC - INC HL ; Plus one for number of dim'ns -GRBARY: EX DE,HL - LD HL,(CUROPR) ; Get address of next array - EX DE,HL - CALL CPDEHL ; Is this array finished? - JP Z,ARRLP ; Yes - Get next one - LD BC,GRBARY ; Loop until array all done -STPOOL: PUSH BC ; Save return address - OR 80H ; Flag string type -STRADD: LD A,(HL) ; Get string length - INC HL - INC HL - LD E,(HL) ; Get LSB of string address - INC HL - LD D,(HL) ; Get MSB of string address - INC HL - RET P ; Not a string - Return - OR A ; Set flags on string length - RET Z ; Null string - Return - LD B,H ; Save variable pointer - LD C,L - LD HL,(STRBOT) ; Bottom of new area - CALL CPDEHL ; String been done? - LD H,B ; Restore variable pointer - LD L,C - RET C ; String done - Ignore - POP HL ; Return address - EX (SP),HL ; Lowest available string area - CALL CPDEHL ; String within string area? - EX (SP),HL ; Lowest available string area - PUSH HL ; Re-save return address - LD H,B ; Restore variable pointer - LD L,C - RET NC ; Outside string area - Ignore - POP BC ; Get return , Throw 2 away - POP AF ; - POP AF ; - PUSH HL ; Save variable pointer - PUSH DE ; Save address of current - PUSH BC ; Put back return address - RET ; Go to it - -SCNEND: POP DE ; Addresses of strings - POP HL ; - LD A,L ; HL = 0 if no more to do - OR H - RET Z ; No more to do - Return - DEC HL - LD B,(HL) ; MSB of address of string - DEC HL - LD C,(HL) ; LSB of address of string - PUSH HL ; Save variable address - DEC HL - DEC HL - LD L,(HL) ; HL = Length of string - LD H,0 - ADD HL,BC ; Address of end of string+1 - LD D,B ; String address to DE - LD E,C - DEC HL ; Last byte in string - LD B,H ; Address to BC - LD C,L - LD HL,(STRBOT) ; Current bottom of string area - CALL MOVSTR ; Move string to new address - POP HL ; Restore variable address - LD (HL),C ; Save new LSB of address - INC HL - LD (HL),B ; Save new MSB of address - LD L,C ; Next string area+1 to HL - LD H,B - DEC HL ; Next string area address - JP GARBLP ; Look for more strings - -CONCAT: PUSH BC ; Save prec' opr & code string - PUSH HL ; - LD HL,(FPREG) ; Get first string - EX (SP),HL ; Save first string - CALL OPRND ; Get second string - EX (SP),HL ; Restore first string - CALL TSTSTR ; Make sure it's a string - LD A,(HL) ; Get length of second string - PUSH HL ; Save first string - LD HL,(FPREG) ; Get second string - PUSH HL ; Save second string - ADD A,(HL) ; Add length of second string - LD E,LS ; ?LS Error - JP C,ERROR ; String too long - Error - CALL MKTMST ; Make temporary string - POP DE ; Get second string to DE - CALL GSTRDE ; Move to string pool if needed - EX (SP),HL ; Get first string - CALL GSTRHL ; Move to string pool if needed - PUSH HL ; Save first string - LD HL,(TMPSTR+2) ; Temporary string address - EX DE,HL ; To DE - CALL SSTSA ; First string to string area - CALL SSTSA ; Second string to string area - LD HL,EVAL2 ; Return to evaluation loop - EX (SP),HL ; Save return,get code string - PUSH HL ; Save code string address - JP TSTOPL ; To temporary string to pool - -SSTSA: POP HL ; Return address - EX (SP),HL ; Get string block,save return - LD A,(HL) ; Get length of string - INC HL - INC HL - LD C,(HL) ; Get LSB of string address - INC HL - LD B,(HL) ; Get MSB of string address - LD L,A ; Length to L -TOSTRA: INC L ; INC - DECed after -TSALP: DEC L ; Count bytes moved - RET Z ; End of string - Return - LD A,(BC) ; Get source - LD (DE),A ; Save destination - INC BC ; Next source - INC DE ; Next destination - JP TSALP ; Loop until string moved - -GETSTR: CALL TSTSTR ; Make sure it's a string -GSTRCU: LD HL,(FPREG) ; Get current string -GSTRHL: EX DE,HL ; Save DE -GSTRDE: CALL BAKTMP ; Was it last tmp-str? - EX DE,HL ; Restore DE - RET NZ ; No - Return - PUSH DE ; Save string - LD D,B ; String block address to DE - LD E,C - DEC DE ; Point to length - LD C,(HL) ; Get string length - LD HL,(STRBOT) ; Current bottom of string area - CALL CPDEHL ; Last one in string area? - JP NZ,POPHL ; No - Return - LD B,A ; Clear B (A=0) - ADD HL,BC ; Remove string from str' area - LD (STRBOT),HL ; Save new bottom of str' area -POPHL: POP HL ; Restore string - RET - -BAKTMP: LD HL,(TMSTPT) ; Get temporary string pool top - DEC HL ; Back - LD B,(HL) ; Get MSB of address - DEC HL ; Back - LD C,(HL) ; Get LSB of address - DEC HL ; Back - DEC HL ; Back - CALL CPDEHL ; String last in string pool? - RET NZ ; Yes - Leave it - LD (TMSTPT),HL ; Save new string pool top - RET - -LEN: LD BC,PASSA ; To return integer A - PUSH BC ; Save address -GETLEN: CALL GETSTR ; Get string and its length - XOR A - LD D,A ; Clear D - LD (TYPE),A ; Set type to numeric - LD A,(HL) ; Get length of string - OR A ; Set status flags - RET - -ASC: LD BC,PASSA ; To return integer A - PUSH BC ; Save address -GTFLNM: CALL GETLEN ; Get length of string - JP Z,FCERR ; Null string - Error - INC HL - INC HL - LD E,(HL) ; Get LSB of address - INC HL - LD D,(HL) ; Get MSB of address - LD A,(DE) ; Get first byte of string - RET - -CHR: LD A,1 ; One character string - CALL MKTMST ; Make a temporary string - CALL MAKINT ; Make it integer A - LD HL,(TMPSTR+2) ; Get address of string - LD (HL),E ; Save character -TOPOOL: POP BC ; Clean up stack - JP TSTOPL ; Temporary string to pool - -LEFT: CALL LFRGNM ; Get number and ending ")" - XOR A ; Start at first byte in string -RIGHT1: EX (SP),HL ; Save code string,Get string - LD C,A ; Starting position in string -MID1: PUSH HL ; Save string block address - LD A,(HL) ; Get length of string - CP B ; Compare with number given - JP C,ALLFOL ; All following bytes required - LD A,B ; Get new length - .BYTE 11H ; Skip "LD C,0" -ALLFOL: LD C,0 ; First byte of string - PUSH BC ; Save position in string - CALL TESTR ; See if enough string space - POP BC ; Get position in string - POP HL ; Restore string block address - PUSH HL ; And re-save it - INC HL - INC HL - LD B,(HL) ; Get LSB of address - INC HL - LD H,(HL) ; Get MSB of address - LD L,B ; HL = address of string - LD B,0 ; BC = starting address - ADD HL,BC ; Point to that byte - LD B,H ; BC = source string - LD C,L - CALL CRTMST ; Create a string entry - LD L,A ; Length of new string - CALL TOSTRA ; Move string to string area - POP DE ; Clear stack - CALL GSTRDE ; Move to string pool if needed - JP TSTOPL ; Temporary string to pool - -RIGHT: CALL LFRGNM ; Get number and ending ")" - POP DE ; Get string length - PUSH DE ; And re-save - LD A,(DE) ; Get length - SUB B ; Move back N bytes - JP RIGHT1 ; Go and get sub-string - -MID: EX DE,HL ; Get code string address - LD A,(HL) ; Get next byte ',' or ")" - CALL MIDNUM ; Get number supplied - INC B ; Is it character zero? - DEC B - JP Z,FCERR ; Yes - Error - PUSH BC ; Save starting position - LD E,255 ; All of string - CP ')' ; Any length given? - JP Z,RSTSTR ; No - Rest of string - CALL CHKSYN ; Make sure ',' follows - .BYTE ',' - CALL GETINT ; Get integer 0-255 -RSTSTR: CALL CHKSYN ; Make sure ")" follows - .BYTE ")" - POP AF ; Restore starting position - EX (SP),HL ; Get string,8ave code string - LD BC,MID1 ; Continuation of MID$ routine - PUSH BC ; Save for return - DEC A ; Starting position-1 - CP (HL) ; Compare with length - LD B,0 ; Zero bytes length - RET NC ; Null string if start past end - LD C,A ; Save starting position-1 - LD A,(HL) ; Get length of string - SUB C ; Subtract start - CP E ; Enough string for it? - LD B,A ; Save maximum length available - RET C ; Truncate string if needed - LD B,E ; Set specified length - RET ; Go and create string - -VAL: CALL GETLEN ; Get length of string - JP Z,RESZER ; Result zero - LD E,A ; Save length - INC HL - INC HL - LD A,(HL) ; Get LSB of address - INC HL - LD H,(HL) ; Get MSB of address - LD L,A ; HL = String address - PUSH HL ; Save string address - ADD HL,DE - LD B,(HL) ; Get end of string+1 byte - LD (HL),D ; Zero it to terminate - EX (SP),HL ; Save string end,get start - PUSH BC ; Save end+1 byte - LD A,(HL) ; Get starting byte - CP '$' ; Hex number indicated? [function added] - JP NZ,VAL1 - CALL HEXTFP ; Convert Hex to FPREG - JR VAL3 +GETNUM: CALL EVAL ; Get a numeric expression +TSTNUM: .BYTE 0F6H ; Clear carry (numeric) +TSTSTR: SCF ; Set carry (string) +CHKTYP: LD A,(TYPE) ; Check types match + ADC A,A ; Expected + actual + OR A ; Clear carry , set parity + RET PE ; Even parity - Types match + JP TMERR ; Different types - Error + +OPNPAR: CALL CHKSYN ; Make sure "(" follows + .BYTE "(" +EVAL: DEC HL ; Evaluate expression & save + LD D,0 ; Precedence value +EVAL1: PUSH DE ; Save precedence + LD C,1 + CALL CHKSTK ; Check for 1 level of stack + CALL OPRND ; Get next expression value +EVAL2: LD (NXTOPR),HL ; Save address of next operator +EVAL3: LD HL,(NXTOPR) ; Restore address of next opr + POP BC ; Precedence value and operator + LD A,B ; Get precedence value + CP 78H ; "AND" or "OR" ? + CALL NC,TSTNUM ; No - Make sure it's a number + LD A,(HL) ; Get next operator / function + LD D,0 ; Clear Last relation +RLTLP: SUB ZGTR ; ">" Token + JP C,FOPRND ; + - * / ^ AND OR - Test it + CP ZLTH+1-ZGTR ; < = > + JP NC,FOPRND ; Function - Call it + CP ZEQUAL-ZGTR ; "=" + RLA ; <- Test for legal + XOR D ; <- combinations of < = > + CP D ; <- by combining last token + LD D,A ; <- with current one + JP C,SNERR ; Error if "<<' '==" or ">>" + LD (CUROPR),HL ; Save address of current token + CALL GETCHR ; Get next character + JP RLTLP ; Treat the two as one + +FOPRND: LD A,D ; < = > found ? + OR A + JP NZ,TSTRED ; Yes - Test for reduction + LD A,(HL) ; Get operator token + LD (CUROPR),HL ; Save operator address + SUB ZPLUS ; Operator or function? + RET C ; Neither - Exit + CP ZOR+1-ZPLUS ; Is it + - * / ^ AND OR ? + RET NC ; No - Exit + LD E,A ; Coded operator + LD A,(TYPE) ; Get data type + DEC A ; FF = numeric , 00 = string + OR E ; Combine with coded operator + LD A,E ; Get coded operator + JP Z,CONCAT ; String concatenation + RLCA ; Times 2 + ADD A,E ; Times 3 + LD E,A ; To DE (D is 0) + LD HL,PRITAB ; Precedence table + ADD HL,DE ; To the operator concerned + LD A,B ; Last operator precedence + LD D,(HL) ; Get evaluation precedence + CP D ; Compare with eval precedence + RET NC ; Exit if higher precedence + INC HL ; Point to routine address + CALL TSTNUM ; Make sure it's a number + +STKTHS: PUSH BC ; Save last precedence & token + LD BC,EVAL3 ; Where to go on prec' break + PUSH BC ; Save on stack for return + LD B,E ; Save operator + LD C,D ; Save precedence + CALL STAKFP ; Move value to stack + LD E,B ; Restore operator + LD D,C ; Restore precedence + LD C,(HL) ; Get LSB of routine address + INC HL + LD B,(HL) ; Get MSB of routine address + INC HL + PUSH BC ; Save routine address + LD HL,(CUROPR) ; Address of current operator + JP EVAL1 ; Loop until prec' break + +OPRND: XOR A ; Get operand routine + LD (TYPE),A ; Set numeric expected + CALL GETCHR ; Get next character + LD E,MO ; ?MO Error + JP Z,ERROR ; No operand - Error + JP C,ASCTFP ; Number - Get value + CALL CHKLTR ; See if a letter + JP NC,CONVAR ; Letter - Find variable + CP '&' ; &H = HEX, &B = BINARY + JR NZ, NOTAMP + CALL GETCHR ; Get next character + CP 'H' ; Hex number indicated? [function added] + JP Z,HEXTFP ; Convert Hex to FPREG + CP 'B' ; Binary number indicated? [function added] + JP Z,BINTFP ; Convert Bin to FPREG + LD E,SN ; If neither then a ?SN Error + JP Z,ERROR ; +NOTAMP: CP ZPLUS ; '+' Token ? + JP Z,OPRND ; Yes - Look for operand + CP '.' ; '.' ? + JP Z,ASCTFP ; Yes - Create FP number + CP ZMINUS ; '-' Token ? + JP Z,MINUS ; Yes - Do minus + CP '"' ; Literal string ? + JP Z,QTSTR ; Get string terminated by '"' + CP ZNOT ; "NOT" Token ? + JP Z,EVNOT ; Yes - Eval NOT expression + CP ZFN ; "FN" Token ? + JP Z,DOFN ; Yes - Do FN routine + SUB ZSGN ; Is it a function? + JP NC,FNOFST ; Yes - Evaluate function +EVLPAR: CALL OPNPAR ; Evaluate expression in "()" + CALL CHKSYN ; Make sure ")" follows + .BYTE ")" + RET + +MINUS: LD D,7DH ; '-' precedence + CALL EVAL1 ; Evaluate until prec' break + LD HL,(NXTOPR) ; Get next operator address + PUSH HL ; Save next operator address + CALL INVSGN ; Negate value +RETNUM: CALL TSTNUM ; Make sure it's a number + POP HL ; Restore next operator address + RET + +CONVAR: CALL GETVAR ; Get variable address to DE +FRMEVL: PUSH HL ; Save code string address + EX DE,HL ; Variable address to HL + LD (FPREG),HL ; Save address of variable + LD A,(TYPE) ; Get type + OR A ; Numeric? + CALL Z,PHLTFP ; Yes - Move contents to FPREG + POP HL ; Restore code string address + RET + +FNOFST: LD B,0 ; Get address of function + RLCA ; Double function offset + LD C,A ; BC = Offset in function table + PUSH BC ; Save adjusted token value + CALL GETCHR ; Get next character + LD A,C ; Get adjusted token value +#IF VDUGFX + CP 2*(ZPOINT-ZSGN) ; Adjusted "POINT" token? + JP Z,POINT ; Yes - Do "POINT" (not POINTB) +#ENDIF + CP 2*(ZLEFT-ZSGN)-1; Adj' LEFT$,RIGHT$ or MID$ ? + JP C,FNVAL ; No - Do function + CALL OPNPAR ; Evaluate expression (X,... + CALL CHKSYN ; Make sure ',' follows + .BYTE ',' + CALL TSTSTR ; Make sure it's a string + EX DE,HL ; Save code string address + LD HL,(FPREG) ; Get address of string + EX (SP),HL ; Save address of string + PUSH HL ; Save adjusted token value + EX DE,HL ; Restore code string address + CALL GETINT ; Get integer 0-255 + EX DE,HL ; Save code string address + EX (SP),HL ; Save integer,HL = adj' token + JP GOFUNC ; Jump to string function + +FNVAL: CALL EVLPAR ; Evaluate expression + EX (SP),HL ; HL = Adjusted token value + LD DE,RETNUM ; Return number from function + PUSH DE ; Save on stack +GOFUNC: LD BC,FNCTAB ; Function routine addresses + ADD HL,BC ; Point to right address + LD C,(HL) ; Get LSB of address + INC HL + LD H,(HL) ; Get MSB of address + LD L,C ; Address to HL + JP (HL) ; Jump to function + +SGNEXP: DEC D ; Dee to flag negative exponent + CP ZMINUS ; '-' token ? + RET Z ; Yes - Return + CP '-' ; '-' ASCII ? + RET Z ; Yes - Return + INC D ; Inc to flag positive exponent + CP '+' ; '+' ASCII ? + RET Z ; Yes - Return + CP ZPLUS ; '+' token ? + RET Z ; Yes - Return + DEC HL ; DEC 'cos GETCHR INCs + RET ; Return "NZ" + +POR: .BYTE 0F6H ; Flag "OR" +PAND: XOR A ; Flag "AND" + PUSH AF ; Save "AND" / "OR" flag + CALL TSTNUM ; Make sure it's a number + CALL DEINT ; Get integer -32768 to 32767 + POP AF ; Restore "AND" / "OR" flag + EX DE,HL ; <- Get last + POP BC ; <- value + EX (SP),HL ; <- from + EX DE,HL ; <- stack + CALL FPBCDE ; Move last value to FPREG + PUSH AF ; Save "AND" / "OR" flag + CALL DEINT ; Get integer -32768 to 32767 + POP AF ; Restore "AND" / "OR" flag + POP BC ; Get value + LD A,C ; Get LSB + LD HL,ACPASS ; Address of save AC as current + JP NZ,POR1 ; Jump if OR + AND E ; "AND" LSBs + LD C,A ; Save LSB + LD A,B ; Get MBS + AND D ; "AND" MSBs + JP (HL) ; Save AC as current (ACPASS) + +POR1: OR E ; "OR" LSBs + LD C,A ; Save LSB + LD A,B ; Get MSB + OR D ; "OR" MSBs + JP (HL) ; Save AC as current (ACPASS) + +TSTRED: LD HL,CMPLOG ; Logical compare routine + LD A,(TYPE) ; Get data type + RRA ; Carry set = string + LD A,D ; Get last precedence value + RLA ; Times 2 plus carry + LD E,A ; To E + LD D,64H ; Relational precedence + LD A,B ; Get current precedence + CP D ; Compare with last + RET NC ; Eval if last was rel' or log' + JP STKTHS ; Stack this one and get next + +CMPLOG: .WORD CMPLG1 ; Compare two values / strings +CMPLG1: LD A,C ; Get data type + OR A + RRA + POP BC ; Get last expression to BCDE + POP DE + PUSH AF ; Save status + CALL CHKTYP ; Check that types match + LD HL,CMPRES ; Result to comparison + PUSH HL ; Save for RETurn + JP Z,CMPNUM ; Compare values if numeric + XOR A ; Compare two strings + LD (TYPE),A ; Set type to numeric + PUSH DE ; Save string name + CALL GSTRCU ; Get current string + LD A,(HL) ; Get length of string + INC HL + INC HL + LD C,(HL) ; Get LSB of address + INC HL + LD B,(HL) ; Get MSB of address + POP DE ; Restore string name + PUSH BC ; Save address of string + PUSH AF ; Save length of string + CALL GSTRDE ; Get second string + CALL LOADFP ; Get address of second string + POP AF ; Restore length of string 1 + LD D,A ; Length to D + POP HL ; Restore address of string 1 +CMPSTR: LD A,E ; Bytes of string 2 to do + OR D ; Bytes of string 1 to do + RET Z ; Exit if all bytes compared + LD A,D ; Get bytes of string 1 to do + SUB 1 + RET C ; Exit if end of string 1 + XOR A + CP E ; Bytes of string 2 to do + INC A + RET NC ; Exit if end of string 2 + DEC D ; Count bytes in string 1 + DEC E ; Count bytes in string 2 + LD A,(BC) ; Byte in string 2 + CP (HL) ; Compare to byte in string 1 + INC HL ; Move up string 1 + INC BC ; Move up string 2 + JP Z,CMPSTR ; Same - Try next bytes + CCF ; Flag difference (">" or "<") + JP FLGDIF ; "<" gives -1 , ">" gives +1 + +CMPRES: INC A ; Increment current value + ADC A,A ; Double plus carry + POP BC ; Get other value + AND B ; Combine them + ADD A,-1 ; Carry set if different + SBC A,A ; 00 - Equal , FF - Different + JP FLGREL ; Set current value & continue + +EVNOT: LD D,5AH ; Precedence value for "NOT" + CALL EVAL1 ; Eval until precedence break + CALL TSTNUM ; Make sure it's a number + CALL DEINT ; Get integer -32768 - 32767 + LD A,E ; Get LSB + CPL ; Invert LSB + LD C,A ; Save "NOT" of LSB + LD A,D ; Get MSB + CPL ; Invert MSB + CALL ACPASS ; Save AC as current + POP BC ; Clean up stack + JP EVAL3 ; Continue evaluation + +DIMRET: DEC HL ; DEC 'cos GETCHR INCs + CALL GETCHR ; Get next character + RET Z ; End of DIM statement + CALL CHKSYN ; Make sure ',' follows + .BYTE ',' +DIM: LD BC,DIMRET ; Return to "DIMRET" + PUSH BC ; Save on stack + .BYTE 0F6H ; Flag "Create" variable +GETVAR: XOR A ; Find variable address,to DE + LD (LCRFLG),A ; Set locate / create flag + LD B,(HL) ; Get First byte of name +GTFNAM: CALL CHKLTR ; See if a letter + JP C,SNERR ; ?SN Error if not a letter + XOR A + LD C,A ; Clear second byte of name + LD (TYPE),A ; Set type to numeric + CALL GETCHR ; Get next character + JP C,SVNAM2 ; Numeric - Save in name + CALL CHKLTR ; See if a letter + JP C,CHARTY ; Not a letter - Check type +SVNAM2: LD C,A ; Save second byte of name +ENDNAM: CALL GETCHR ; Get next character + JP C,ENDNAM ; Numeric - Get another + CALL CHKLTR ; See if a letter + JP NC,ENDNAM ; Letter - Get another +CHARTY: SUB '$' ; String variable? + JP NZ,NOTSTR ; No - Numeric variable + INC A ; A = 1 (string type) + LD (TYPE),A ; Set type to string + RRCA ; A = 80H , Flag for string + ADD A,C ; 2nd byte of name has bit 7 on + LD C,A ; Resave second byte on name + CALL GETCHR ; Get next character +NOTSTR: LD A,(FORFLG) ; Array name needed ? + DEC A + JP Z,ARLDSV ; Yes - Get array name + JP P,NSCFOR ; No array with "FOR" or "FN" + LD A,(HL) ; Get byte again + SUB '(' ; Subscripted variable? + JP Z,SBSCPT ; Yes - Sort out subscript + +NSCFOR: XOR A ; Simple variable + LD (FORFLG),A ; Clear "FOR" flag + PUSH HL ; Save code string address + LD D,B ; DE = Variable name to find + LD E,C + LD HL,(FNRGNM) ; FN argument name + CALL CPDEHL ; Is it the FN argument? + LD DE,FNARG ; Point to argument value + JP Z,POPHRT ; Yes - Return FN argument value + LD HL,(VAREND) ; End of variables + EX DE,HL ; Address of end of search + LD HL,(PROGND) ; Start of variables address +FNDVAR: CALL CPDEHL ; End of variable list table? + JP Z,CFEVAL ; Yes - Called from EVAL? + LD A,C ; Get second byte of name + SUB (HL) ; Compare with name in list + INC HL ; Move on to first byte + JP NZ,FNTHR ; Different - Find another + LD A,B ; Get first byte of name + SUB (HL) ; Compare with name in list +FNTHR: INC HL ; Move on to LSB of value + JP Z,RETADR ; Found - Return address + INC HL ; <- Skip + INC HL ; <- over + INC HL ; <- F.P. + INC HL ; <- value + JP FNDVAR ; Keep looking + +CFEVAL: POP HL ; Restore code string address + EX (SP),HL ; Get return address + PUSH DE ; Save address of variable + LD DE,FRMEVL ; Return address in EVAL + CALL CPDEHL ; Called from EVAL ? + POP DE ; Restore address of variable + JP Z,RETNUL ; Yes - Return null variable + EX (SP),HL ; Put back return + PUSH HL ; Save code string address + PUSH BC ; Save variable name + LD BC,6 ; 2 byte name plus 4 byte data + LD HL,(ARREND) ; End of arrays + PUSH HL ; Save end of arrays + ADD HL,BC ; Move up 6 bytes + POP BC ; Source address in BC + PUSH HL ; Save new end address + CALL MOVUP ; Move arrays up + POP HL ; Restore new end address + LD (ARREND),HL ; Set new end address + LD H,B ; End of variables to HL + LD L,C + LD (VAREND),HL ; Set new end address + +ZEROLP: DEC HL ; Back through to zero variable + LD (HL),0 ; Zero byte in variable + CALL CPDEHL ; Done them all? + JP NZ,ZEROLP ; No - Keep on going + POP DE ; Get variable name + LD (HL),E ; Store second character + INC HL + LD (HL),D ; Store first character + INC HL +RETADR: EX DE,HL ; Address of variable in DE + POP HL ; Restore code string address + RET + +RETNUL: LD (FPEXP),A ; Set result to zero + LD HL,ZERBYT ; Also set a null string + LD (FPREG),HL ; Save for EVAL + POP HL ; Restore code string address + RET + +SBSCPT: PUSH HL ; Save code string address + LD HL,(LCRFLG) ; Locate/Create and Type + EX (SP),HL ; Save and get code string + LD D,A ; Zero number of dimensions +SCPTLP: PUSH DE ; Save number of dimensions + PUSH BC ; Save array name + CALL FPSINT ; Get subscript (0-32767) + POP BC ; Restore array name + POP AF ; Get number of dimensions + EX DE,HL + EX (SP),HL ; Save subscript value + PUSH HL ; Save LCRFLG and TYPE + EX DE,HL + INC A ; Count dimensions + LD D,A ; Save in D + LD A,(HL) ; Get next byte in code string + CP ',' ; Comma (more to come)? + JP Z,SCPTLP ; Yes - More subscripts + CALL CHKSYN ; Make sure ")" follows + .BYTE ")" + LD (NXTOPR),HL ; Save code string address + POP HL ; Get LCRFLG and TYPE + LD (LCRFLG),HL ; Restore Locate/create & type + LD E,0 ; Flag not CSAVE* or CLOAD* + PUSH DE ; Save number of dimensions (D) + .BYTE 11H ; Skip "PUSH HL" and "PUSH AF' + +ARLDSV: PUSH HL ; Save code string address + PUSH AF ; A = 00 , Flags set = Z,N + LD HL,(VAREND) ; Start of arrays + .BYTE 3EH ; Skip "ADD HL,DE" +FNDARY: ADD HL,DE ; Move to next array start + EX DE,HL + LD HL,(ARREND) ; End of arrays + EX DE,HL ; Current array pointer + CALL CPDEHL ; End of arrays found? + JP Z,CREARY ; Yes - Create array + LD A,(HL) ; Get second byte of name + CP C ; Compare with name given + INC HL ; Move on + JP NZ,NXTARY ; Different - Find next array + LD A,(HL) ; Get first byte of name + CP B ; Compare with name given +NXTARY: INC HL ; Move on + LD E,(HL) ; Get LSB of next array address + INC HL + LD D,(HL) ; Get MSB of next array address + INC HL + JP NZ,FNDARY ; Not found - Keep looking + LD A,(LCRFLG) ; Found Locate or Create it? + OR A + JP NZ,DDERR ; Create - ?DD Error + POP AF ; Locate - Get number of dim'ns + LD B,H ; BC Points to array dim'ns + LD C,L + JP Z,POPHRT ; Jump if array load/save + SUB (HL) ; Same number of dimensions? + JP Z,FINDEL ; Yes - Find element +BSERR: LD E,BS ; ?BS Error + JP ERROR ; Output error + +CREARY: LD DE,4 ; 4 Bytes per entry + POP AF ; Array to save or 0 dim'ns? + JP Z,FCERR ; Yes - ?FC Error + LD (HL),C ; Save second byte of name + INC HL + LD (HL),B ; Save first byte of name + INC HL + LD C,A ; Number of dimensions to C + CALL CHKSTK ; Check if enough memory + INC HL ; Point to number of dimensions + INC HL + LD (CUROPR),HL ; Save address of pointer + LD (HL),C ; Set number of dimensions + INC HL + LD A,(LCRFLG) ; Locate of Create? + RLA ; Carry set = Create + LD A,C ; Get number of dimensions +CRARLP: LD BC,10+1 ; Default dimension size 10 + JP NC,DEFSIZ ; Locate - Set default size + POP BC ; Get specified dimension size + INC BC ; Include zero element +DEFSIZ: LD (HL),C ; Save LSB of dimension size + INC HL + LD (HL),B ; Save MSB of dimension size + INC HL + PUSH AF ; Save num' of dim'ns an status + PUSH HL ; Save address of dim'n size + CALL MLDEBC ; Multiply DE by BC to find + EX DE,HL ; amount of mem needed (to DE) + POP HL ; Restore address of dimension + POP AF ; Restore number of dimensions + DEC A ; Count them + JP NZ,CRARLP ; Do next dimension if more + PUSH AF ; Save locate/create flag + LD B,D ; MSB of memory needed + LD C,E ; LSB of memory needed + EX DE,HL + ADD HL,DE ; Add bytes to array start + JP C,OMERR ; Too big - Error + CALL ENFMEM ; See if enough memory + LD (ARREND),HL ; Save new end of array + +ZERARY: DEC HL ; Back through array data + LD (HL),0 ; Set array element to zero + CALL CPDEHL ; All elements zeroed? + JP NZ,ZERARY ; No - Keep on going + INC BC ; Number of bytes + 1 + LD D,A ; A=0 + LD HL,(CUROPR) ; Get address of array + LD E,(HL) ; Number of dimensions + EX DE,HL ; To HL + ADD HL,HL ; Two bytes per dimension size + ADD HL,BC ; Add number of bytes + EX DE,HL ; Bytes needed to DE + DEC HL + DEC HL + LD (HL),E ; Save LSB of bytes needed + INC HL + LD (HL),D ; Save MSB of bytes needed + INC HL + POP AF ; Locate / Create? + JP C,ENDDIM ; A is 0 , End if create +FINDEL: LD B,A ; Find array element + LD C,A + LD A,(HL) ; Number of dimensions + INC HL + .BYTE 16H ; Skip "POP HL" +FNDELP: POP HL ; Address of next dim' size + LD E,(HL) ; Get LSB of dim'n size + INC HL + LD D,(HL) ; Get MSB of dim'n size + INC HL + EX (SP),HL ; Save address - Get index + PUSH AF ; Save number of dim'ns + CALL CPDEHL ; Dimension too large? + JP NC,BSERR ; Yes - ?BS Error + PUSH HL ; Save index + CALL MLDEBC ; Multiply previous by size + POP DE ; Index supplied to DE + ADD HL,DE ; Add index to pointer + POP AF ; Number of dimensions + DEC A ; Count them + LD B,H ; MSB of pointer + LD C,L ; LSB of pointer + JP NZ,FNDELP ; More - Keep going + ADD HL,HL ; 4 Bytes per element + ADD HL,HL + POP BC ; Start of array + ADD HL,BC ; Point to element + EX DE,HL ; Address of element to DE +ENDDIM: LD HL,(NXTOPR) ; Got code string address + RET + +FRE: LD HL,(ARREND) ; Start of free memory + EX DE,HL ; To DE + LD HL,0 ; End of free memory + ADD HL,SP ; Current stack value + LD A,(TYPE) ; Dummy argument type + OR A + JP Z,FRENUM ; Numeric - Free variable space + CALL GSTRCU ; Current string to pool + CALL GARBGE ; Garbage collection + LD HL,(STRSPC) ; Bottom of string space in use + EX DE,HL ; To DE + LD HL,(STRBOT) ; Bottom of string space +FRENUM: LD A,L ; Get LSB of end + SUB E ; Subtract LSB of beginning + LD C,A ; Save difference if C + LD A,H ; Get MSB of end + SBC A,D ; Subtract MSB of beginning +ACPASS: LD B,C ; Return integer AC +ABPASS: LD D,B ; Return integer AB + LD E,0 + LD HL,TYPE ; Point to type + LD (HL),E ; Set type to numeric + LD B,80H+16 ; 16 bit integer + JP RETINT ; Return the integr + +POS: LD A,(CURPOS) ; Get cursor position +PASSA: LD B,A ; Put A into AB + XOR A ; Zero A + JP ABPASS ; Return integer AB + +DEF: CALL CHEKFN ; Get "FN" and name + CALL IDTEST ; Test for illegal direct + LD BC,DATA ; To get next statement + PUSH BC ; Save address for RETurn + PUSH DE ; Save address of function ptr + CALL CHKSYN ; Make sure "(" follows + .BYTE "(" + CALL GETVAR ; Get argument variable name + PUSH HL ; Save code string address + EX DE,HL ; Argument address to HL + DEC HL + LD D,(HL) ; Get first byte of arg name + DEC HL + LD E,(HL) ; Get second byte of arg name + POP HL ; Restore code string address + CALL TSTNUM ; Make sure numeric argument + CALL CHKSYN ; Make sure ")" follows + .BYTE ")" + CALL CHKSYN ; Make sure "=" follows + .BYTE ZEQUAL ; "=" token + LD B,H ; Code string address to BC + LD C,L + EX (SP),HL ; Save code str , Get FN ptr + LD (HL),C ; Save LSB of FN code string + INC HL + LD (HL),B ; Save MSB of FN code string + JP SVSTAD ; Save address and do function + +DOFN: CALL CHEKFN ; Make sure FN follows + PUSH DE ; Save function pointer address + CALL EVLPAR ; Evaluate expression in "()" + CALL TSTNUM ; Make sure numeric result + EX (SP),HL ; Save code str , Get FN ptr + LD E,(HL) ; Get LSB of FN code string + INC HL + LD D,(HL) ; Get MSB of FN code string + INC HL + LD A,D ; And function DEFined? + OR E + JP Z,UFERR ; No - ?UF Error + LD A,(HL) ; Get LSB of argument address + INC HL + LD H,(HL) ; Get MSB of argument address + LD L,A ; HL = Arg variable address + PUSH HL ; Save it + LD HL,(FNRGNM) ; Get old argument name + EX (SP),HL ; ; Save old , Get new + LD (FNRGNM),HL ; Set new argument name + LD HL,(FNARG+2) ; Get LSB,NLSB of old arg value + PUSH HL ; Save it + LD HL,(FNARG) ; Get MSB,EXP of old arg value + PUSH HL ; Save it + LD HL,FNARG ; HL = Value of argument + PUSH DE ; Save FN code string address + CALL FPTHL ; Move FPREG to argument + POP HL ; Get FN code string address + CALL GETNUM ; Get value from function + DEC HL ; DEC 'cos GETCHR INCs + CALL GETCHR ; Get next character + JP NZ,SNERR ; Bad character in FN - Error + POP HL ; Get MSB,EXP of old arg + LD (FNARG),HL ; Restore it + POP HL ; Get LSB,NLSB of old arg + LD (FNARG+2),HL ; Restore it + POP HL ; Get name of old arg + LD (FNRGNM),HL ; Restore it + POP HL ; Restore code string address + RET + +IDTEST: PUSH HL ; Save code string address + LD HL,(LINEAT) ; Get current line number + INC HL ; -1 means direct statement + LD A,H + OR L + POP HL ; Restore code string address + RET NZ ; Return if in program + LD E,ID ; ?ID Error + JP ERROR + +CHEKFN: CALL CHKSYN ; Make sure FN follows + .BYTE ZFN ; "FN" token + LD A,80H + LD (FORFLG),A ; Flag FN name to find + OR (HL) ; FN name has bit 7 set + LD B,A ; in first byte of name + CALL GTFNAM ; Get FN name + JP TSTNUM ; Make sure numeric function + +STR: CALL TSTNUM ; Make sure it's a number + CALL NUMASC ; Turn number into text +STR1: CALL CRTST ; Create string entry for it + CALL GSTRCU ; Current string to pool + LD BC,TOPOOL ; Save in string pool + PUSH BC ; Save address on stack + +SAVSTR: LD A,(HL) ; Get string length + INC HL + INC HL + PUSH HL ; Save pointer to string + CALL TESTR ; See if enough string space + POP HL ; Restore pointer to string + LD C,(HL) ; Get LSB of address + INC HL + LD B,(HL) ; Get MSB of address + CALL CRTMST ; Create string entry + PUSH HL ; Save pointer to MSB of addr + LD L,A ; Length of string + CALL TOSTRA ; Move to string area + POP DE ; Restore pointer to MSB + RET + +MKTMST: CALL TESTR ; See if enough string space +CRTMST: LD HL,TMPSTR ; Temporary string + PUSH HL ; Save it + LD (HL),A ; Save length of string + INC HL +SVSTAD: INC HL + LD (HL),E ; Save LSB of address + INC HL + LD (HL),D ; Save MSB of address + POP HL ; Restore pointer + RET + +CRTST: DEC HL ; DEC - INCed after +QTSTR: LD B,'"' ; Terminating quote + LD D,B ; Quote to D +DTSTR: PUSH HL ; Save start + LD C,-1 ; Set counter to -1 +QTSTLP: INC HL ; Move on + LD A,(HL) ; Get byte + INC C ; Count bytes + OR A ; End of line? + JP Z,CRTSTE ; Yes - Create string entry + CP D ; Terminator D found? + JP Z,CRTSTE ; Yes - Create string entry + CP B ; Terminator B found? + JP NZ,QTSTLP ; No - Keep looking +CRTSTE: CP '"' ; End with '"'? + CALL Z,GETCHR ; Yes - Get next character + EX (SP),HL ; Starting quote + INC HL ; First byte of string + EX DE,HL ; To DE + LD A,C ; Get length + CALL CRTMST ; Create string entry +TSTOPL: LD DE,TMPSTR ; Temporary string + LD HL,(TMSTPT) ; Temporary string pool pointer + LD (FPREG),HL ; Save address of string ptr + LD A,1 + LD (TYPE),A ; Set type to string + CALL DETHL4 ; Move string to pool + CALL CPDEHL ; Out of string pool? + LD (TMSTPT),HL ; Save new pointer + POP HL ; Restore code string address + LD A,(HL) ; Get next code byte + RET NZ ; Return if pool OK + LD E,ST ; ?ST Error + JP ERROR ; String pool overflow + +PRNUMS: INC HL ; Skip leading space +PRS: CALL CRTST ; Create string entry for it +PRS1: CALL GSTRCU ; Current string to pool + CALL LOADFP ; Move string block to BCDE + INC E ; Length + 1 +PRSLP: DEC E ; Count characters + RET Z ; End of string + LD A,(BC) ; Get byte to output + CALL OUTC ; Output character in A + CP CR ; Return? + CALL Z,DONULL ; Yes - Do nulls + INC BC ; Next byte in string + JP PRSLP ; More characters to output + +TESTR: OR A ; Test if enough room + .BYTE 0EH ; No garbage collection done +GRBDON: POP AF ; Garbage collection done + PUSH AF ; Save status + LD HL,(STRSPC) ; Bottom of string space in use + EX DE,HL ; To DE + LD HL,(STRBOT) ; Bottom of string area + CPL ; Negate length (Top down) + LD C,A ; -Length to BC + LD B,-1 ; BC = -ve length of string + ADD HL,BC ; Add to bottom of space in use + INC HL ; Plus one for 2's complement + CALL CPDEHL ; Below string RAM area? + JP C,TESTOS ; Tidy up if not done else err + LD (STRBOT),HL ; Save new bottom of area + INC HL ; Point to first byte of string + EX DE,HL ; Address to DE +POPAF: POP AF ; Throw away status push + RET + +TESTOS: POP AF ; Garbage collect been done? + LD E,OS ; ?OS Error + JP Z,ERROR ; Yes - Not enough string apace + CP A ; Flag garbage collect done + PUSH AF ; Save status + LD BC,GRBDON ; Garbage collection done + PUSH BC ; Save for RETurn +GARBGE: LD HL,(LSTRAM) ; Get end of RAM pointer +GARBLP: LD (STRBOT),HL ; Reset string pointer + LD HL,0 + PUSH HL ; Flag no string found + LD HL,(STRSPC) ; Get bottom of string space + PUSH HL ; Save bottom of string space + LD HL,TMSTPL ; Temporary string pool +GRBLP: EX DE,HL + LD HL,(TMSTPT) ; Temporary string pool pointer + EX DE,HL + CALL CPDEHL ; Temporary string pool done? + LD BC,GRBLP ; Loop until string pool done + JP NZ,STPOOL ; No - See if in string area + LD HL,(PROGND) ; Start of simple variables +SMPVAR: EX DE,HL + LD HL,(VAREND) ; End of simple variables + EX DE,HL + CALL CPDEHL ; All simple strings done? + JP Z,ARRLP ; Yes - Do string arrays + LD A,(HL) ; Get type of variable + INC HL + INC HL + OR A ; "S" flag set if string + CALL STRADD ; See if string in string area + JP SMPVAR ; Loop until simple ones done + +GNXARY: POP BC ; Scrap address of this array +ARRLP: EX DE,HL + LD HL,(ARREND) ; End of string arrays + EX DE,HL + CALL CPDEHL ; All string arrays done? + JP Z,SCNEND ; Yes - Move string if found + CALL LOADFP ; Get array name to BCDE + LD A,E ; Get type of array + PUSH HL ; Save address of num of dim'ns + ADD HL,BC ; Start of next array + OR A ; Test type of array + JP P,GNXARY ; Numeric array - Ignore it + LD (CUROPR),HL ; Save address of next array + POP HL ; Get address of num of dim'ns + LD C,(HL) ; BC = Number of dimensions + LD B,0 + ADD HL,BC ; Two bytes per dimension size + ADD HL,BC + INC HL ; Plus one for number of dim'ns +GRBARY: EX DE,HL + LD HL,(CUROPR) ; Get address of next array + EX DE,HL + CALL CPDEHL ; Is this array finished? + JP Z,ARRLP ; Yes - Get next one + LD BC,GRBARY ; Loop until array all done +STPOOL: PUSH BC ; Save return address + OR 80H ; Flag string type +STRADD: LD A,(HL) ; Get string length + INC HL + INC HL + LD E,(HL) ; Get LSB of string address + INC HL + LD D,(HL) ; Get MSB of string address + INC HL + RET P ; Not a string - Return + OR A ; Set flags on string length + RET Z ; Null string - Return + LD B,H ; Save variable pointer + LD C,L + LD HL,(STRBOT) ; Bottom of new area + CALL CPDEHL ; String been done? + LD H,B ; Restore variable pointer + LD L,C + RET C ; String done - Ignore + POP HL ; Return address + EX (SP),HL ; Lowest available string area + CALL CPDEHL ; String within string area? + EX (SP),HL ; Lowest available string area + PUSH HL ; Re-save return address + LD H,B ; Restore variable pointer + LD L,C + RET NC ; Outside string area - Ignore + POP BC ; Get return , Throw 2 away + POP AF ; + POP AF ; + PUSH HL ; Save variable pointer + PUSH DE ; Save address of current + PUSH BC ; Put back return address + RET ; Go to it + +SCNEND: POP DE ; Addresses of strings + POP HL ; + LD A,L ; HL = 0 if no more to do + OR H + RET Z ; No more to do - Return + DEC HL + LD B,(HL) ; MSB of address of string + DEC HL + LD C,(HL) ; LSB of address of string + PUSH HL ; Save variable address + DEC HL + DEC HL + LD L,(HL) ; HL = Length of string + LD H,0 + ADD HL,BC ; Address of end of string+1 + LD D,B ; String address to DE + LD E,C + DEC HL ; Last byte in string + LD B,H ; Address to BC + LD C,L + LD HL,(STRBOT) ; Current bottom of string area + CALL MOVSTR ; Move string to new address + POP HL ; Restore variable address + LD (HL),C ; Save new LSB of address + INC HL + LD (HL),B ; Save new MSB of address + LD L,C ; Next string area+1 to HL + LD H,B + DEC HL ; Next string area address + JP GARBLP ; Look for more strings + +CONCAT: PUSH BC ; Save prec' opr & code string + PUSH HL + LD HL,(FPREG) ; Get first string + EX (SP),HL ; Save first string + CALL OPRND ; Get second string + EX (SP),HL ; Restore first string + CALL TSTSTR ; Make sure it's a string + LD A,(HL) ; Get length of second string + PUSH HL ; Save first string + LD HL,(FPREG) ; Get second string + PUSH HL ; Save second string + ADD A,(HL) ; Add length of second string + LD E,LS ; ?LS Error + JP C,ERROR ; String too long - Error + CALL MKTMST ; Make temporary string + POP DE ; Get second string to DE + CALL GSTRDE ; Move to string pool if needed + EX (SP),HL ; Get first string + CALL GSTRHL ; Move to string pool if needed + PUSH HL ; Save first string + LD HL,(TMPSTR+2) ; Temporary string address + EX DE,HL ; To DE + CALL SSTSA ; First string to string area + CALL SSTSA ; Second string to string area + LD HL,EVAL2 ; Return to evaluation loop + EX (SP),HL ; Save return,get code string + PUSH HL ; Save code string address + JP TSTOPL ; To temporary string to pool + +SSTSA: POP HL ; Return address + EX (SP),HL ; Get string block,save return + LD A,(HL) ; Get length of string + INC HL + INC HL + LD C,(HL) ; Get LSB of string address + INC HL + LD B,(HL) ; Get MSB of string address + LD L,A ; Length to L +TOSTRA: INC L ; INC - DECed after +TSALP: DEC L ; Count bytes moved + RET Z ; End of string - Return + LD A,(BC) ; Get source + LD (DE),A ; Save destination + INC BC ; Next source + INC DE ; Next destination + JP TSALP ; Loop until string moved + +GETSTR: CALL TSTSTR ; Make sure it's a string +GSTRCU: LD HL,(FPREG) ; Get current string +GSTRHL: EX DE,HL ; Save DE +GSTRDE: CALL BAKTMP ; Was it last tmp-str? + EX DE,HL ; Restore DE + RET NZ ; No - Return + PUSH DE ; Save string + LD D,B ; String block address to DE + LD E,C + DEC DE ; Point to length + LD C,(HL) ; Get string length + LD HL,(STRBOT) ; Current bottom of string area + CALL CPDEHL ; Last one in string area? + JP NZ,POPHL ; No - Return + LD B,A ; Clear B (A=0) + ADD HL,BC ; Remove string from str' area + LD (STRBOT),HL ; Save new bottom of str' area +POPHL: POP HL ; Restore string + RET + +BAKTMP: LD HL,(TMSTPT) ; Get temporary string pool top + DEC HL ; Back + LD B,(HL) ; Get MSB of address + DEC HL ; Back + LD C,(HL) ; Get LSB of address + DEC HL ; Back + DEC HL ; Back + CALL CPDEHL ; String last in string pool? + RET NZ ; Yes - Leave it + LD (TMSTPT),HL ; Save new string pool top + RET + +LEN: LD BC,PASSA ; To return integer A + PUSH BC ; Save address +GETLEN: CALL GETSTR ; Get string and its length + XOR A + LD D,A ; Clear D + LD (TYPE),A ; Set type to numeric + LD A,(HL) ; Get length of string + OR A ; Set status flags + RET + +ASC: LD BC,PASSA ; To return integer A + PUSH BC ; Save address +GTFLNM: CALL GETLEN ; Get length of string + JP Z,FCERR ; Null string - Error + INC HL + INC HL + LD E,(HL) ; Get LSB of address + INC HL + LD D,(HL) ; Get MSB of address + LD A,(DE) ; Get first byte of string + RET + +CHR: LD A,1 ; One character string + CALL MKTMST ; Make a temporary string + CALL MAKINT ; Make it integer A + LD HL,(TMPSTR+2) ; Get address of string + LD (HL),E ; Save character +TOPOOL: POP BC ; Clean up stack + JP TSTOPL ; Temporary string to pool + +LEFT: CALL LFRGNM ; Get number and ending ")" + XOR A ; Start at first byte in string +RIGHT1: EX (SP),HL ; Save code string,Get string + LD C,A ; Starting position in string +MID1: PUSH HL ; Save string block address + LD A,(HL) ; Get length of string + CP B ; Compare with number given + JP C,ALLFOL ; All following bytes required + LD A,B ; Get new length + .BYTE 11H ; Skip "LD C,0" +ALLFOL: LD C,0 ; First byte of string + PUSH BC ; Save position in string + CALL TESTR ; See if enough string space + POP BC ; Get position in string + POP HL ; Restore string block address + PUSH HL ; And re-save it + INC HL + INC HL + LD B,(HL) ; Get LSB of address + INC HL + LD H,(HL) ; Get MSB of address + LD L,B ; HL = address of string + LD B,0 ; BC = starting address + ADD HL,BC ; Point to that byte + LD B,H ; BC = source string + LD C,L + CALL CRTMST ; Create a string entry + LD L,A ; Length of new string + CALL TOSTRA ; Move string to string area + POP DE ; Clear stack + CALL GSTRDE ; Move to string pool if needed + JP TSTOPL ; Temporary string to pool + +RIGHT: CALL LFRGNM ; Get number and ending ")" + POP DE ; Get string length + PUSH DE ; And re-save + LD A,(DE) ; Get length + SUB B ; Move back N bytes + JP RIGHT1 ; Go and get sub-string + +MID: EX DE,HL ; Get code string address + LD A,(HL) ; Get next byte ',' or ")" + CALL MIDNUM ; Get number supplied + INC B ; Is it character zero? + DEC B + JP Z,FCERR ; Yes - Error + PUSH BC ; Save starting position + LD E,255 ; All of string + CP ')' ; Any length given? + JP Z,RSTSTR ; No - Rest of string + CALL CHKSYN ; Make sure ',' follows + .BYTE ',' + CALL GETINT ; Get integer 0-255 +RSTSTR: CALL CHKSYN ; Make sure ")" follows + .BYTE ")" + POP AF ; Restore starting position + EX (SP),HL ; Get string,8ave code string + LD BC,MID1 ; Continuation of MID$ routine + PUSH BC ; Save for return + DEC A ; Starting position-1 + CP (HL) ; Compare with length + LD B,0 ; Zero bytes length + RET NC ; Null string if start past end + LD C,A ; Save starting position-1 + LD A,(HL) ; Get length of string + SUB C ; Subtract start + CP E ; Enough string for it? + LD B,A ; Save maximum length available + RET C ; Truncate string if needed + LD B,E ; Set specified length + RET ; Go and create string + +VAL: CALL GETLEN ; Get length of string + JP Z,RESZER ; Result zero + LD E,A ; Save length + INC HL + INC HL + LD A,(HL) ; Get LSB of address + INC HL + LD H,(HL) ; Get MSB of address + LD L,A ; HL = String address + PUSH HL ; Save string address + ADD HL,DE + LD B,(HL) ; Get end of string+1 byte + LD (HL),D ; Zero it to terminate + EX (SP),HL ; Save string end,get start + PUSH BC ; Save end+1 byte + LD A,(HL) ; Get starting byte + CP '$' ; Hex number indicated? [function added] + JP NZ,VAL1 + CALL HEXTFP ; Convert Hex to FPREG + JR VAL3 VAL1: CP '%' ; Binary number indicated? [function added] - JP NZ,VAL2 - CALL BINTFP ; Convert Bin to FPREG - JR VAL3 -VAL2: CALL ASCTFP ; Convert ASCII string to FP -VAL3: POP BC ; Restore end+1 byte - POP HL ; Restore end+1 address - LD (HL),B ; Put back original byte - RET - -LFRGNM: EX DE,HL ; Code string address to HL - CALL CHKSYN ; Make sure ")" follows - .BYTE ")" -MIDNUM: POP BC ; Get return address - POP DE ; Get number supplied - PUSH BC ; Re-save return address - LD B,E ; Number to B - RET - -INP: CALL MAKINT ; Make it integer A - LD (INPORT),A ; Set input port - CALL INPSUB ; Get input from port - JP PASSA ; Return integer A - -POUT: CALL SETIO ; Set up port number - JP OUTSUB ; Output data and return - -WAIT: CALL SETIO ; Set up port number - PUSH AF ; Save AND mask - LD E,0 ; Assume zero if none given - DEC HL ; DEC 'cos GETCHR INCs - CALL GETCHR ; Get next character - JP Z,NOXOR ; No XOR byte given - CALL CHKSYN ; Make sure ',' follows - .BYTE ',' - CALL GETINT ; Get integer 0-255 to XOR with -NOXOR: POP BC ; Restore AND mask -WAITLP: CALL INPSUB ; Get input - XOR E ; Flip selected bits - AND B ; Result non-zero? - JP Z,WAITLP ; No = keep waiting - RET - -SETIO: CALL GETINT ; Get integer 0-255 - LD (INPORT),A ; Set input port - LD (OTPORT),A ; Set output port - CALL CHKSYN ; Make sure ',' follows - .BYTE ',' - JP GETINT ; Get integer 0-255 and return - -FNDNUM: CALL GETCHR ; Get next character -GETINT: CALL GETNUM ; Get a number from 0 to 255 -MAKINT: CALL DEPINT ; Make sure value 0 - 255 - LD A,D ; Get MSB of number - OR A ; Zero? - JP NZ,FCERR ; No - Error - DEC HL ; DEC 'cos GETCHR INCs - CALL GETCHR ; Get next character - LD A,E ; Get number to A - RET - -PEEK: CALL DEINT ; Get memory address - LD A,(DE) ; Get byte in memory - JP PASSA ; Return integer A - -POKE: CALL GETNUM ; Get memory address - CALL DEINT ; Get integer -32768 to 3276 - PUSH DE ; Save memory address - CALL CHKSYN ; Make sure ',' follows - .BYTE ',' - CALL GETINT ; Get integer 0-255 - POP DE ; Restore memory address - LD (DE),A ; Load it into memory - RET - -ROUND: LD HL,HALF ; Add 0.5 to FPREG -ADDPHL: CALL LOADFP ; Load FP at (HL) to BCDE - JP FPADD ; Add BCDE to FPREG - -SUBPHL: CALL LOADFP ; FPREG = -FPREG + number at HL - .BYTE 21H ; Skip "POP BC" and "POP DE" -PSUB: POP BC ; Get FP number from stack - POP DE -SUBCDE: CALL INVSGN ; Negate FPREG -FPADD: LD A,B ; Get FP exponent - OR A ; Is number zero? - RET Z ; Yes - Nothing to add - LD A,(FPEXP) ; Get FPREG exponent - OR A ; Is this number zero? - JP Z,FPBCDE ; Yes - Move BCDE to FPREG - SUB B ; BCDE number larger? - JP NC,NOSWAP ; No - Don't swap them - CPL ; Two's complement - INC A ; FP exponent - EX DE,HL - CALL STAKFP ; Put FPREG on stack - EX DE,HL - CALL FPBCDE ; Move BCDE to FPREG - POP BC ; Restore number from stack - POP DE -NOSWAP: CP 24+1 ; Second number insignificant? - RET NC ; Yes - First number is result - PUSH AF ; Save number of bits to scale - CALL SIGNS ; Set MSBs & sign of result - LD H,A ; Save sign of result - POP AF ; Restore scaling factor - CALL SCALE ; Scale BCDE to same exponent - OR H ; Result to be positive? - LD HL,FPREG ; Point to FPREG - JP P,MINCDE ; No - Subtract FPREG from CDE - CALL PLUCDE ; Add FPREG to CDE - JP NC,RONDUP ; No overflow - Round it up - INC HL ; Point to exponent - INC (HL) ; Increment it - JP Z,OVERR ; Number overflowed - Error - LD L,1 ; 1 bit to shift right - CALL SHRT1 ; Shift result right - JP RONDUP ; Round it up - -MINCDE: XOR A ; Clear A and carry - SUB B ; Negate exponent - LD B,A ; Re-save exponent - LD A,(HL) ; Get LSB of FPREG - SBC A, E ; Subtract LSB of BCDE - LD E,A ; Save LSB of BCDE - INC HL - LD A,(HL) ; Get NMSB of FPREG - SBC A,D ; Subtract NMSB of BCDE - LD D,A ; Save NMSB of BCDE - INC HL - LD A,(HL) ; Get MSB of FPREG - SBC A,C ; Subtract MSB of BCDE - LD C,A ; Save MSB of BCDE -CONPOS: CALL C,COMPL ; Overflow - Make it positive - -BNORM: LD L,B ; L = Exponent - LD H,E ; H = LSB - XOR A -BNRMLP: LD B,A ; Save bit count - LD A,C ; Get MSB - OR A ; Is it zero? - JP NZ,PNORM ; No - Do it bit at a time - LD C,D ; MSB = NMSB - LD D,H ; NMSB= LSB - LD H,L ; LSB = VLSB - LD L,A ; VLSB= 0 - LD A,B ; Get exponent - SUB 8 ; Count 8 bits - CP -24-8 ; Was number zero? - JP NZ,BNRMLP ; No - Keep normalising -RESZER: XOR A ; Result is zero -SAVEXP: LD (FPEXP),A ; Save result as zero - RET - -NORMAL: DEC B ; Count bits - ADD HL,HL ; Shift HL left - LD A,D ; Get NMSB - RLA ; Shift left with last bit - LD D,A ; Save NMSB - LD A,C ; Get MSB - ADC A,A ; Shift left with last bit - LD C,A ; Save MSB -PNORM: JP P,NORMAL ; Not done - Keep going - LD A,B ; Number of bits shifted - LD E,H ; Save HL in EB - LD B,L - OR A ; Any shifting done? - JP Z,RONDUP ; No - Round it up - LD HL,FPEXP ; Point to exponent - ADD A,(HL) ; Add shifted bits - LD (HL),A ; Re-save exponent - JP NC,RESZER ; Underflow - Result is zero - RET Z ; Result is zero -RONDUP: LD A,B ; Get VLSB of number -RONDB: LD HL,FPEXP ; Point to exponent - OR A ; Any rounding? - CALL M,FPROND ; Yes - Round number up - LD B,(HL) ; B = Exponent - INC HL - LD A,(HL) ; Get sign of result - AND 10000000B ; Only bit 7 needed - XOR C ; Set correct sign - LD C,A ; Save correct sign in number - JP FPBCDE ; Move BCDE to FPREG - -FPROND: INC E ; Round LSB - RET NZ ; Return if ok - INC D ; Round NMSB - RET NZ ; Return if ok - INC C ; Round MSB - RET NZ ; Return if ok - LD C,80H ; Set normal value - INC (HL) ; Increment exponent - RET NZ ; Return if ok - JP OVERR ; Overflow error - -PLUCDE: LD A,(HL) ; Get LSB of FPREG - ADD A,E ; Add LSB of BCDE - LD E,A ; Save LSB of BCDE - INC HL - LD A,(HL) ; Get NMSB of FPREG - ADC A,D ; Add NMSB of BCDE - LD D,A ; Save NMSB of BCDE - INC HL - LD A,(HL) ; Get MSB of FPREG - ADC A,C ; Add MSB of BCDE - LD C,A ; Save MSB of BCDE - RET - -COMPL: LD HL,SGNRES ; Sign of result - LD A,(HL) ; Get sign of result - CPL ; Negate it - LD (HL),A ; Put it back - XOR A - LD L,A ; Set L to zero - SUB B ; Negate exponent,set carry - LD B,A ; Re-save exponent - LD A,L ; Load zero - SBC A,E ; Negate LSB - LD E,A ; Re-save LSB - LD A,L ; Load zero - SBC A,D ; Negate NMSB - LD D,A ; Re-save NMSB - LD A,L ; Load zero - SBC A,C ; Negate MSB - LD C,A ; Re-save MSB - RET - -SCALE: LD B,0 ; Clear underflow -SCALLP: SUB 8 ; 8 bits (a whole byte)? - JP C,SHRITE ; No - Shift right A bits - LD B,E ; <- Shift - LD E,D ; <- right - LD D,C ; <- eight - LD C,0 ; <- bits - JP SCALLP ; More bits to shift - -SHRITE: ADD A,8+1 ; Adjust count - LD L,A ; Save bits to shift -SHRLP: XOR A ; Flag for all done - DEC L ; All shifting done? - RET Z ; Yes - Return - LD A,C ; Get MSB -SHRT1: RRA ; Shift it right - LD C,A ; Re-save - LD A,D ; Get NMSB - RRA ; Shift right with last bit - LD D,A ; Re-save it - LD A,E ; Get LSB - RRA ; Shift right with last bit - LD E,A ; Re-save it - LD A,B ; Get underflow - RRA ; Shift right with last bit - LD B,A ; Re-save underflow - JP SHRLP ; More bits to do - -UNITY: .BYTE 000H,000H,000H,081H ; 1.00000 - -LOGTAB: .BYTE 3 ; Table used by LOG - .BYTE 0AAH,056H,019H,080H ; 0.59898 - .BYTE 0F1H,022H,076H,080H ; 0.96147 - .BYTE 045H,0AAH,038H,082H ; 2.88539 - -LOG: CALL TSTSGN ; Test sign of value - OR A - JP PE,FCERR ; ?FC Error if <= zero - LD HL,FPEXP ; Point to exponent - LD A,(HL) ; Get exponent - LD BC,8035H ; BCDE = SQR(1/2) - LD DE,04F3H - SUB B ; Scale value to be < 1 - PUSH AF ; Save scale factor - LD (HL),B ; Save new exponent - PUSH DE ; Save SQR(1/2) - PUSH BC - CALL FPADD ; Add SQR(1/2) to value - POP BC ; Restore SQR(1/2) - POP DE - INC B ; Make it SQR(2) - CALL DVBCDE ; Divide by SQR(2) - LD HL,UNITY ; Point to 1. - CALL SUBPHL ; Subtract FPREG from 1 - LD HL,LOGTAB ; Coefficient table - CALL SUMSER ; Evaluate sum of series - LD BC,8080H ; BCDE = -0.5 - LD DE,0000H - CALL FPADD ; Subtract 0.5 from FPREG - POP AF ; Restore scale factor - CALL RSCALE ; Re-scale number -MULLN2: LD BC,8031H ; BCDE = Ln(2) - LD DE,7218H - .BYTE 21H ; Skip "POP BC" and "POP DE" - -MULT: POP BC ; Get number from stack - POP DE -FPMULT: CALL TSTSGN ; Test sign of FPREG - RET Z ; Return zero if zero - LD L,0 ; Flag add exponents - CALL ADDEXP ; Add exponents - LD A,C ; Get MSB of multiplier - LD (MULVAL),A ; Save MSB of multiplier - EX DE,HL - LD (MULVAL+1),HL ; Save rest of multiplier - LD BC,0 ; Partial product (BCDE) = zero - LD D,B - LD E,B - LD HL,BNORM ; Address of normalise - PUSH HL ; Save for return - LD HL,MULT8 ; Address of 8 bit multiply - PUSH HL ; Save for NMSB,MSB - PUSH HL ; - LD HL,FPREG ; Point to number -MULT8: LD A,(HL) ; Get LSB of number - INC HL ; Point to NMSB - OR A ; Test LSB - JP Z,BYTSFT ; Zero - shift to next byte - PUSH HL ; Save address of number - LD L,8 ; 8 bits to multiply by -MUL8LP: RRA ; Shift LSB right - LD H,A ; Save LSB - LD A,C ; Get MSB - JP NC,NOMADD ; Bit was zero - Don't add - PUSH HL ; Save LSB and count - LD HL,(MULVAL+1) ; Get LSB and NMSB - ADD HL,DE ; Add NMSB and LSB - EX DE,HL ; Leave sum in DE - POP HL ; Restore MSB and count - LD A,(MULVAL) ; Get MSB of multiplier - ADC A,C ; Add MSB -NOMADD: RRA ; Shift MSB right - LD C,A ; Re-save MSB - LD A,D ; Get NMSB - RRA ; Shift NMSB right - LD D,A ; Re-save NMSB - LD A,E ; Get LSB - RRA ; Shift LSB right - LD E,A ; Re-save LSB - LD A,B ; Get VLSB - RRA ; Shift VLSB right - LD B,A ; Re-save VLSB - DEC L ; Count bits multiplied - LD A,H ; Get LSB of multiplier - JP NZ,MUL8LP ; More - Do it -POPHRT: POP HL ; Restore address of number - RET - -BYTSFT: LD B,E ; Shift partial product left - LD E,D - LD D,C - LD C,A - RET - -DIV10: CALL STAKFP ; Save FPREG on stack - LD BC,8420H ; BCDE = 10. - LD DE,0000H - CALL FPBCDE ; Move 10 to FPREG - -DIV: POP BC ; Get number from stack - POP DE -DVBCDE: CALL TSTSGN ; Test sign of FPREG - JP Z,DZERR ; Error if division by zero - LD L,-1 ; Flag subtract exponents - CALL ADDEXP ; Subtract exponents - INC (HL) ; Add 2 to exponent to adjust - INC (HL) - DEC HL ; Point to MSB - LD A,(HL) ; Get MSB of dividend - LD (DIV3),A ; Save for subtraction - DEC HL - LD A,(HL) ; Get NMSB of dividend - LD (DIV2),A ; Save for subtraction - DEC HL - LD A,(HL) ; Get MSB of dividend - LD (DIV1),A ; Save for subtraction - LD B,C ; Get MSB - EX DE,HL ; NMSB,LSB to HL - XOR A - LD C,A ; Clear MSB of quotient - LD D,A ; Clear NMSB of quotient - LD E,A ; Clear LSB of quotient - LD (DIV4),A ; Clear overflow count -DIVLP: PUSH HL ; Save divisor - PUSH BC - LD A,L ; Get LSB of number - CALL DIVSUP ; Subt' divisor from dividend - SBC A,0 ; Count for overflows - CCF - JP NC,RESDIV ; Restore divisor if borrow - LD (DIV4),A ; Re-save overflow count - POP AF ; Scrap divisor - POP AF - SCF ; Set carry to - .BYTE 0D2H ; Skip "POP BC" and "POP HL" - -RESDIV: POP BC ; Restore divisor - POP HL - LD A,C ; Get MSB of quotient - INC A - DEC A - RRA ; Bit 0 to bit 7 - JP M,RONDB ; Done - Normalise result - RLA ; Restore carry - LD A,E ; Get LSB of quotient - RLA ; Double it - LD E,A ; Put it back - LD A,D ; Get NMSB of quotient - RLA ; Double it - LD D,A ; Put it back - LD A,C ; Get MSB of quotient - RLA ; Double it - LD C,A ; Put it back - ADD HL,HL ; Double NMSB,LSB of divisor - LD A,B ; Get MSB of divisor - RLA ; Double it - LD B,A ; Put it back - LD A,(DIV4) ; Get VLSB of quotient - RLA ; Double it - LD (DIV4),A ; Put it back - LD A,C ; Get MSB of quotient - OR D ; Merge NMSB - OR E ; Merge LSB - JP NZ,DIVLP ; Not done - Keep dividing - PUSH HL ; Save divisor - LD HL,FPEXP ; Point to exponent - DEC (HL) ; Divide by 2 - POP HL ; Restore divisor - JP NZ,DIVLP ; Ok - Keep going - JP OVERR ; Overflow error - -ADDEXP: LD A,B ; Get exponent of dividend - OR A ; Test it - JP Z,OVTST3 ; Zero - Result zero - LD A,L ; Get add/subtract flag - LD HL,FPEXP ; Point to exponent - XOR (HL) ; Add or subtract it - ADD A,B ; Add the other exponent - LD B,A ; Save new exponent - RRA ; Test exponent for overflow - XOR B - LD A,B ; Get exponent - JP P,OVTST2 ; Positive - Test for overflow - ADD A,80H ; Add excess 128 - LD (HL),A ; Save new exponent - JP Z,POPHRT ; Zero - Result zero - CALL SIGNS ; Set MSBs and sign of result - LD (HL),A ; Save new exponent - DEC HL ; Point to MSB - RET - -OVTST1: CALL TSTSGN ; Test sign of FPREG - CPL ; Invert sign - POP HL ; Clean up stack -OVTST2: OR A ; Test if new exponent zero -OVTST3: POP HL ; Clear off return address - JP P,RESZER ; Result zero - JP OVERR ; Overflow error - -MLSP10: CALL BCDEFP ; Move FPREG to BCDE - LD A,B ; Get exponent - OR A ; Is it zero? - RET Z ; Yes - Result is zero - ADD A,2 ; Multiply by 4 - JP C,OVERR ; Overflow - ?OV Error - LD B,A ; Re-save exponent - CALL FPADD ; Add BCDE to FPREG (Times 5) - LD HL,FPEXP ; Point to exponent - INC (HL) ; Double number (Times 10) - RET NZ ; Ok - Return - JP OVERR ; Overflow error - -TSTSGN: LD A,(FPEXP) ; Get sign of FPREG - OR A - RET Z ; RETurn if number is zero - LD A,(FPREG+2) ; Get MSB of FPREG - .BYTE 0FEH ; Test sign -RETREL: CPL ; Invert sign - RLA ; Sign bit to carry -FLGDIF: SBC A,A ; Carry to all bits of A - RET NZ ; Return -1 if negative - INC A ; Bump to +1 - RET ; Positive - Return +1 - -SGN: CALL TSTSGN ; Test sign of FPREG -FLGREL: LD B,80H+8 ; 8 bit integer in exponent - LD DE,0 ; Zero NMSB and LSB -RETINT: LD HL,FPEXP ; Point to exponent - LD C,A ; CDE = MSB,NMSB and LSB - LD (HL),B ; Save exponent - LD B,0 ; CDE = integer to normalise - INC HL ; Point to sign of result - LD (HL),80H ; Set sign of result - RLA ; Carry = sign of integer - JP CONPOS ; Set sign of result - -ABS: CALL TSTSGN ; Test sign of FPREG - RET P ; Return if positive -INVSGN: LD HL,FPREG+2 ; Point to MSB - LD A,(HL) ; Get sign of mantissa - XOR 80H ; Invert sign of mantissa - LD (HL),A ; Re-save sign of mantissa - RET - -STAKFP: EX DE,HL ; Save code string address - LD HL,(FPREG) ; LSB,NLSB of FPREG - EX (SP),HL ; Stack them,get return - PUSH HL ; Re-save return - LD HL,(FPREG+2) ; MSB and exponent of FPREG - EX (SP),HL ; Stack them,get return - PUSH HL ; Re-save return - EX DE,HL ; Restore code string address - RET - -PHLTFP: CALL LOADFP ; Number at HL to BCDE -FPBCDE: EX DE,HL ; Save code string address - LD (FPREG),HL ; Save LSB,NLSB of number - LD H,B ; Exponent of number - LD L,C ; MSB of number - LD (FPREG+2),HL ; Save MSB and exponent - EX DE,HL ; Restore code string address - RET - -BCDEFP: LD HL,FPREG ; Point to FPREG -LOADFP: LD E,(HL) ; Get LSB of number - INC HL - LD D,(HL) ; Get NMSB of number - INC HL - LD C,(HL) ; Get MSB of number - INC HL - LD B,(HL) ; Get exponent of number -INCHL: INC HL ; Used for conditional "INC HL" - RET - -FPTHL: LD DE,FPREG ; Point to FPREG -DETHL4: LD B,4 ; 4 bytes to move -DETHLB: LD A,(DE) ; Get source - LD (HL),A ; Save destination - INC DE ; Next source - INC HL ; Next destination - DEC B ; Count bytes - JP NZ,DETHLB ; Loop if more - RET - -SIGNS: LD HL,FPREG+2 ; Point to MSB of FPREG - LD A,(HL) ; Get MSB - RLCA ; Old sign to carry - SCF ; Set MSBit - RRA ; Set MSBit of MSB - LD (HL),A ; Save new MSB - CCF ; Complement sign - RRA ; Old sign to carry - INC HL - INC HL - LD (HL),A ; Set sign of result - LD A,C ; Get MSB - RLCA ; Old sign to carry - SCF ; Set MSBit - RRA ; Set MSBit of MSB - LD C,A ; Save MSB - RRA - XOR (HL) ; New sign of result - RET - -CMPNUM: LD A,B ; Get exponent of number - OR A - JP Z,TSTSGN ; Zero - Test sign of FPREG - LD HL,RETREL ; Return relation routine - PUSH HL ; Save for return - CALL TSTSGN ; Test sign of FPREG - LD A,C ; Get MSB of number - RET Z ; FPREG zero - Number's MSB - LD HL,FPREG+2 ; MSB of FPREG - XOR (HL) ; Combine signs - LD A,C ; Get MSB of number - RET M ; Exit if signs different - CALL CMPFP ; Compare FP numbers - RRA ; Get carry to sign - XOR C ; Combine with MSB of number - RET - -CMPFP: INC HL ; Point to exponent - LD A,B ; Get exponent - CP (HL) ; Compare exponents - RET NZ ; Different - DEC HL ; Point to MBS - LD A,C ; Get MSB - CP (HL) ; Compare MSBs - RET NZ ; Different - DEC HL ; Point to NMSB - LD A,D ; Get NMSB - CP (HL) ; Compare NMSBs - RET NZ ; Different - DEC HL ; Point to LSB - LD A,E ; Get LSB - SUB (HL) ; Compare LSBs - RET NZ ; Different - POP HL ; Drop RETurn - POP HL ; Drop another RETurn - RET - -FPINT: LD B,A ; <- Move - LD C,A ; <- exponent - LD D,A ; <- to all - LD E,A ; <- bits - OR A ; Test exponent - RET Z ; Zero - Return zero - PUSH HL ; Save pointer to number - CALL BCDEFP ; Move FPREG to BCDE - CALL SIGNS ; Set MSBs & sign of result - XOR (HL) ; Combine with sign of FPREG - LD H,A ; Save combined signs - CALL M,DCBCDE ; Negative - Decrement BCDE - LD A,80H+24 ; 24 bits - SUB B ; Bits to shift - CALL SCALE ; Shift BCDE - LD A,H ; Get combined sign - RLA ; Sign to carry - CALL C,FPROND ; Negative - Round number up - LD B,0 ; Zero exponent - CALL C,COMPL ; If negative make positive - POP HL ; Restore pointer to number - RET - -DCBCDE: DEC DE ; Decrement BCDE - LD A,D ; Test LSBs - AND E - INC A - RET NZ ; Exit if LSBs not FFFF - DEC BC ; Decrement MSBs - RET - -INT: LD HL,FPEXP ; Point to exponent - LD A,(HL) ; Get exponent - CP 80H+24 ; Integer accuracy only? - LD A,(FPREG) ; Get LSB - RET NC ; Yes - Already integer - LD A,(HL) ; Get exponent - CALL FPINT ; F.P to integer - LD (HL),80H+24 ; Save 24 bit integer - LD A,E ; Get LSB of number - PUSH AF ; Save LSB - LD A,C ; Get MSB of number - RLA ; Sign to carry - CALL CONPOS ; Set sign of result - POP AF ; Restore LSB of number - RET - -MLDEBC: LD HL,0 ; Clear partial product - LD A,B ; Test multiplier - OR C - RET Z ; Return zero if zero - LD A,16 ; 16 bits -MLDBLP: ADD HL,HL ; Shift P.P left - JP C,BSERR ; ?BS Error if overflow - EX DE,HL - ADD HL,HL ; Shift multiplier left - EX DE,HL - JP NC,NOMLAD ; Bit was zero - No add - ADD HL,BC ; Add multiplicand - JP C,BSERR ; ?BS Error if overflow -NOMLAD: DEC A ; Count bits - JP NZ,MLDBLP ; More - RET - -ASCTFP: CP '-' ; Negative? - PUSH AF ; Save it and flags - JP Z,CNVNUM ; Yes - Convert number - CP '+' ; Positive? - JP Z,CNVNUM ; Yes - Convert number - DEC HL ; DEC 'cos GETCHR INCs -CNVNUM: CALL RESZER ; Set result to zero - LD B,A ; Digits after point counter - LD D,A ; Sign of exponent - LD E,A ; Exponent of ten - CPL - LD C,A ; Before or after point flag -MANLP: CALL GETCHR ; Get next character - JP C,ADDIG ; Digit - Add to number - CP '.' - JP Z,DPOINT ; '.' - Flag point - CP 'E' - JP NZ,CONEXP ; Not 'E' - Scale number - CALL GETCHR ; Get next character - CALL SGNEXP ; Get sign of exponent -EXPLP: CALL GETCHR ; Get next character - JP C,EDIGIT ; Digit - Add to exponent - INC D ; Is sign negative? - JP NZ,CONEXP ; No - Scale number - XOR A - SUB E ; Negate exponent - LD E,A ; And re-save it - INC C ; Flag end of number -DPOINT: INC C ; Flag point passed - JP Z,MANLP ; Zero - Get another digit -CONEXP: PUSH HL ; Save code string address - LD A,E ; Get exponent - SUB B ; Subtract digits after point -SCALMI: CALL P,SCALPL ; Positive - Multiply number - JP P,ENDCON ; Positive - All done - PUSH AF ; Save number of times to /10 - CALL DIV10 ; Divide by 10 - POP AF ; Restore count - INC A ; Count divides - -ENDCON: JP NZ,SCALMI ; More to do - POP DE ; Restore code string address - POP AF ; Restore sign of number - CALL Z,INVSGN ; Negative - Negate number - EX DE,HL ; Code string address to HL - RET - -SCALPL: RET Z ; Exit if no scaling needed -MULTEN: PUSH AF ; Save count - CALL MLSP10 ; Multiply number by 10 - POP AF ; Restore count - DEC A ; Count multiplies - RET - -ADDIG: PUSH DE ; Save sign of exponent - LD D,A ; Save digit - LD A,B ; Get digits after point - ADC A,C ; Add one if after point - LD B,A ; Re-save counter - PUSH BC ; Save point flags - PUSH HL ; Save code string address - PUSH DE ; Save digit - CALL MLSP10 ; Multiply number by 10 - POP AF ; Restore digit - SUB '0' ; Make it absolute - CALL RSCALE ; Re-scale number - POP HL ; Restore code string address - POP BC ; Restore point flags - POP DE ; Restore sign of exponent - JP MANLP ; Get another digit - -RSCALE: CALL STAKFP ; Put number on stack - CALL FLGREL ; Digit to add to FPREG -PADD: POP BC ; Restore number - POP DE - JP FPADD ; Add BCDE to FPREG and return - -EDIGIT: LD A,E ; Get digit - RLCA ; Times 2 - RLCA ; Times 4 - ADD A,E ; Times 5 - RLCA ; Times 10 - ADD A,(HL) ; Add next digit - SUB '0' ; Make it absolute - LD E,A ; Save new digit - JP EXPLP ; Look for another digit - -LINEIN: PUSH HL ; Save code string address - LD HL,INMSG ; Output " in " - CALL PRS ; Output string at HL - POP HL ; Restore code string address -PRNTHL: EX DE,HL ; Code string address to DE - XOR A - LD B,80H+24 ; 24 bits - CALL RETINT ; Return the integer - LD HL,PRNUMS ; Print number string - PUSH HL ; Save for return -NUMASC: LD HL,PBUFF ; Convert number to ASCII - PUSH HL ; Save for return - CALL TSTSGN ; Test sign of FPREG - LD (HL),' ' ; Space at start - JP P,SPCFST ; Positive - Space to start - LD (HL),'-' ; '-' sign at start -SPCFST: INC HL ; First byte of number - LD (HL),'0' ; '0' if zero - JP Z,JSTZER ; Return '0' if zero - PUSH HL ; Save buffer address - CALL M,INVSGN ; Negate FPREG if negative - XOR A ; Zero A - PUSH AF ; Save it - CALL RNGTST ; Test number is in range -SIXDIG: LD BC,9143H ; BCDE - 99999.9 - LD DE,4FF8H - CALL CMPNUM ; Compare numbers - OR A - JP PO,INRNG ; > 99999.9 - Sort it out - POP AF ; Restore count - CALL MULTEN ; Multiply by ten - PUSH AF ; Re-save count - JP SIXDIG ; Test it again - -GTSIXD: CALL DIV10 ; Divide by 10 - POP AF ; Get count - INC A ; Count divides - PUSH AF ; Re-save count - CALL RNGTST ; Test number is in range -INRNG: CALL ROUND ; Add 0.5 to FPREG - INC A - CALL FPINT ; F.P to integer - CALL FPBCDE ; Move BCDE to FPREG - LD BC,0306H ; 1E+06 to 1E-03 range - POP AF ; Restore count - ADD A,C ; 6 digits before point - INC A ; Add one - JP M,MAKNUM ; Do it in 'E' form if < 1E-02 - CP 6+1+1 ; More than 999999 ? - JP NC,MAKNUM ; Yes - Do it in 'E' form - INC A ; Adjust for exponent - LD B,A ; Exponent of number - LD A,2 ; Make it zero after - -MAKNUM: DEC A ; Adjust for digits to do - DEC A - POP HL ; Restore buffer address - PUSH AF ; Save count - LD DE,POWERS ; Powers of ten - DEC B ; Count digits before point - JP NZ,DIGTXT ; Not zero - Do number - LD (HL),'.' ; Save point - INC HL ; Move on - LD (HL),'0' ; Save zero - INC HL ; Move on -DIGTXT: DEC B ; Count digits before point - LD (HL),'.' ; Save point in case - CALL Z,INCHL ; Last digit - move on - PUSH BC ; Save digits before point - PUSH HL ; Save buffer address - PUSH DE ; Save powers of ten - CALL BCDEFP ; Move FPREG to BCDE - POP HL ; Powers of ten table - LD B, '0'-1 ; ASCII '0' - 1 -TRYAGN: INC B ; Count subtractions - LD A,E ; Get LSB - SUB (HL) ; Subtract LSB - LD E,A ; Save LSB - INC HL - LD A,D ; Get NMSB - SBC A,(HL) ; Subtract NMSB - LD D,A ; Save NMSB - INC HL - LD A,C ; Get MSB - SBC A,(HL) ; Subtract MSB - LD C,A ; Save MSB - DEC HL ; Point back to start - DEC HL - JP NC,TRYAGN ; No overflow - Try again - CALL PLUCDE ; Restore number - INC HL ; Start of next number - CALL FPBCDE ; Move BCDE to FPREG - EX DE,HL ; Save point in table - POP HL ; Restore buffer address - LD (HL),B ; Save digit in buffer - INC HL ; And move on - POP BC ; Restore digit count - DEC C ; Count digits - JP NZ,DIGTXT ; More - Do them - DEC B ; Any decimal part? - JP Z,DOEBIT ; No - Do 'E' bit -SUPTLZ: DEC HL ; Move back through buffer - LD A,(HL) ; Get character - CP '0' ; '0' character? - JP Z,SUPTLZ ; Yes - Look back for more - CP '.' ; A decimal point? - CALL NZ,INCHL ; Move back over digit - -DOEBIT: POP AF ; Get 'E' flag - JP Z,NOENED ; No 'E' needed - End buffer - LD (HL),'E' ; Put 'E' in buffer - INC HL ; And move on - LD (HL),'+' ; Put '+' in buffer - JP P,OUTEXP ; Positive - Output exponent - LD (HL),'-' ; Put '-' in buffer - CPL ; Negate exponent - INC A -OUTEXP: LD B,'0'-1 ; ASCII '0' - 1 -EXPTEN: INC B ; Count subtractions - SUB 10 ; Tens digit - JP NC,EXPTEN ; More to do - ADD A,'0'+10 ; Restore and make ASCII - INC HL ; Move on - LD (HL),B ; Save MSB of exponent -JSTZER: INC HL ; - LD (HL),A ; Save LSB of exponent - INC HL -NOENED: LD (HL),C ; Mark end of buffer - POP HL ; Restore code string address - RET - -RNGTST: LD BC,9474H ; BCDE = 999999. - LD DE,23F7H - CALL CMPNUM ; Compare numbers - OR A - POP HL ; Return address to HL - JP PO,GTSIXD ; Too big - Divide by ten - JP (HL) ; Otherwise return to caller - -HALF: .BYTE 00H,00H,00H,80H ; 0.5 - -POWERS: .BYTE 0A0H,086H,001H ; 100000 - .BYTE 010H,027H,000H ; 10000 - .BYTE 0E8H,003H,000H ; 1000 - .BYTE 064H,000H,000H ; 100 - .BYTE 00AH,000H,000H ; 10 - .BYTE 001H,000H,000H ; 1 - -NEGAFT: LD HL,INVSGN ; Negate result - EX (SP),HL ; To be done after caller - JP (HL) ; Return to caller - -SQR: CALL STAKFP ; Put value on stack - LD HL,HALF ; Set power to 1/2 - CALL PHLTFP ; Move 1/2 to FPREG - -POWER: POP BC ; Get base - POP DE - CALL TSTSGN ; Test sign of power - LD A,B ; Get exponent of base - JP Z,EXP ; Make result 1 if zero - JP P,POWER1 ; Positive base - Ok - OR A ; Zero to negative power? - JP Z,DZERR ; Yes - ?/0 Error -POWER1: OR A ; Base zero? - JP Z,SAVEXP ; Yes - Return zero - PUSH DE ; Save base - PUSH BC - LD A,C ; Get MSB of base - OR 01111111B ; Get sign status - CALL BCDEFP ; Move power to BCDE - JP P,POWER2 ; Positive base - Ok - PUSH DE ; Save power - PUSH BC - CALL INT ; Get integer of power - POP BC ; Restore power - POP DE - PUSH AF ; MSB of base - CALL CMPNUM ; Power an integer? - POP HL ; Restore MSB of base - LD A,H ; but don't affect flags - RRA ; Exponent odd or even? -POWER2: POP HL ; Restore MSB and exponent - LD (FPREG+2),HL ; Save base in FPREG - POP HL ; LSBs of base - LD (FPREG),HL ; Save in FPREG - CALL C,NEGAFT ; Odd power - Negate result - CALL Z,INVSGN ; Negative base - Negate it - PUSH DE ; Save power - PUSH BC - CALL LOG ; Get LOG of base - POP BC ; Restore power - POP DE - CALL FPMULT ; Multiply LOG by power - -EXP: CALL STAKFP ; Put value on stack - LD BC,08138H ; BCDE = 1/Ln(2) - LD DE,0AA3BH - CALL FPMULT ; Multiply value by 1/LN(2) - LD A,(FPEXP) ; Get exponent - CP 80H+8 ; Is it in range? - JP NC,OVTST1 ; No - Test for overflow - CALL INT ; Get INT of FPREG - ADD A,80H ; For excess 128 - ADD A,2 ; Exponent > 126? - JP C,OVTST1 ; Yes - Test for overflow - PUSH AF ; Save scaling factor - LD HL,UNITY ; Point to 1. - CALL ADDPHL ; Add 1 to FPREG - CALL MULLN2 ; Multiply by LN(2) - POP AF ; Restore scaling factor - POP BC ; Restore exponent - POP DE - PUSH AF ; Save scaling factor - CALL SUBCDE ; Subtract exponent from FPREG - CALL INVSGN ; Negate result - LD HL,EXPTAB ; Coefficient table - CALL SMSER1 ; Sum the series - LD DE,0 ; Zero LSBs - POP BC ; Scaling factor - LD C,D ; Zero MSB - JP FPMULT ; Scale result to correct value - -EXPTAB: .BYTE 8 ; Table used by EXP - .BYTE 040H,02EH,094H,074H ; -1/7! (-1/5040) - .BYTE 070H,04FH,02EH,077H ; 1/6! ( 1/720) - .BYTE 06EH,002H,088H,07AH ; -1/5! (-1/120) - .BYTE 0E6H,0A0H,02AH,07CH ; 1/4! ( 1/24) - .BYTE 050H,0AAH,0AAH,07EH ; -1/3! (-1/6) - .BYTE 0FFH,0FFH,07FH,07FH ; 1/2! ( 1/2) - .BYTE 000H,000H,080H,081H ; -1/1! (-1/1) - .BYTE 000H,000H,000H,081H ; 1/0! ( 1/1) - -SUMSER: CALL STAKFP ; Put FPREG on stack - LD DE,MULT ; Multiply by "X" - PUSH DE ; To be done after - PUSH HL ; Save address of table - CALL BCDEFP ; Move FPREG to BCDE - CALL FPMULT ; Square the value - POP HL ; Restore address of table -SMSER1: CALL STAKFP ; Put value on stack - LD A,(HL) ; Get number of coefficients - INC HL ; Point to start of table - CALL PHLTFP ; Move coefficient to FPREG - .BYTE 06H ; Skip "POP AF" -SUMLP: POP AF ; Restore count - POP BC ; Restore number - POP DE - DEC A ; Cont coefficients - RET Z ; All done - PUSH DE ; Save number - PUSH BC - PUSH AF ; Save count - PUSH HL ; Save address in table - CALL FPMULT ; Multiply FPREG by BCDE - POP HL ; Restore address in table - CALL LOADFP ; Number at HL to BCDE - PUSH HL ; Save address in table - CALL FPADD ; Add coefficient to FPREG - POP HL ; Restore address in table - JP SUMLP ; More coefficients - -RND: CALL TSTSGN ; Test sign of FPREG - LD HL,SEED+2 ; Random number seed - JP M,RESEED ; Negative - Re-seed - LD HL,LSTRND ; Last random number - CALL PHLTFP ; Move last RND to FPREG - LD HL,SEED+2 ; Random number seed - RET Z ; Return if RND(0) - ADD A,(HL) ; Add (SEED)+2) - AND 00000111B ; 0 to 7 - LD B,0 - LD (HL),A ; Re-save seed - INC HL ; Move to coefficient table - ADD A,A ; 4 bytes - ADD A,A ; per entry - LD C,A ; BC = Offset into table - ADD HL,BC ; Point to coefficient - CALL LOADFP ; Coefficient to BCDE - CALL FPMULT ; ; Multiply FPREG by coefficient - LD A,(SEED+1) ; Get (SEED+1) - INC A ; Add 1 - AND 00000011B ; 0 to 3 - LD B,0 - CP 1 ; Is it zero? - ADC A,B ; Yes - Make it 1 - LD (SEED+1),A ; Re-save seed - LD HL,RNDTAB-4 ; Addition table - ADD A,A ; 4 bytes - ADD A,A ; per entry - LD C,A ; BC = Offset into table - ADD HL,BC ; Point to value - CALL ADDPHL ; Add value to FPREG -RND1: CALL BCDEFP ; Move FPREG to BCDE - LD A,E ; Get LSB - LD E,C ; LSB = MSB - XOR 01001111B ; Fiddle around - LD C,A ; New MSB - LD (HL),80H ; Set exponent - DEC HL ; Point to MSB - LD B,(HL) ; Get MSB - LD (HL),80H ; Make value -0.5 - LD HL,SEED ; Random number seed - INC (HL) ; Count seed - LD A,(HL) ; Get seed - SUB 171 ; Do it modulo 171 - JP NZ,RND2 ; Non-zero - Ok - LD (HL),A ; Zero seed - INC C ; Fillde about - DEC D ; with the - INC E ; number -RND2: CALL BNORM ; Normalise number - LD HL,LSTRND ; Save random number - JP FPTHL ; Move FPREG to last and return - -RESEED: LD (HL),A ; Re-seed random numbers - DEC HL - LD (HL),A - DEC HL - LD (HL),A - JP RND1 ; Return RND seed - -RNDTAB: .BYTE 068H,0B1H,046H,068H ; Table used by RND - .BYTE 099H,0E9H,092H,069H - .BYTE 010H,0D1H,075H,068H - -COS: LD HL,HALFPI ; Point to PI/2 - CALL ADDPHL ; Add it to PPREG -SIN: CALL STAKFP ; Put angle on stack - LD BC,8349H ; BCDE = 2 PI - LD DE,0FDBH - CALL FPBCDE ; Move 2 PI to FPREG - POP BC ; Restore angle - POP DE - CALL DVBCDE ; Divide angle by 2 PI - CALL STAKFP ; Put it on stack - CALL INT ; Get INT of result - POP BC ; Restore number - POP DE - CALL SUBCDE ; Make it 0 <= value < 1 - LD HL,QUARTR ; Point to 0.25 - CALL SUBPHL ; Subtract value from 0.25 - CALL TSTSGN ; Test sign of value - SCF ; Flag positive - JP P,SIN1 ; Positive - Ok - CALL ROUND ; Add 0.5 to value - CALL TSTSGN ; Test sign of value - OR A ; Flag negative -SIN1: PUSH AF ; Save sign - CALL P,INVSGN ; Negate value if positive - LD HL,QUARTR ; Point to 0.25 - CALL ADDPHL ; Add 0.25 to value - POP AF ; Restore sign - CALL NC,INVSGN ; Negative - Make positive - LD HL,SINTAB ; Coefficient table - JP SUMSER ; Evaluate sum of series - -HALFPI: .BYTE 0DBH,00FH,049H,081H ; 1.5708 (PI/2) - -QUARTR: .BYTE 000H,000H,000H,07FH ; 0.25 - -SINTAB: .BYTE 5 ; Table used by SIN - .BYTE 0BAH,0D7H,01EH,086H ; 39.711 - .BYTE 064H,026H,099H,087H ;-76.575 - .BYTE 058H,034H,023H,087H ; 81.602 - .BYTE 0E0H,05DH,0A5H,086H ;-41.342 - .BYTE 0DAH,00FH,049H,083H ; 6.2832 - -TAN: CALL STAKFP ; Put angle on stack - CALL SIN ; Get SIN of angle - POP BC ; Restore angle - POP HL - CALL STAKFP ; Save SIN of angle - EX DE,HL ; BCDE = Angle - CALL FPBCDE ; Angle to FPREG - CALL COS ; Get COS of angle - JP DIV ; TAN = SIN / COS - -ATN: CALL TSTSGN ; Test sign of value - CALL M,NEGAFT ; Negate result after if -ve - CALL M,INVSGN ; Negate value if -ve - LD A,(FPEXP) ; Get exponent - CP 81H ; Number less than 1? - JP C,ATN1 ; Yes - Get arc tangnt - LD BC,8100H ; BCDE = 1 - LD D,C - LD E,C - CALL DVBCDE ; Get reciprocal of number - LD HL,SUBPHL ; Sub angle from PI/2 - PUSH HL ; Save for angle > 1 -ATN1: LD HL,ATNTAB ; Coefficient table - CALL SUMSER ; Evaluate sum of series - LD HL,HALFPI ; PI/2 - angle in case > 1 - RET ; Number > 1 - Sub from PI/2 - -ATNTAB: .BYTE 9 ; Table used by ATN - .BYTE 04AH,0D7H,03BH,078H ; 1/17 - .BYTE 002H,06EH,084H,07BH ;-1/15 - .BYTE 0FEH,0C1H,02FH,07CH ; 1/13 - .BYTE 074H,031H,09AH,07DH ;-1/11 - .BYTE 084H,03DH,05AH,07DH ; 1/9 - .BYTE 0C8H,07FH,091H,07EH ;-1/7 - .BYTE 0E4H,0BBH,04CH,07EH ; 1/5 - .BYTE 06CH,0AAH,0AAH,07FH ;-1/3 - .BYTE 000H,000H,000H,081H ; 1/1 - -ARET: RET ; A RETurn instruction - -GETINP: - PUSH BC - PUSH DE - PUSH HL - ; INPUT CHARACTER FROM CONSOLE VIA HBIOS - LD C,CIODEV_CONSOLE ; CONSOLE UNIT TO C - LD B,BF_CIOIN ; HBIOS FUNC: INPUT CHAR - RST 08 ; HBIOS READS CHARACTDR - LD A,E ; MOVE CHARACTER TO A FOR RETURN - ; RESTORE REGISTERS (AF IS OUTPUT) - POP HL - POP DE - POP BC - RET -CLS: - LD A,CS ; ASCII Clear screen - JP MONOUT ; Output character - -WIDTH: CALL GETINT ; Get integer 0-255 - LD A,E ; Width to A - LD (LWIDTH),A ; Set width - RET - -LINES: CALL GETNUM ; Get a number - CALL DEINT ; Get integer -32768 to 32767 - LD (LINESC),DE ; Set lines counter - LD (LINESN),DE ; Set lines number - RET - -DEEK: CALL DEINT ; Get integer -32768 to 32767 - PUSH DE ; Save number - POP HL ; Number to HL - LD B,(HL) ; Get LSB of contents - INC HL - LD A,(HL) ; Get MSB of contents - JP ABPASS ; Return integer AB - -DOKE: CALL GETNUM ; Get a number - CALL DEINT ; Get integer -32768 to 32767 - PUSH DE ; Save address - CALL CHKSYN ; Make sure ',' follows - .BYTE ',' - CALL GETNUM ; Get a number - CALL DEINT ; Get integer -32768 to 32767 - EX (SP),HL ; Save value,get address - LD (HL),E ; Save LSB of value - INC HL - LD (HL),D ; Save MSB of value - POP HL ; Restore code string address - RET + JP NZ,VAL2 + CALL BINTFP ; Convert Bin to FPREG + JR VAL3 +VAL2: CALL ASCTFP ; Convert ASCII string to FP +VAL3: POP BC ; Restore end+1 byte + POP HL ; Restore end+1 address + LD (HL),B ; Put back original byte + RET + +LFRGNM: EX DE,HL ; Code string address to HL + CALL CHKSYN ; Make sure ")" follows + .BYTE ")" +MIDNUM: POP BC ; Get return address + POP DE ; Get number supplied + PUSH BC ; Re-save return address + LD B,E ; Number to B + RET + +INP: CALL MAKINT ; Make it integer A + LD (INPORT),A ; Set input port + CALL INPSUB ; Get input from port + JP PASSA ; Return integer A + +POUT: CALL SETIO ; Set up port number + JP OUTSUB ; Output data and return + +WAIT: CALL SETIO ; Set up port number + PUSH AF ; Save AND mask + LD E,0 ; Assume zero if none given + DEC HL ; DEC 'cos GETCHR INCs + CALL GETCHR ; Get next character + JP Z,NOXOR ; No XOR byte given + CALL CHKSYN ; Make sure ',' follows + .BYTE ',' + CALL GETINT ; Get integer 0-255 to XOR with +NOXOR: POP BC ; Restore AND mask +WAITLP: CALL INPSUB ; Get input + XOR E ; Flip selected bits + AND B ; Result non-zero? + JP Z,WAITLP ; No = keep waiting + RET + +SETIO: CALL GETINT ; Get integer 0-255 + LD (INPORT),A ; Set input port + LD (OTPORT),A ; Set output port + CALL CHKSYN ; Make sure ',' follows + .BYTE ',' + JP GETINT ; Get integer 0-255 and return + +FNDNUM: CALL GETCHR ; Get next character +GETINT: CALL GETNUM ; Get a number from 0 to 255 +MAKINT: CALL DEPINT ; Make sure value 0 - 255 + LD A,D ; Get MSB of number + OR A ; Zero? + JP NZ,FCERR ; No - Error + DEC HL ; DEC 'cos GETCHR INCs + CALL GETCHR ; Get next character + LD A,E ; Get number to A + RET + +PEEK: CALL DEINT ; Get memory address + LD A,(DE) ; Get byte in memory + JP PASSA ; Return integer A + +POKE: CALL GETNUM ; Get memory address + CALL DEINT ; Get integer -32768 to 3276 + PUSH DE ; Save memory address + CALL CHKSYN ; Make sure ',' follows + .BYTE ',' + CALL GETINT ; Get integer 0-255 + POP DE ; Restore memory address + LD (DE),A ; Load it into memory + RET + +ROUND: LD HL,HALF ; Add 0.5 to FPREG +ADDPHL: CALL LOADFP ; Load FP at (HL) to BCDE + JP FPADD ; Add BCDE to FPREG + +SUBPHL: CALL LOADFP ; FPREG = -FPREG + number at HL + .BYTE 21H ; Skip "POP BC" and "POP DE" +PSUB: POP BC ; Get FP number from stack + POP DE +SUBCDE: CALL INVSGN ; Negate FPREG +FPADD: LD A,B ; Get FP exponent + OR A ; Is number zero? + RET Z ; Yes - Nothing to add + LD A,(FPEXP) ; Get FPREG exponent + OR A ; Is this number zero? + JP Z,FPBCDE ; Yes - Move BCDE to FPREG + SUB B ; BCDE number larger? + JP NC,NOSWAP ; No - Don't swap them + CPL ; Two's complement + INC A ; FP exponent + EX DE,HL + CALL STAKFP ; Put FPREG on stack + EX DE,HL + CALL FPBCDE ; Move BCDE to FPREG + POP BC ; Restore number from stack + POP DE +NOSWAP: CP 24+1 ; Second number insignificant? + RET NC ; Yes - First number is result + PUSH AF ; Save number of bits to scale + CALL SIGNS ; Set MSBs & sign of result + LD H,A ; Save sign of result + POP AF ; Restore scaling factor + CALL SCALE ; Scale BCDE to same exponent + OR H ; Result to be positive? + LD HL,FPREG ; Point to FPREG + JP P,MINCDE ; No - Subtract FPREG from CDE + CALL PLUCDE ; Add FPREG to CDE + JP NC,RONDUP ; No overflow - Round it up + INC HL ; Point to exponent + INC (HL) ; Increment it + JP Z,OVERR ; Number overflowed - Error + LD L,1 ; 1 bit to shift right + CALL SHRT1 ; Shift result right + JP RONDUP ; Round it up + +MINCDE: XOR A ; Clear A and carry + SUB B ; Negate exponent + LD B,A ; Re-save exponent + LD A,(HL) ; Get LSB of FPREG + SBC A, E ; Subtract LSB of BCDE + LD E,A ; Save LSB of BCDE + INC HL + LD A,(HL) ; Get NMSB of FPREG + SBC A,D ; Subtract NMSB of BCDE + LD D,A ; Save NMSB of BCDE + INC HL + LD A,(HL) ; Get MSB of FPREG + SBC A,C ; Subtract MSB of BCDE + LD C,A ; Save MSB of BCDE +CONPOS: CALL C,COMPL ; Overflow - Make it positive + +BNORM: LD L,B ; L = Exponent + LD H,E ; H = LSB + XOR A +BNRMLP: LD B,A ; Save bit count + LD A,C ; Get MSB + OR A ; Is it zero? + JP NZ,PNORM ; No - Do it bit at a time + LD C,D ; MSB = NMSB + LD D,H ; NMSB= LSB + LD H,L ; LSB = VLSB + LD L,A ; VLSB= 0 + LD A,B ; Get exponent + SUB 8 ; Count 8 bits + CP -24-8 ; Was number zero? + JP NZ,BNRMLP ; No - Keep normalising +RESZER: XOR A ; Result is zero +SAVEXP: LD (FPEXP),A ; Save result as zero + RET + +NORMAL: DEC B ; Count bits + ADD HL,HL ; Shift HL left + LD A,D ; Get NMSB + RLA ; Shift left with last bit + LD D,A ; Save NMSB + LD A,C ; Get MSB + ADC A,A ; Shift left with last bit + LD C,A ; Save MSB +PNORM: JP P,NORMAL ; Not done - Keep going + LD A,B ; Number of bits shifted + LD E,H ; Save HL in EB + LD B,L + OR A ; Any shifting done? + JP Z,RONDUP ; No - Round it up + LD HL,FPEXP ; Point to exponent + ADD A,(HL) ; Add shifted bits + LD (HL),A ; Re-save exponent + JP NC,RESZER ; Underflow - Result is zero + RET Z ; Result is zero +RONDUP: LD A,B ; Get VLSB of number +RONDB: LD HL,FPEXP ; Point to exponent + OR A ; Any rounding? + CALL M,FPROND ; Yes - Round number up + LD B,(HL) ; B = Exponent + INC HL + LD A,(HL) ; Get sign of result + AND 10000000B ; Only bit 7 needed + XOR C ; Set correct sign + LD C,A ; Save correct sign in number + JP FPBCDE ; Move BCDE to FPREG + +FPROND: INC E ; Round LSB + RET NZ ; Return if ok + INC D ; Round NMSB + RET NZ ; Return if ok + INC C ; Round MSB + RET NZ ; Return if ok + LD C,80H ; Set normal value + INC (HL) ; Increment exponent + RET NZ ; Return if ok + JP OVERR ; Overflow error + +PLUCDE: LD A,(HL) ; Get LSB of FPREG + ADD A,E ; Add LSB of BCDE + LD E,A ; Save LSB of BCDE + INC HL + LD A,(HL) ; Get NMSB of FPREG + ADC A,D ; Add NMSB of BCDE + LD D,A ; Save NMSB of BCDE + INC HL + LD A,(HL) ; Get MSB of FPREG + ADC A,C ; Add MSB of BCDE + LD C,A ; Save MSB of BCDE + RET + +COMPL: LD HL,SGNRES ; Sign of result + LD A,(HL) ; Get sign of result + CPL ; Negate it + LD (HL),A ; Put it back + XOR A + LD L,A ; Set L to zero + SUB B ; Negate exponent,set carry + LD B,A ; Re-save exponent + LD A,L ; Load zero + SBC A,E ; Negate LSB + LD E,A ; Re-save LSB + LD A,L ; Load zero + SBC A,D ; Negate NMSB + LD D,A ; Re-save NMSB + LD A,L ; Load zero + SBC A,C ; Negate MSB + LD C,A ; Re-save MSB + RET + +SCALE: LD B,0 ; Clear underflow +SCALLP: SUB 8 ; 8 bits (a whole byte)? + JP C,SHRITE ; No - Shift right A bits + LD B,E ; <- Shift + LD E,D ; <- right + LD D,C ; <- eight + LD C,0 ; <- bits + JP SCALLP ; More bits to shift + +SHRITE: ADD A,8+1 ; Adjust count + LD L,A ; Save bits to shift +SHRLP: XOR A ; Flag for all done + DEC L ; All shifting done? + RET Z ; Yes - Return + LD A,C ; Get MSB +SHRT1: RRA ; Shift it right + LD C,A ; Re-save + LD A,D ; Get NMSB + RRA ; Shift right with last bit + LD D,A ; Re-save it + LD A,E ; Get LSB + RRA ; Shift right with last bit + LD E,A ; Re-save it + LD A,B ; Get underflow + RRA ; Shift right with last bit + LD B,A ; Re-save underflow + JP SHRLP ; More bits to do + +UNITY: .BYTE 000H,000H,000H,081H ; 1.00000 + +LOGTAB: .BYTE 3 ; Table used by LOG + .BYTE 0AAH,056H,019H,080H ; 0.59898 + .BYTE 0F1H,022H,076H,080H ; 0.96147 + .BYTE 045H,0AAH,038H,082H ; 2.88539 + +LOG: CALL TSTSGN ; Test sign of value + OR A + JP PE,FCERR ; ?FC Error if <= zero + LD HL,FPEXP ; Point to exponent + LD A,(HL) ; Get exponent + LD BC,8035H ; BCDE = SQR(1/2) + LD DE,04F3H + SUB B ; Scale value to be < 1 + PUSH AF ; Save scale factor + LD (HL),B ; Save new exponent + PUSH DE ; Save SQR(1/2) + PUSH BC + CALL FPADD ; Add SQR(1/2) to value + POP BC ; Restore SQR(1/2) + POP DE + INC B ; Make it SQR(2) + CALL DVBCDE ; Divide by SQR(2) + LD HL,UNITY ; Point to 1. + CALL SUBPHL ; Subtract FPREG from 1 + LD HL,LOGTAB ; Coefficient table + CALL SUMSER ; Evaluate sum of series + LD BC,8080H ; BCDE = -0.5 + LD DE,0000H + CALL FPADD ; Subtract 0.5 from FPREG + POP AF ; Restore scale factor + CALL RSCALE ; Re-scale number +MULLN2: LD BC,8031H ; BCDE = Ln(2) + LD DE,7218H + .BYTE 21H ; Skip "POP BC" and "POP DE" + +MULT: POP BC ; Get number from stack + POP DE +FPMULT: CALL TSTSGN ; Test sign of FPREG + RET Z ; Return zero if zero + LD L,0 ; Flag add exponents + CALL ADDEXP ; Add exponents + LD A,C ; Get MSB of multiplier + LD (MULVAL),A ; Save MSB of multiplier + EX DE,HL + LD (MULVAL+1),HL ; Save rest of multiplier + LD BC,0 ; Partial product (BCDE) = zero + LD D,B + LD E,B + LD HL,BNORM ; Address of normalise + PUSH HL ; Save for return + LD HL,MULT8 ; Address of 8 bit multiply + PUSH HL ; Save for NMSB,MSB + PUSH HL + LD HL,FPREG ; Point to number +MULT8: LD A,(HL) ; Get LSB of number + INC HL ; Point to NMSB + OR A ; Test LSB + JP Z,BYTSFT ; Zero - shift to next byte + PUSH HL ; Save address of number + LD L,8 ; 8 bits to multiply by +MUL8LP: RRA ; Shift LSB right + LD H,A ; Save LSB + LD A,C ; Get MSB + JP NC,NOMADD ; Bit was zero - Don't add + PUSH HL ; Save LSB and count + LD HL,(MULVAL+1) ; Get LSB and NMSB + ADD HL,DE ; Add NMSB and LSB + EX DE,HL ; Leave sum in DE + POP HL ; Restore MSB and count + LD A,(MULVAL) ; Get MSB of multiplier + ADC A,C ; Add MSB +NOMADD: RRA ; Shift MSB right + LD C,A ; Re-save MSB + LD A,D ; Get NMSB + RRA ; Shift NMSB right + LD D,A ; Re-save NMSB + LD A,E ; Get LSB + RRA ; Shift LSB right + LD E,A ; Re-save LSB + LD A,B ; Get VLSB + RRA ; Shift VLSB right + LD B,A ; Re-save VLSB + DEC L ; Count bits multiplied + LD A,H ; Get LSB of multiplier + JP NZ,MUL8LP ; More - Do it +POPHRT: POP HL ; Restore address of number + RET + +BYTSFT: LD B,E ; Shift partial product left + LD E,D + LD D,C + LD C,A + RET + +DIV10: CALL STAKFP ; Save FPREG on stack + LD BC,8420H ; BCDE = 10. + LD DE,0000H + CALL FPBCDE ; Move 10 to FPREG + +DIV: POP BC ; Get number from stack + POP DE +DVBCDE: CALL TSTSGN ; Test sign of FPREG + JP Z,DZERR ; Error if division by zero + LD L,-1 ; Flag subtract exponents + CALL ADDEXP ; Subtract exponents + INC (HL) ; Add 2 to exponent to adjust + INC (HL) + DEC HL ; Point to MSB + LD A,(HL) ; Get MSB of dividend + LD (DIV3),A ; Save for subtraction + DEC HL + LD A,(HL) ; Get NMSB of dividend + LD (DIV2),A ; Save for subtraction + DEC HL + LD A,(HL) ; Get MSB of dividend + LD (DIV1),A ; Save for subtraction + LD B,C ; Get MSB + EX DE,HL ; NMSB,LSB to HL + XOR A + LD C,A ; Clear MSB of quotient + LD D,A ; Clear NMSB of quotient + LD E,A ; Clear LSB of quotient + LD (DIV4),A ; Clear overflow count +DIVLP: PUSH HL ; Save divisor + PUSH BC + LD A,L ; Get LSB of number + CALL DIVSUP ; Subt' divisor from dividend + SBC A,0 ; Count for overflows + CCF + JP NC,RESDIV ; Restore divisor if borrow + LD (DIV4),A ; Re-save overflow count + POP AF ; Scrap divisor + POP AF + SCF ; Set carry to + .BYTE 0D2H ; Skip "POP BC" and "POP HL" + +RESDIV: POP BC ; Restore divisor + POP HL + LD A,C ; Get MSB of quotient + INC A + DEC A + RRA ; Bit 0 to bit 7 + JP M,RONDB ; Done - Normalise result + RLA ; Restore carry + LD A,E ; Get LSB of quotient + RLA ; Double it + LD E,A ; Put it back + LD A,D ; Get NMSB of quotient + RLA ; Double it + LD D,A ; Put it back + LD A,C ; Get MSB of quotient + RLA ; Double it + LD C,A ; Put it back + ADD HL,HL ; Double NMSB,LSB of divisor + LD A,B ; Get MSB of divisor + RLA ; Double it + LD B,A ; Put it back + LD A,(DIV4) ; Get VLSB of quotient + RLA ; Double it + LD (DIV4),A ; Put it back + LD A,C ; Get MSB of quotient + OR D ; Merge NMSB + OR E ; Merge LSB + JP NZ,DIVLP ; Not done - Keep dividing + PUSH HL ; Save divisor + LD HL,FPEXP ; Point to exponent + DEC (HL) ; Divide by 2 + POP HL ; Restore divisor + JP NZ,DIVLP ; Ok - Keep going + JP OVERR ; Overflow error + +ADDEXP: LD A,B ; Get exponent of dividend + OR A ; Test it + JP Z,OVTST3 ; Zero - Result zero + LD A,L ; Get add/subtract flag + LD HL,FPEXP ; Point to exponent + XOR (HL) ; Add or subtract it + ADD A,B ; Add the other exponent + LD B,A ; Save new exponent + RRA ; Test exponent for overflow + XOR B + LD A,B ; Get exponent + JP P,OVTST2 ; Positive - Test for overflow + ADD A,80H ; Add excess 128 + LD (HL),A ; Save new exponent + JP Z,POPHRT ; Zero - Result zero + CALL SIGNS ; Set MSBs and sign of result + LD (HL),A ; Save new exponent + DEC HL ; Point to MSB + RET + +OVTST1: CALL TSTSGN ; Test sign of FPREG + CPL ; Invert sign + POP HL ; Clean up stack +OVTST2: OR A ; Test if new exponent zero +OVTST3: POP HL ; Clear off return address + JP P,RESZER ; Result zero + JP OVERR ; Overflow error + +MLSP10: CALL BCDEFP ; Move FPREG to BCDE + LD A,B ; Get exponent + OR A ; Is it zero? + RET Z ; Yes - Result is zero + ADD A,2 ; Multiply by 4 + JP C,OVERR ; Overflow - ?OV Error + LD B,A ; Re-save exponent + CALL FPADD ; Add BCDE to FPREG (Times 5) + LD HL,FPEXP ; Point to exponent + INC (HL) ; Double number (Times 10) + RET NZ ; Ok - Return + JP OVERR ; Overflow error + +TSTSGN: LD A,(FPEXP) ; Get sign of FPREG + OR A + RET Z ; RETurn if number is zero + LD A,(FPREG+2) ; Get MSB of FPREG + .BYTE 0FEH ; Test sign +RETREL: CPL ; Invert sign + RLA ; Sign bit to carry +FLGDIF: SBC A,A ; Carry to all bits of A + RET NZ ; Return -1 if negative + INC A ; Bump to +1 + RET ; Positive - Return +1 + +SGN: CALL TSTSGN ; Test sign of FPREG +FLGREL: LD B,80H+8 ; 8 bit integer in exponent + LD DE,0 ; Zero NMSB and LSB +RETINT: LD HL,FPEXP ; Point to exponent + LD C,A ; CDE = MSB,NMSB and LSB + LD (HL),B ; Save exponent + LD B,0 ; CDE = integer to normalise + INC HL ; Point to sign of result + LD (HL),80H ; Set sign of result + RLA ; Carry = sign of integer + JP CONPOS ; Set sign of result + +ABS: CALL TSTSGN ; Test sign of FPREG + RET P ; Return if positive +INVSGN: LD HL,FPREG+2 ; Point to MSB + LD A,(HL) ; Get sign of mantissa + XOR 80H ; Invert sign of mantissa + LD (HL),A ; Re-save sign of mantissa + RET + +STAKFP: EX DE,HL ; Save code string address + LD HL,(FPREG) ; LSB,NLSB of FPREG + EX (SP),HL ; Stack them,get return + PUSH HL ; Re-save return + LD HL,(FPREG+2) ; MSB and exponent of FPREG + EX (SP),HL ; Stack them,get return + PUSH HL ; Re-save return + EX DE,HL ; Restore code string address + RET + +PHLTFP: CALL LOADFP ; Number at HL to BCDE +FPBCDE: EX DE,HL ; Save code string address + LD (FPREG),HL ; Save LSB,NLSB of number + LD H,B ; Exponent of number + LD L,C ; MSB of number + LD (FPREG+2),HL ; Save MSB and exponent + EX DE,HL ; Restore code string address + RET + +BCDEFP: LD HL,FPREG ; Point to FPREG +LOADFP: LD E,(HL) ; Get LSB of number + INC HL + LD D,(HL) ; Get NMSB of number + INC HL + LD C,(HL) ; Get MSB of number + INC HL + LD B,(HL) ; Get exponent of number +INCHL: INC HL ; Used for conditional "INC HL" + RET + +FPTHL: LD DE,FPREG ; Point to FPREG +DETHL4: LD B,4 ; 4 bytes to move +DETHLB: LD A,(DE) ; Get source + LD (HL),A ; Save destination + INC DE ; Next source + INC HL ; Next destination + DEC B ; Count bytes + JP NZ,DETHLB ; Loop if more + RET + +SIGNS: LD HL,FPREG+2 ; Point to MSB of FPREG + LD A,(HL) ; Get MSB + RLCA ; Old sign to carry + SCF ; Set MSBit + RRA ; Set MSBit of MSB + LD (HL),A ; Save new MSB + CCF ; Complement sign + RRA ; Old sign to carry + INC HL + INC HL + LD (HL),A ; Set sign of result + LD A,C ; Get MSB + RLCA ; Old sign to carry + SCF ; Set MSBit + RRA ; Set MSBit of MSB + LD C,A ; Save MSB + RRA + XOR (HL) ; New sign of result + RET + +CMPNUM: LD A,B ; Get exponent of number + OR A + JP Z,TSTSGN ; Zero - Test sign of FPREG + LD HL,RETREL ; Return relation routine + PUSH HL ; Save for return + CALL TSTSGN ; Test sign of FPREG + LD A,C ; Get MSB of number + RET Z ; FPREG zero - Number's MSB + LD HL,FPREG+2 ; MSB of FPREG + XOR (HL) ; Combine signs + LD A,C ; Get MSB of number + RET M ; Exit if signs different + CALL CMPFP ; Compare FP numbers + RRA ; Get carry to sign + XOR C ; Combine with MSB of number + RET + +CMPFP: INC HL ; Point to exponent + LD A,B ; Get exponent + CP (HL) ; Compare exponents + RET NZ ; Different + DEC HL ; Point to MBS + LD A,C ; Get MSB + CP (HL) ; Compare MSBs + RET NZ ; Different + DEC HL ; Point to NMSB + LD A,D ; Get NMSB + CP (HL) ; Compare NMSBs + RET NZ ; Different + DEC HL ; Point to LSB + LD A,E ; Get LSB + SUB (HL) ; Compare LSBs + RET NZ ; Different + POP HL ; Drop RETurn + POP HL ; Drop another RETurn + RET + +FPINT: LD B,A ; <- Move + LD C,A ; <- exponent + LD D,A ; <- to all + LD E,A ; <- bits + OR A ; Test exponent + RET Z ; Zero - Return zero + PUSH HL ; Save pointer to number + CALL BCDEFP ; Move FPREG to BCDE + CALL SIGNS ; Set MSBs & sign of result + XOR (HL) ; Combine with sign of FPREG + LD H,A ; Save combined signs + CALL M,DCBCDE ; Negative - Decrement BCDE + LD A,80H+24 ; 24 bits + SUB B ; Bits to shift + CALL SCALE ; Shift BCDE + LD A,H ; Get combined sign + RLA ; Sign to carry + CALL C,FPROND ; Negative - Round number up + LD B,0 ; Zero exponent + CALL C,COMPL ; If negative make positive + POP HL ; Restore pointer to number + RET + +DCBCDE: DEC DE ; Decrement BCDE + LD A,D ; Test LSBs + AND E + INC A + RET NZ ; Exit if LSBs not FFFF + DEC BC ; Decrement MSBs + RET + +INT: LD HL,FPEXP ; Point to exponent + LD A,(HL) ; Get exponent + CP 80H+24 ; Integer accuracy only? + LD A,(FPREG) ; Get LSB + RET NC ; Yes - Already integer + LD A,(HL) ; Get exponent + CALL FPINT ; F.P to integer + LD (HL),80H+24 ; Save 24 bit integer + LD A,E ; Get LSB of number + PUSH AF ; Save LSB + LD A,C ; Get MSB of number + RLA ; Sign to carry + CALL CONPOS ; Set sign of result + POP AF ; Restore LSB of number + RET + +MLDEBC: LD HL,0 ; Clear partial product + LD A,B ; Test multiplier + OR C + RET Z ; Return zero if zero + LD A,16 ; 16 bits +MLDBLP: ADD HL,HL ; Shift P.P left + JP C,BSERR ; ?BS Error if overflow + EX DE,HL + ADD HL,HL ; Shift multiplier left + EX DE,HL + JP NC,NOMLAD ; Bit was zero - No add + ADD HL,BC ; Add multiplicand + JP C,BSERR ; ?BS Error if overflow +NOMLAD: DEC A ; Count bits + JP NZ,MLDBLP ; More + RET + +ASCTFP: CP '-' ; Negative? + PUSH AF ; Save it and flags + JP Z,CNVNUM ; Yes - Convert number + CP '+' ; Positive? + JP Z,CNVNUM ; Yes - Convert number + DEC HL ; DEC 'cos GETCHR INCs +CNVNUM: CALL RESZER ; Set result to zero + LD B,A ; Digits after point counter + LD D,A ; Sign of exponent + LD E,A ; Exponent of ten + CPL + LD C,A ; Before or after point flag +MANLP: CALL GETCHR ; Get next character + JP C,ADDIG ; Digit - Add to number + CP '.' + JP Z,DPOINT ; '.' - Flag point + CP 'E' + JP NZ,CONEXP ; Not 'E' - Scale number + CALL GETCHR ; Get next character + CALL SGNEXP ; Get sign of exponent +EXPLP: CALL GETCHR ; Get next character + JP C,EDIGIT ; Digit - Add to exponent + INC D ; Is sign negative? + JP NZ,CONEXP ; No - Scale number + XOR A + SUB E ; Negate exponent + LD E,A ; And re-save it + INC C ; Flag end of number +DPOINT: INC C ; Flag point passed + JP Z,MANLP ; Zero - Get another digit +CONEXP: PUSH HL ; Save code string address + LD A,E ; Get exponent + SUB B ; Subtract digits after point +SCALMI: CALL P,SCALPL ; Positive - Multiply number + JP P,ENDCON ; Positive - All done + PUSH AF ; Save number of times to /10 + CALL DIV10 ; Divide by 10 + POP AF ; Restore count + INC A ; Count divides + +ENDCON: JP NZ,SCALMI ; More to do + POP DE ; Restore code string address + POP AF ; Restore sign of number + CALL Z,INVSGN ; Negative - Negate number + EX DE,HL ; Code string address to HL + RET + +SCALPL: RET Z ; Exit if no scaling needed +MULTEN: PUSH AF ; Save count + CALL MLSP10 ; Multiply number by 10 + POP AF ; Restore count + DEC A ; Count multiplies + RET + +ADDIG: PUSH DE ; Save sign of exponent + LD D,A ; Save digit + LD A,B ; Get digits after point + ADC A,C ; Add one if after point + LD B,A ; Re-save counter + PUSH BC ; Save point flags + PUSH HL ; Save code string address + PUSH DE ; Save digit + CALL MLSP10 ; Multiply number by 10 + POP AF ; Restore digit + SUB '0' ; Make it absolute + CALL RSCALE ; Re-scale number + POP HL ; Restore code string address + POP BC ; Restore point flags + POP DE ; Restore sign of exponent + JP MANLP ; Get another digit + +RSCALE: CALL STAKFP ; Put number on stack + CALL FLGREL ; Digit to add to FPREG +PADD: POP BC ; Restore number + POP DE + JP FPADD ; Add BCDE to FPREG and return + +EDIGIT: LD A,E ; Get digit + RLCA ; Times 2 + RLCA ; Times 4 + ADD A,E ; Times 5 + RLCA ; Times 10 + ADD A,(HL) ; Add next digit + SUB '0' ; Make it absolute + LD E,A ; Save new digit + JP EXPLP ; Look for another digit + +LINEIN: PUSH HL ; Save code string address + LD HL,INMSG ; Output " in " + CALL PRS ; Output string at HL + POP HL ; Restore code string address +PRNTHL: EX DE,HL ; Code string address to DE + XOR A + LD B,80H+24 ; 24 bits + CALL RETINT ; Return the integer + LD HL,PRNUMS ; Print number string + PUSH HL ; Save for return +NUMASC: LD HL,PBUFF ; Convert number to ASCII + PUSH HL ; Save for return + CALL TSTSGN ; Test sign of FPREG + LD (HL),' ' ; Space at start + JP P,SPCFST ; Positive - Space to start + LD (HL),'-' ; '-' sign at start +SPCFST: INC HL ; First byte of number + LD (HL),'0' ; '0' if zero + JP Z,JSTZER ; Return '0' if zero + PUSH HL ; Save buffer address + CALL M,INVSGN ; Negate FPREG if negative + XOR A ; Zero A + PUSH AF ; Save it + CALL RNGTST ; Test number is in range +SIXDIG: LD BC,9143H ; BCDE - 99999.9 + LD DE,4FF8H + CALL CMPNUM ; Compare numbers + OR A + JP PO,INRNG ; > 99999.9 - Sort it out + POP AF ; Restore count + CALL MULTEN ; Multiply by ten + PUSH AF ; Re-save count + JP SIXDIG ; Test it again + +GTSIXD: CALL DIV10 ; Divide by 10 + POP AF ; Get count + INC A ; Count divides + PUSH AF ; Re-save count + CALL RNGTST ; Test number is in range +INRNG: CALL ROUND ; Add 0.5 to FPREG + INC A + CALL FPINT ; F.P to integer + CALL FPBCDE ; Move BCDE to FPREG + LD BC,0306H ; 1E+06 to 1E-03 range + POP AF ; Restore count + ADD A,C ; 6 digits before point + INC A ; Add one + JP M,MAKNUM ; Do it in 'E' form if < 1E-02 + CP 6+1+1 ; More than 999999 ? + JP NC,MAKNUM ; Yes - Do it in 'E' form + INC A ; Adjust for exponent + LD B,A ; Exponent of number + LD A,2 ; Make it zero after + +MAKNUM: DEC A ; Adjust for digits to do + DEC A + POP HL ; Restore buffer address + PUSH AF ; Save count + LD DE,POWERS ; Powers of ten + DEC B ; Count digits before point + JP NZ,DIGTXT ; Not zero - Do number + LD (HL),'.' ; Save point + INC HL ; Move on + LD (HL),'0' ; Save zero + INC HL ; Move on +DIGTXT: DEC B ; Count digits before point + LD (HL),'.' ; Save point in case + CALL Z,INCHL ; Last digit - move on + PUSH BC ; Save digits before point + PUSH HL ; Save buffer address + PUSH DE ; Save powers of ten + CALL BCDEFP ; Move FPREG to BCDE + POP HL ; Powers of ten table + LD B, '0'-1 ; ASCII '0' - 1 +TRYAGN: INC B ; Count subtractions + LD A,E ; Get LSB + SUB (HL) ; Subtract LSB + LD E,A ; Save LSB + INC HL + LD A,D ; Get NMSB + SBC A,(HL) ; Subtract NMSB + LD D,A ; Save NMSB + INC HL + LD A,C ; Get MSB + SBC A,(HL) ; Subtract MSB + LD C,A ; Save MSB + DEC HL ; Point back to start + DEC HL + JP NC,TRYAGN ; No overflow - Try again + CALL PLUCDE ; Restore number + INC HL ; Start of next number + CALL FPBCDE ; Move BCDE to FPREG + EX DE,HL ; Save point in table + POP HL ; Restore buffer address + LD (HL),B ; Save digit in buffer + INC HL ; And move on + POP BC ; Restore digit count + DEC C ; Count digits + JP NZ,DIGTXT ; More - Do them + DEC B ; Any decimal part? + JP Z,DOEBIT ; No - Do 'E' bit +SUPTLZ: DEC HL ; Move back through buffer + LD A,(HL) ; Get character + CP '0' ; '0' character? + JP Z,SUPTLZ ; Yes - Look back for more + CP '.' ; A decimal point? + CALL NZ,INCHL ; Move back over digit + +DOEBIT: POP AF ; Get 'E' flag + JP Z,NOENED ; No 'E' needed - End buffer + LD (HL),'E' ; Put 'E' in buffer + INC HL ; And move on + LD (HL),'+' ; Put '+' in buffer + JP P,OUTEXP ; Positive - Output exponent + LD (HL),'-' ; Put '-' in buffer + CPL ; Negate exponent + INC A +OUTEXP: LD B,'0'-1 ; ASCII '0' - 1 +EXPTEN: INC B ; Count subtractions + SUB 10 ; Tens digit + JP NC,EXPTEN ; More to do + ADD A,'0'+10 ; Restore and make ASCII + INC HL ; Move on + LD (HL),B ; Save MSB of exponent +JSTZER: INC HL ; + LD (HL),A ; Save LSB of exponent + INC HL +NOENED: LD (HL),C ; Mark end of buffer + POP HL ; Restore code string address + RET + +RNGTST: LD BC,9474H ; BCDE = 999999. + LD DE,23F7H + CALL CMPNUM ; Compare numbers + OR A + POP HL ; Return address to HL + JP PO,GTSIXD ; Too big - Divide by ten + JP (HL) ; Otherwise return to caller + +HALF: .BYTE 00H,00H,00H,80H ; 0.5 + +POWERS: .BYTE 0A0H,086H,001H ; 100000 + .BYTE 010H,027H,000H ; 10000 + .BYTE 0E8H,003H,000H ; 1000 + .BYTE 064H,000H,000H ; 100 + .BYTE 00AH,000H,000H ; 10 + .BYTE 001H,000H,000H ; 1 + +NEGAFT: LD HL,INVSGN ; Negate result + EX (SP),HL ; To be done after caller + JP (HL) ; Return to caller + +SQR: CALL STAKFP ; Put value on stack + LD HL,HALF ; Set power to 1/2 + CALL PHLTFP ; Move 1/2 to FPREG + +POWER: POP BC ; Get base + POP DE + CALL TSTSGN ; Test sign of power + LD A,B ; Get exponent of base + JP Z,EXP ; Make result 1 if zero + JP P,POWER1 ; Positive base - Ok + OR A ; Zero to negative power? + JP Z,DZERR ; Yes - ?/0 Error +POWER1: OR A ; Base zero? + JP Z,SAVEXP ; Yes - Return zero + PUSH DE ; Save base + PUSH BC + LD A,C ; Get MSB of base + OR 01111111B ; Get sign status + CALL BCDEFP ; Move power to BCDE + JP P,POWER2 ; Positive base - Ok + PUSH DE ; Save power + PUSH BC + CALL INT ; Get integer of power + POP BC ; Restore power + POP DE + PUSH AF ; MSB of base + CALL CMPNUM ; Power an integer? + POP HL ; Restore MSB of base + LD A,H ; but don't affect flags + RRA ; Exponent odd or even? +POWER2: POP HL ; Restore MSB and exponent + LD (FPREG+2),HL ; Save base in FPREG + POP HL ; LSBs of base + LD (FPREG),HL ; Save in FPREG + CALL C,NEGAFT ; Odd power - Negate result + CALL Z,INVSGN ; Negative base - Negate it + PUSH DE ; Save power + PUSH BC + CALL LOG ; Get LOG of base + POP BC ; Restore power + POP DE + CALL FPMULT ; Multiply LOG by power + +EXP: CALL STAKFP ; Put value on stack + LD BC,08138H ; BCDE = 1/Ln(2) + LD DE,0AA3BH + CALL FPMULT ; Multiply value by 1/LN(2) + LD A,(FPEXP) ; Get exponent + CP 80H+8 ; Is it in range? + JP NC,OVTST1 ; No - Test for overflow + CALL INT ; Get INT of FPREG + ADD A,80H ; For excess 128 + ADD A,2 ; Exponent > 126? + JP C,OVTST1 ; Yes - Test for overflow + PUSH AF ; Save scaling factor + LD HL,UNITY ; Point to 1. + CALL ADDPHL ; Add 1 to FPREG + CALL MULLN2 ; Multiply by LN(2) + POP AF ; Restore scaling factor + POP BC ; Restore exponent + POP DE + PUSH AF ; Save scaling factor + CALL SUBCDE ; Subtract exponent from FPREG + CALL INVSGN ; Negate result + LD HL,EXPTAB ; Coefficient table + CALL SMSER1 ; Sum the series + LD DE,0 ; Zero LSBs + POP BC ; Scaling factor + LD C,D ; Zero MSB + JP FPMULT ; Scale result to correct value + +EXPTAB: .BYTE 8 ; Table used by EXP + .BYTE 040H,02EH,094H,074H ; -1/7! (-1/5040) + .BYTE 070H,04FH,02EH,077H ; 1/6! ( 1/720) + .BYTE 06EH,002H,088H,07AH ; -1/5! (-1/120) + .BYTE 0E6H,0A0H,02AH,07CH ; 1/4! ( 1/24) + .BYTE 050H,0AAH,0AAH,07EH ; -1/3! (-1/6) + .BYTE 0FFH,0FFH,07FH,07FH ; 1/2! ( 1/2) + .BYTE 000H,000H,080H,081H ; -1/1! (-1/1) + .BYTE 000H,000H,000H,081H ; 1/0! ( 1/1) + +SUMSER: CALL STAKFP ; Put FPREG on stack + LD DE,MULT ; Multiply by "X" + PUSH DE ; To be done after + PUSH HL ; Save address of table + CALL BCDEFP ; Move FPREG to BCDE + CALL FPMULT ; Square the value + POP HL ; Restore address of table +SMSER1: CALL STAKFP ; Put value on stack + LD A,(HL) ; Get number of coefficients + INC HL ; Point to start of table + CALL PHLTFP ; Move coefficient to FPREG + .BYTE 06H ; Skip "POP AF" +SUMLP: POP AF ; Restore count + POP BC ; Restore number + POP DE + DEC A ; Cont coefficients + RET Z ; All done + PUSH DE ; Save number + PUSH BC + PUSH AF ; Save count + PUSH HL ; Save address in table + CALL FPMULT ; Multiply FPREG by BCDE + POP HL ; Restore address in table + CALL LOADFP ; Number at HL to BCDE + PUSH HL ; Save address in table + CALL FPADD ; Add coefficient to FPREG + POP HL ; Restore address in table + JP SUMLP ; More coefficients + +RND: CALL TSTSGN ; Test sign of FPREG + LD HL,SEED+2 ; Random number seed + JP M,RESEED ; Negative - Re-seed + LD HL,LSTRND ; Last random number + CALL PHLTFP ; Move last RND to FPREG + LD HL,SEED+2 ; Random number seed + RET Z ; Return if RND(0) + ADD A,(HL) ; Add (SEED)+2) + AND 00000111B ; 0 to 7 + LD B,0 + LD (HL),A ; Re-save seed + INC HL ; Move to coefficient table + ADD A,A ; 4 bytes + ADD A,A ; per entry + LD C,A ; BC = Offset into table + ADD HL,BC ; Point to coefficient + CALL LOADFP ; Coefficient to BCDE + CALL FPMULT ; Multiply FPREG by coefficient + LD A,(SEED+1) ; Get (SEED+1) + INC A ; Add 1 + AND 00000011B ; 0 to 3 + LD B,0 + CP 1 ; Is it zero? + ADC A,B ; Yes - Make it 1 + LD (SEED+1),A ; Re-save seed + LD HL,RNDTAB-4 ; Addition table + ADD A,A ; 4 bytes + ADD A,A ; per entry + LD C,A ; BC = Offset into table + ADD HL,BC ; Point to value + CALL ADDPHL ; Add value to FPREG +RND1: CALL BCDEFP ; Move FPREG to BCDE + LD A,E ; Get LSB + LD E,C ; LSB = MSB + XOR 01001111B ; Fiddle around + LD C,A ; New MSB + LD (HL),80H ; Set exponent + DEC HL ; Point to MSB + LD B,(HL) ; Get MSB + LD (HL),80H ; Make value -0.5 + LD HL,SEED ; Random number seed + INC (HL) ; Count seed + LD A,(HL) ; Get seed + SUB 171 ; Do it modulo 171 + JP NZ,RND2 ; Non-zero - Ok + LD (HL),A ; Zero seed + INC C ; Fillde about + DEC D ; with the + INC E ; number +RND2: CALL BNORM ; Normalise number + LD HL,LSTRND ; Save random number + JP FPTHL ; Move FPREG to last and return + +RESEED: LD (HL),A ; Re-seed random numbers + DEC HL + LD (HL),A + DEC HL + LD (HL),A + JP RND1 ; Return RND seed + +RNDTAB: .BYTE 068H,0B1H,046H,068H ; Table used by RND + .BYTE 099H,0E9H,092H,069H + .BYTE 010H,0D1H,075H,068H + +COS: LD HL,HALFPI ; Point to PI/2 + CALL ADDPHL ; Add it to PPREG +SIN: CALL STAKFP ; Put angle on stack + LD BC,8349H ; BCDE = 2 PI + LD DE,0FDBH + CALL FPBCDE ; Move 2 PI to FPREG + POP BC ; Restore angle + POP DE + CALL DVBCDE ; Divide angle by 2 PI + CALL STAKFP ; Put it on stack + CALL INT ; Get INT of result + POP BC ; Restore number + POP DE + CALL SUBCDE ; Make it 0 <= value < 1 + LD HL,QUARTR ; Point to 0.25 + CALL SUBPHL ; Subtract value from 0.25 + CALL TSTSGN ; Test sign of value + SCF ; Flag positive + JP P,SIN1 ; Positive - Ok + CALL ROUND ; Add 0.5 to value + CALL TSTSGN ; Test sign of value + OR A ; Flag negative +SIN1: PUSH AF ; Save sign + CALL P,INVSGN ; Negate value if positive + LD HL,QUARTR ; Point to 0.25 + CALL ADDPHL ; Add 0.25 to value + POP AF ; Restore sign + CALL NC,INVSGN ; Negative - Make positive + LD HL,SINTAB ; Coefficient table + JP SUMSER ; Evaluate sum of series + +HALFPI: .BYTE 0DBH,00FH,049H,081H ; 1.5708 (PI/2) + +QUARTR: .BYTE 000H,000H,000H,07FH ; 0.25 + +SINTAB: .BYTE 5 ; Table used by SIN + .BYTE 0BAH,0D7H,01EH,086H ; 39.711 + .BYTE 064H,026H,099H,087H ;-76.575 + .BYTE 058H,034H,023H,087H ; 81.602 + .BYTE 0E0H,05DH,0A5H,086H ;-41.342 + .BYTE 0DAH,00FH,049H,083H ; 6.2832 + +TAN: CALL STAKFP ; Put angle on stack + CALL SIN ; Get SIN of angle + POP BC ; Restore angle + POP HL + CALL STAKFP ; Save SIN of angle + EX DE,HL ; BCDE = Angle + CALL FPBCDE ; Angle to FPREG + CALL COS ; Get COS of angle + JP DIV ; TAN = SIN / COS + +ATN: CALL TSTSGN ; Test sign of value + CALL M,NEGAFT ; Negate result after if -ve + CALL M,INVSGN ; Negate value if -ve + LD A,(FPEXP) ; Get exponent + CP 81H ; Number less than 1? + JP C,ATN1 ; Yes - Get arc tangnt + LD BC,8100H ; BCDE = 1 + LD D,C + LD E,C + CALL DVBCDE ; Get reciprocal of number + LD HL,SUBPHL ; Sub angle from PI/2 + PUSH HL ; Save for angle > 1 +ATN1: LD HL,ATNTAB ; Coefficient table + CALL SUMSER ; Evaluate sum of series + LD HL,HALFPI ; PI/2 - angle in case > 1 + RET ; Number > 1 - Sub from PI/2 + +ATNTAB: .BYTE 9 ; Table used by ATN + .BYTE 04AH,0D7H,03BH,078H ; 1/17 + .BYTE 002H,06EH,084H,07BH ;-1/15 + .BYTE 0FEH,0C1H,02FH,07CH ; 1/13 + .BYTE 074H,031H,09AH,07DH ;-1/11 + .BYTE 084H,03DH,05AH,07DH ; 1/9 + .BYTE 0C8H,07FH,091H,07EH ;-1/7 + .BYTE 0E4H,0BBH,04CH,07EH ; 1/5 + .BYTE 06CH,0AAH,0AAH,07FH ;-1/3 + .BYTE 000H,000H,000H,081H ; 1/1 + +ARET: RET ; A RETurn instruction +; +#IF VDUGFX +; +; GETXYA +; Decode the x,y pixel coordinate from BASIC +; Convert pixel coordinate to character coordinate and create the graphic character mask. +; Convert character coordinate to screen address +; +; On exit, A = Graphic character byte mask. HL= screen address. +; Calling routine set, reset, point uses these to update screen. +; +GETXYA: CALL CHKSYN ; Make sure "(" follows + .DB "(" + CALL GETNUM ; Get a number + CALL DEINT ; Get integer -32768 to 32767 + PUSH DE ; Save "X" + CALL CHKSYN ; Make sure "," follows + .DB "," + CALL GETNUM ; Get a number + CALL CHKSYN ; Make sure ")" follows + .DB ")" + CALL DEINT ; Get integer -32768 to 32767 + PUSH HL ; Save code string address + POP IY ; In IY +; +; Pixel column (0-159) is on the stack. DE contains the pixel row (0-74) +; Convert X,Y Pixels co-ord. to X,Y character co-ord. Create Byte mask +; + POP HL ; Get the pixel column + LD A,L ; Change HL from a pixel + SRL A ; column to a character column + LD L,A ; by dividing by two. + SBC A,A ; If the pixel column is even + INC A ; then set the byte mask to 00000010 + ADC A,A ; if it is odd set mask to 00000001 + PUSH HL ; Save the character column + + LD HL,(VDUROWS*3)-1; Get row to HL + SBC HL,DE ; C=0 from above + LD DE,-1 ; Zero line count + LD BC,3 ; 3 blocks per line +DIV3LP: SBC HL,BC ; Subtract 3 + INC DE ; Count the subtractions + JP P,DIV3LP ; More to do + + OR A ; HL is the remainder which defines + DEC BC ; which pixel row the mask should be on + ADC HL,BC ; HL = -1, 0, 1 and flags are set + + JR Z,BY4 ; Byte mask is in A and + JP P,ROW0SKP ; set for pixel row 0 + RLCA ; Move the mask to + RLCA ; pixel row 1 or 2 +BY4: RLCA ; based on the remainder + RLCA ; calculate above in HL +ROW0SKP:OR 10000000B ; Convert Byte mask (0-63) to a font character (128-192) +; +; A=Byte mask, stack = character column (0-79). DE contains character row (0-24) +; Check for valid character co-ord and calculate screen address +; + POP HL ; Character column + PUSH AF ; Save byte mask + + LD A,H ; Column high byte to A + OR A ; Error if negative + JP NZ,FCERR ; or >255 + LD A,L ; Column low byte to A + CP VDUCOLS ; Error if out of + JP P,FCERR ; Range + + LD B,E ; Rows to B + INC B + LD E,L ; Columns to E + + LD HL,-(VDUCOLS) ; Base VDU address + ADD HL,DE ; Add column to address + LD DE,VDUCOLS ; Line to DE +ADD80X: ADD HL,DE ; Multiply by lines + DJNZ ADD80X +; + POP AF ; Restore byte mask + RET +; +SETB: CALL GETXYA ; Get co-ords and VDU address + PUSH AF ; Save bit mask + CALL VDU_RDHL ; Get character from screen + CP 192 ; Is it a block graphic? + JR NC,NOTBLK1 + CP 128 + JR NC,SETOR ; Yes - OR new bit +NOTBLK1:POP AF ; Restore bit mask +PUTBIT: CALL VDU_WRHL ; Put character on screen +RESCSA: PUSH IY ; Restore code string address + POP HL ; From IY + RET + +SETOR: POP BC ; Restore bit mask + OR B ; Merge the bits + JR PUTBIT ; Save on screen + +RESETB: CALL GETXYA ; Get co-ords and VDU address + LD D,A ; Save byte mask + CALL VDU_RDHL ; Get character from screen + LD E,A ; Save current character + PUSH DE + CP 192 ; If it is not a block + JR C,NOTBLK2 ; change it to a blank + CP 128 + JR C,NOTBLK2 + XOR A +NOTBLK2:LD B,00111111B ; Six bits per block + AND B ; Clear bits 7 & 6 + POP BC ; Get bit mask + AND B ; Test for common bit + JR Z,RESCSA ; None - Leave it + LD A,E ; Recall byte from screen + AND 00111111B ; Isolate bit + XOR B ; Clear that bit + JR PUTBIT ; Save the space + +NORES: POP BC ; Drop bit mask + JR RESCSA ; Restore code string address + +POINTB: CALL GETXYA ; Get co-ords and VDU address + PUSH AF + CALL VDU_RDHL ; Get character from screen + LD B,A + POP AF + CALL TSTBIT ; Test if bit is set + JP NZ,POINT0 ; Different - Return zero + LD A,0 + LD B,1 ; Integer AB = 1 +POINTX: POP HL ; Drop return + PUSH IY ; PUSH code string address + LD DE,RETNUM ; To return a number + PUSH DE ; Save for return + JP ABPASS ; Return integer AB + +POINT0: LD B,0 ; Set zero + JP POINTX ; Return value +; +;---------------------------------------------------------------------- +; INITIALIZE VDU +;---------------------------------------------------------------------- +; +VDU_INIT: + PUSH BC + PUSH HL + LD C,10 ; SET CURSOR OFF + LD A,00100000B + CALL VDU_WRREG +; LD HL,0 ; SET SCREEN START ADDRESS +; LD C,12 +; CALL VDU_WRREGX +; LD C,14 ; SET CURSOR START ADDRESS +; CALL VDU_WRREGX +; + LD HL,(VDUROWS*VDUCOLS) ; CLEAR SCREEN +VDU_FILL: + LD C,18 + CALL VDU_WRREGX + LD A,31 + OUT (VDUREG),A + CALL VDU_WAITRDY ; WAIT FOR VDU TO BE READY + LD A,' ' + OUT (VDURWR),A + LD A,H + OR L + DEC HL + JR NZ,VDU_FILL + POP HL + POP BC + RET +; +;---------------------------------------------------------------------- +; WAIT FOR VDU TO BE READY FOR A DATA READ/WRITE +;---------------------------------------------------------------------- +; +VDU_WAITRDY: + IN A,(VDUSTS) ; READ STATUS + OR A ; SET FLAGS + RET M ; IF BIT 7 SET, THEN READY! + JR VDU_WAITRDY ; KEEP CHECKING +; +;---------------------------------------------------------------------- +; UPDATE SY6845 REGISTERS +; VDU_WRREG WRITES VALUE IN A TO VDU REGISTER SPECIFIED IN C +; VDU_WRREGX WRITES VALUE IN HL TO VDU REGISTER PAIR IN C, C+1 +;---------------------------------------------------------------------- +; +VDU_WRREG: + PUSH AF ; SAVE VALUE TO WRITE + LD A,C ; SET A TO VDU REGISTER TO SELECT + OUT (VDUREG),A ; WRITE IT TO SELECT THE REGISTER + POP AF ; RECOVER VALUE TO WRITE + OUT (VDUDTA),A ; WRITE IT + RET +; +VDU_WRREGX: + LD A,H ; SETUP MSB TO WRITE + CALL VDU_WRREG ; DO IT + INC C ; NEXT VDU REGISTER + LD A,L ; SETUP LSB TO WRITE + JR VDU_WRREG ; DO IT & RETURN +; +VDU_RDHL: + LD C,18 ; READ A BYTE + CALL VDU_WRREGX ; FROM VIDEO MEMORY + LD A,31 ; POINTED TO BY HL + OUT (VDUREG),A ; AND RETURN IT IN A + CALL VDU_WAITRDY + IN A,(VDURRD) + RET +; +VDU_WRHL: + PUSH AF ; WRITE A BYTE IN A + LD C,18 ; TO VIDEO MEMORY + CALL VDU_WRREGX ; POINTED TO BY HL + LD A,31 + OUT (VDUREG),A + CALL VDU_WAITRDY + POP AF + OUT (VDURWR),A + RET +#ENDIF +; INPUT CHARACTER FROM CONSOLE VIA HBIOS + +GETINP: + PUSH BC + PUSH DE + PUSH HL + LD C,CIO_CONSOLE ; CONSOLE UNIT TO C + LD B,BF_CIOIN ; HBIOS FUNC: INPUT CHAR + RST 08 ; HBIOS READS CHARACTDR + LD A,E ; MOVE CHARACTER TO A FOR RETURN + POP HL ; RESTORE REGISTERS (AF IS OUTPUT) + POP DE + POP BC + RET +CLS: +#IF VDUGFX + CALL VDU_INIT ; Clear VDU screen +#ENDIF +#IF VT100 + PUSH HL + LD HL,VT_CLS ; Output zero terminated +VT0OUT: LD A,(HL) ; VT100 escape sequence + INC HL ; directly to console. + OR A + CALL NZ,MONOUT ; clear screen + JR NZ,VT0OUT ; and home cursor + POP HL + RET +#ELSE + LD A,CS ; ASCII Clear screen + JP MONOUT ; Output character +#ENDIF + +WIDTH: CALL GETINT ; Get integer 0-255 + LD A,E ; Width to A + LD (LWIDTH),A ; Set width + RET + +LINES: CALL GETNUM ; Get a number + CALL DEINT ; Get integer -32768 to 32767 + LD (LINESC),DE ; Set lines counter + LD (LINESN),DE ; Set lines number + RET + +DEEK: CALL DEINT ; Get integer -32768 to 32767 + PUSH DE ; Save number + POP HL ; Number to HL + LD B,(HL) ; Get LSB of contents + INC HL + LD A,(HL) ; Get MSB of contents + JP ABPASS ; Return integer AB + +DOKE: CALL GETNUM ; Get a number + CALL DEINT ; Get integer -32768 to 32767 + PUSH DE ; Save address + CALL CHKSYN ; Make sure ',' follows + .BYTE ',' + CALL GETNUM ; Get a number + CALL DEINT ; Get integer -32768 to 32767 + EX (SP),HL ; Save value,get address + LD (HL),E ; Save LSB of value + INC HL + LD (HL),D ; Save MSB of value + POP HL ; Restore code string address + RET ; HEX$(nn) Convert 16 bit number to Hexadecimal string -HEX: CALL TSTNUM ; Verify it's a number - CALL DEINT ; Get integer -32768 to 32767 - PUSH BC ; Save contents of BC - LD HL,PBUFF - LD A,D ; Get high order into A - CP $0 - JR Z,HEX2 ; Skip output if both high digits are zero - CALL BYT2ASC ; Convert D to ASCII - LD A,B - CP '0' - JR Z,HEX1 ; Don't store high digit if zero - LD (HL),B ; Store it to PBUFF - INC HL ; Next location -HEX1: LD (HL),C ; Store C to PBUFF+1 - INC HL ; Next location -HEX2: LD A,E ; Get lower byte - CALL BYT2ASC ; Convert E to ASCII - LD A,D - CP $0 - JR NZ,HEX3 ; If upper byte was not zero then always print lower byte - LD A,B - CP '0' ; If high digit of lower byte is zero then don't print - JR Z,HEX4 -HEX3: LD (HL),B ; to PBUFF+2 - INC HL ; Next location -HEX4: LD (HL),C ; to PBUFF+3 - INC HL ; PBUFF+4 to zero - XOR A ; Terminating character - LD (HL),A ; Store zero to terminate - INC HL ; Make sure PBUFF is terminated - LD (HL),A ; Store the double zero there - POP BC ; Get BC back - LD HL,PBUFF ; Reset to start of PBUFF - JP STR1 ; Convert the PBUFF to a string and return it - -BYT2ASC LD B,A ; Save original value - AND $0F ; Strip off upper nybble - CP $0A ; 0-9? - JR C,ADD30 ; If A-F, add 7 more - ADD A,$07 ; Bring value up to ASCII A-F -ADD30 ADD A,$30 ; And make ASCII - LD C,A ; Save converted char to C - LD A,B ; Retrieve original value - RRCA ; and Rotate it right - RRCA - RRCA - RRCA - AND $0F ; Mask off upper nybble - CP $0A ; 0-9? < A hex? - JR C,ADD301 ; Skip Add 7 - ADD A,$07 ; Bring it up to ASCII A-F -ADD301 ADD A,$30 ; And make it full ASCII - LD B,A ; Store high order byte - RET - +HEX: CALL TSTNUM ; Verify it's a number + CALL DEINT ; Get integer -32768 to 32767 + PUSH BC ; Save contents of BC + LD HL,PBUFF + LD A,D ; Get high order into A + CP $0 + JR Z,HEX2 ; Skip output if both high digits are zero + CALL BYT2ASC ; Convert D to ASCII + LD A,B + CP '0' + JR Z,HEX1 ; Don't store high digit if zero + LD (HL),B ; Store it to PBUFF + INC HL ; Next location +HEX1: LD (HL),C ; Store C to PBUFF+1 + INC HL ; Next location +HEX2: LD A,E ; Get lower byte + CALL BYT2ASC ; Convert E to ASCII + LD A,D + CP $0 + JR NZ,HEX3 ; If upper byte was not zero then always print lower byte + LD A,B + CP '0' ; If high digit of lower byte is zero then don't print + JR Z,HEX4 +HEX3: LD (HL),B ; to PBUFF+2 + INC HL ; Next location +HEX4: LD (HL),C ; to PBUFF+3 + INC HL ; PBUFF+4 to zero + XOR A ; Terminating character + LD (HL),A ; Store zero to terminate + INC HL ; Make sure PBUFF is terminated + LD (HL),A ; Store the double zero there + POP BC ; Get BC back + LD HL,PBUFF ; Reset to start of PBUFF + JP STR1 ; Convert the PBUFF to a string and return it + +BYT2ASC LD B,A ; Save original value + AND $0F ; Strip off upper nybble + CP $0A ; 0-9? + JR C,ADD30 ; If A-F, add 7 more + ADD A,$07 ; Bring value up to ASCII A-F +ADD30 ADD A,$30 ; And make ASCII + LD C,A ; Save converted char to C + LD A,B ; Retrieve original value + RRCA ; and Rotate it right + RRCA + RRCA + RRCA + AND $0F ; Mask off upper nybble + CP $0A ; 0-9? < A hex? + JR C,ADD301 ; Skip Add 7 + ADD A,$07 ; Bring it up to ASCII A-F +ADD301 ADD A,$30 ; And make it full ASCII + LD B,A ; Store high order byte + RET +; ; Convert "&Hnnnn" to FPREG ; Gets a character from (HL) checks for Hexadecimal ASCII numbers "&Hnnnn" ; Char is in A, NC if char is ;<=>?@ A-z, CY is set if 0-9 -HEXTFP EX DE,HL ; Move code string pointer to DE - LD HL,$0000 ; Zero out the value - CALL GETHEX ; Check the number for valid hex - JP C,HXERR ; First value wasn't hex, HX error - JR HEXLP1 ; Convert first character -HEXLP CALL GETHEX ; Get second and addtional characters - JR C,HEXIT ; Exit if not a hex character -HEXLP1 ADD HL,HL ; Rotate 4 bits to the left - ADD HL,HL - ADD HL,HL - ADD HL,HL - OR L ; Add in D0-D3 into L - LD L,A ; Save new value - JR HEXLP ; And continue until all hex characters are in - -GETHEX INC DE ; Next location - LD A,(DE) ; Load character at pointer - CP ' ' - JP Z,GETHEX ; Skip spaces - SUB $30 ; Get absolute value - RET C ; < "0", error - CP $0A - JR C,NOSUB7 ; Is already in the range 0-9 - SUB $07 ; Reduce to A-F - CP $0A ; Value should be $0A-$0F at this point - RET C ; CY set if was : ; < = > ? @ -NOSUB7 CP $10 ; > Greater than "F"? - CCF - RET ; CY set if it wasn't valid hex - -HEXIT EX DE,HL ; Value into DE, Code string into HL - LD A,D ; Load DE into AC - LD C,E ; For prep to - PUSH HL - CALL ACPASS ; ACPASS to set AC as integer into FPREG - POP HL - RET - -HXERR: LD E,HX ; ?HEX Error - JP ERROR - +; +HEXTFP EX DE,HL ; Move code string pointer to DE + LD HL,$0000 ; Zero out the value + CALL GETHEX ; Check the number for valid hex + JP C,HXERR ; First value wasn't hex, HX error + JR HEXLP1 ; Convert first character +HEXLP CALL GETHEX ; Get second and addtional characters + JR C,HEXIT ; Exit if not a hex character +HEXLP1 ADD HL,HL ; Rotate 4 bits to the left + ADD HL,HL + ADD HL,HL + ADD HL,HL + OR L ; Add in D0-D3 into L + LD L,A ; Save new value + JR HEXLP ; And continue until all hex characters are in + +GETHEX INC DE ; Next location + LD A,(DE) ; Load character at pointer + CP ' ' + JP Z,GETHEX ; Skip spaces + SUB $30 ; Get absolute value + RET C ; < "0", error + CP $0A + JR C,NOSUB7 ; Is already in the range 0-9 + SUB $07 ; Reduce to A-F + CP $0A ; Value should be $0A-$0F at this point + RET C ; CY set if was : ; < = > ? @ +NOSUB7 CP $10 ; > Greater than "F"? + CCF + RET ; CY set if it wasn't valid hex + +HEXIT EX DE,HL ; Value into DE, Code string into HL + LD A,D ; Load DE into AC + LD C,E ; For prep to + PUSH HL + CALL ACPASS ; ACPASS to set AC as integer into FPREG + POP HL + RET + +HXERR: LD E,HX ; ?HEX Error + JP ERROR +; ; BIN$(NN) Convert integer to a 1-16 char binary string -BIN: CALL TSTNUM ; Verify it's a number - CALL DEINT ; Get integer -32768 to 32767 -BIN2: PUSH BC ; Save contents of BC - LD HL,PBUFF - LD B,17 ; One higher than max char count -ZEROSUP: ; Suppress leading zeros - DEC B ; Max 16 chars - LD A,B - CP $01 - JR Z,BITOUT ; Always output at least one character - RL E - RL D - JR NC,ZEROSUP - JR BITOUT2 -BITOUT: - RL E - RL D ; Top bit now in carry +; +BIN: CALL TSTNUM ; Verify it's a number + CALL DEINT ; Get integer -32768 to 32767 +BIN2: PUSH BC ; Save contents of BC + LD HL,PBUFF + LD B,17 ; One higher than max char count +ZEROSUP: ; Suppress leading zeros + DEC B ; Max 16 chars + LD A,B + CP $01 + JR Z,BITOUT ; Always output at least one character + RL E + RL D + JR NC,ZEROSUP + JR BITOUT2 +BITOUT: + RL E + RL D ; Top bit now in carry BITOUT2: - LD A,'0' ; Char for '0' - ADC A,0 ; If carry set then '0' --> '1' - LD (HL),A - INC HL - DEC B - JR NZ,BITOUT - XOR A ; Terminating character - LD (HL),A ; Store zero to terminate - INC HL ; Make sure PBUFF is terminated - LD (HL),A ; Store the double zero there - POP BC - LD HL,PBUFF - JP STR1 - + LD A,'0' ; Char for '0' + ADC A,0 ; If carry set then '0' --> '1' + LD (HL),A + INC HL + DEC B + JR NZ,BITOUT + XOR A ; Terminating character + LD (HL),A ; Store zero to terminate + INC HL ; Make sure PBUFF is terminated + LD (HL),A ; Store the double zero there + POP BC + LD HL,PBUFF + JP STR1 +; ; Convert "&Bnnnn" to FPREG ; Gets a character from (HL) checks for Binary ASCII numbers "&Bnnnn" -BINTFP: EX DE,HL ; Move code string pointer to DE - LD HL,$0000 ; Zero out the value - CALL CHKBIN ; Check the number for valid bin - JP C,BINERR ; First value wasn't bin, HX error -BINIT: SUB '0' - ADD HL,HL ; Rotate HL left - OR L - LD L,A - CALL CHKBIN ; Get second and addtional characters - JR NC,BINIT ; Process if a bin character - EX DE,HL ; Value into DE, Code string into HL - LD A,D ; Load DE into AC - LD C,E ; For prep to - PUSH HL - CALL ACPASS ; ACPASS to set AC as integer into FPREG - POP HL - RET +; +BINTFP: EX DE,HL ; Move code string pointer to DE + LD HL,$0000 ; Zero out the value + CALL CHKBIN ; Check the number for valid bin + JP C,BINERR ; First value wasn't bin, HX error +BINIT: SUB '0' + ADD HL,HL ; Rotate HL left + OR L + LD L,A + CALL CHKBIN ; Get second and addtional characters + JR NC,BINIT ; Process if a bin character + EX DE,HL ; Value into DE, Code string into HL + LD A,D ; Load DE into AC + LD C,E ; For prep to + PUSH HL + CALL ACPASS ; ACPASS to set AC as integer into FPREG + POP HL + RET ; Char is in A, NC if char is 0 or 1 -CHKBIN: INC DE - LD A,(DE) - CP ' ' - JP Z,CHKBIN ; Skip spaces - CP '0' ; Set C if < '0' - RET C - CP '2' - CCF ; Set C if > '1' - RET - -BINERR: LD E,BN ; ?BIN Error - JP ERROR - - -JJUMP1: - LD IX,-1 ; Flag cold start - JP CSTART ; Go and initialise - -MONOUT: - ; SAVE ALL INCOMING REGISTERS - PUSH AF - PUSH BC - PUSH DE - PUSH HL - ; OUTPUT CHARACTER TO CONSOLE VIA HBIOS - LD E,A ; OUTPUT CHAR TO E - LD C,CIODEV_CONSOLE; CONSOLE UNIT TO C - LD B,BF_CIOOUT ; HBIOS FUNC: OUTPUT CHAR - RST 08 ; HBIOS OUTPUTS CHARACTDR - ; RESTORE ALL REGISTERS - POP HL - POP DE - POP BC - POP AF - RET - -MONITR: LD A,BID_BOOT ; BOOT BANK - LD HL,0 ; ADDRESS ZERO - CALL HB_BNKCALL ; DOES NOT RETURN - -INITST: LD A,0 ; Clear break flag - LD (BRKFLG),A - JP INIT - -ARETN: RETN ; Return from NMI - - -TSTBIT: PUSH AF ; Save bit mask - AND B ; Get common bits - POP BC ; Restore bit mask - CP B ; Same bit set? - LD A,0 ; Return 0 in A - RET - -OUTNCR: CALL OUTC ; Output character in A - JP PRNTCRLF ; Output CRLF - -TXT_READY: - .DB CR,LF - .TEXT "BASIC READY " - .DB CR,LF,0FFH - -SLACK .EQU (BAS_END - $) - .FILL SLACK,00H +CHKBIN: INC DE + LD A,(DE) + CP ' ' + JP Z,CHKBIN ; Skip spaces + CP '0' ; Set C if < '0' + RET C + CP '2' + CCF ; Set C if > '1' + RET + +BINERR: LD E,BN ; ?BIN Error + JP ERROR ; -BAS_STACK .EQU $ +JJUMP1: + LD IX,-1 ; Flag cold start + JP CSTART ; Go and initialise + +; OUTPUT CHARACTER A TO CONSOLE VIA HBIOS + +MONOUT: + PUSH AF ; SAVE ALL INCOMING REGISTERS + PUSH BC + PUSH DE + PUSH HL + LD E,A ; OUTPUT CHAR TO E + LD C,CIO_CONSOLE ; CONSOLE UNIT TO C + LD B,BF_CIOOUT ; HBIOS FUNC: OUTPUT CHAR + RST 08 ; HBIOS OUTPUTS CHARACTDR + POP HL ; RESTORE ALL REGISTERS + POP DE + POP BC + POP AF + RET + +MONITR: LD A,BID_BOOT ; BOOT BANK + LD HL,0 ; ADDRESS ZERO + CALL HB_BNKCALL ; DOES NOT RETURN + +INITST: LD A,0 ; Clear break flag + LD (BRKFLG),A + CALL SET_DUR_TBL ; SET UP SOUND TABLE +#IF VDUGFX + CALL VDU_INIT ; SET AND CLEAR VDU SCREEN 0 +#ENDIF + JP INIT + +ARETN: RETN ; Return from NMI + +TSTBIT: PUSH AF ; Save bit mask + AND B ; Get common bits + POP BC ; Restore bit mask + CP B ; Same bit set? + LD A,0 ; Return 0 in A + RET + +OUTNCR: CALL OUTC ; Output character in A + JP PRNTCRLF ; Output CRLF + +; --------------------------------------------------------------------------------------- + +; PLAY O,N,D ; PLAY OCTAVE 0-8, NOTE N (0-11), DURATION (1-8) [1/8 - 8/8 SECONDS] + +PLAY: CALL GETINT ; GET OCTAVE + PUSH AF ; AND SAVE + + CALL CHKSYN ; Make sure ',' follows + .BYTE ',' + + CALL GETINT ; GET NOTE + PUSH HL ; SAVE SYNTAX POINTER + LD L,A + LD H,0 + ADD HL,HL ; X2 + ADD HL,HL ; X4 + LD DE,FRQDURTBL ; POINT TO NOTE ENTRY + ADD HL,DE ; ITS IN HL + + EX (SP),HL ; RESTORE SYNTAX POINTER + ; IN HL. NOTE PTR ON STACK + + CALL CHKSYN ; Make sure ',' follows + .BYTE ',' + CALL GETINT ; GET DURATION + + POP DE ; GET NOTE PTR IN DE + EX (SP),HL ; GET OCTAVE IN HL. SYNTAX POINTER ON STACK + EX DE,HL ; PUT NOTE PTR IN HL, OCTAVE IN DE + + PUSH BC ; SAVE SYNTAX POINTER + LD B,D ; SAVE OCTAVE + PUSH AF ; SAVE DURATION + + LD A,(HL) ; LOAD 1ST ARG + INC HL ; IN DE + LD E,A ; WHICH IS THE + LD A,(HL) ; FREQUENCY FOR + INC HL ; THE EIGHTH + LD D,A ; OCTAVE + + PUSH DE + + LD A,(HL) ; LOAD 2ND ARG + INC HL ; IN DE + LD E,A ; WHICH IS THE + LD A,(HL) ; PITCH + LD D,A + + PUSH DE ; SETUP ARGS IN HL + POP HL ; AND DE + POP DE ; DE = FREQUENCY + ; HL = PITCH + LD A,8 ; DIVIDE THE + SUB B ; FREQUENCY BASED + JR Z,SPK_OCTOK ; ON THE OCTAVE + LD B,A +SPK_OCTDIV: + SRL D ; 0>D>C ; MULTIPLY THE + RR E ; C>E>C ; DURATION EVERY + SLA L ; CC>C ; BCHL = BCHL / 2 + RR H ; C>H>C ; + RR L ; C>L>C ; + SRL C ; 0>C>C ; BCHL = BCHL / 2 + RR H ; C>H>C ; + RR L ; C>L>C ; + SRL C ; 0>C>C ; BCHL = BCHL / 2 + RR H ; C>H>C ; + RR L ; C>L>C ; + + POP DE + EX DE,HL + +; The following SPK_BEEPER routine is a modification of code from +; "The Complete SPECTRUM ROM DISSASSEMBLY" by Dr Ian Logan & Dr Frank O’Hara +; https://www.esocop.org/docs/CompleteSpectrumROMDisassemblyThe.pdf ; - .ECHO "BASIC space remaining: " - .ECHO SLACK - .ECHO " bytes.\n" - -.end +; DE Number of passes to make through the sound generation loop +; HL Loop delay parameter + + PUSH IX + DI ; Disable the interrupt for the duration of a 'beep'. + LD A,L ; Save L temporarily. + SRL L ; Each '1' in the L register is to count 4 T states, but take INT (L/4) and count 16 T states instead. + SRL L + CPL ; Go back to the original value in L and find how many were lost by taking 3-(A mod 4). + AND $03 + LD C,A + LD B,$00 + LD IX,SPK_DLYADJ ; The base address of the timing loop. + ADD IX,BC ; Alter the length of the timing loop. Use an earlier starting point for each '1' lost by taking INT (L/4). + LD A,(RTCVAL) ; Fetch the present border colour from BORDCR and move it to bits 2, 1 and 0 of the A register. +; +; The HL register holds the 'length of the timing loop' with 16 T states being used for each '1' in the L register and 1024 T states for each '1' in the H register. +; +SPK_DLYADJ: + NOP ; Add 4 T states for each earlier entry point that is used. + NOP + NOP + INC B ; The values in the B and C registers will come from the H and L registers - see below. + INC C +BE_H_L_LP: + DEC C ; The 'timing loop', i.e. BC*4 T states. (But note that at the half-cycle point, C will be equal to L+1.) + JR NZ,BE_H_L_LP + LD C,$3F + DEC B + JP NZ,BE_H_L_LP +; + XOR %00000100 ; Flip bit 2. The loudspeaker is now alternately activated and deactivated. + OUT (RTCIO),A ; Perform the 'OUT' operation, leaving other bits unchanged. + LD B,H ; Reset the B register. + LD C,A ; Save the A register. + BIT 4,A ; Jump if at the half-cycle point. + JR NZ,BE_AGAIN +; + LD A,D ; After a full cycle the DE register pair is tested. + OR E + JR Z,BE_END ; Jump forward if the last complete pass has been made already. + LD A,C ; Fetch the saved value. + LD C,L ; Reset the C register. + DEC DE ; Decrease the pass counter. + JP (IX) ; Jump back to the required starting location of the loop. +; +BE_AGAIN: ; The parameters for the second half-cycle are set up. + LD C,L ; Reset the C register. + INC C ; Add 16 T states as this path is shorter. + JP (IX) ; Jump back. + +BE_END: EI + POP IX + POP BC ; RECALL SYNTAX POINTER + POP HL + RET +; +RTCVAL .DB 0 +; +; SETUP THE ONE SECOND TONE DURATION TABLE BASED ON PROCESSOR SPEED AND TONE FREQUENCY +; +; DURATION = (CPUMHZ / 8) / FREQUENCY +; DURATION = (CPUKHZ * 1000 / 8 ) / FREQUENCY +; DURATION = (CPUKHZ * 125 ) / FREQUENCY +; DURATION = (CPUKHZ * 256 / 2 - CPUKHZ - (2 * CPUKHZ) ) / FREQUENCY + +SET_DUR_TBL: + LD B,BF_SYSGET ; GET CPU SPEED + LD C,BF_SYSGET_CPUINFO ; FROM HBIOS + RST 08 ; IN DE + + PUSH DE ; SAVE FOR CALCULATION + POP BC ; - CPUKHZ - (2 * CPUKHZ) + + LD H,E ; DEHL = DE * 256 + LD E,D + LD D,0 + LD L,D + + SRL E ; 0>E>C ; DEHL = DEHL / 2 + RR H ; C>H>C ; + RR L ; C>L>C ; + + SBC HL,BC ; DEHL = DEHL - CPUKHZ + JR NC,FRQ_AJ1 + DEC DE +FRQ_AJ1:SLA C ; C$" -PIO_STR_PIO .DB "Zilog PIO$" -PIO_STR_8255 .DB "i8255 PIO$" \ No newline at end of file +; DESCRIPTION OF DIFFERENT PORT TYPES +; +PIO_TYPE_STR: + .TEXT "$" ; IDX 0 + .TEXT "Zilog PIO$" ; IDX 1 + .TEXT "i8255 PPI$" ; IDX 2 + .TEXT "IO Port$" ; IDX 3 +; +; Z80 PIO PORT TABLE - EACH ENTRY IS FOR 1 CHIP I.E. TWO PORTS +; +; 32 BYTE DATA STRUCTURE FOR EACH PORT +; +; .DB 0 ; IY+0 CIO DEVICE NUMBER (SET DURING PRE-INIT, THEN FIXED) +; .DB 0 ; IY+1 PIO TYPE (SET AT ASSEMBLY, FIXED) +; .DB 0 ; IY+2 PIO CHANNEL (SET AT ASSEMBLY, FIXED) +; .DB PIOBASE+2 ; IY+3 BASE DATA PORT (SET AT ASSEMBLY, FIXED) +; .DB 0 ; IY+4 SPW - MODE 3 I/O DIRECTION BYTE (SET AT ASSEMBLE, SET WITH INIT) +; .DB 0 ; IY+5 SPW - MODE, INTERRUPT (SET AT ASSEMBLY, SET WITH INIT) +; .DW 0 ; IY+6/7 FUNCTION TABLE (SET AT ASSEMBLY, SET DURING PRE-INIT AND AT INIT) +; .DW PIO_IN ; IY+8 ADDR FOR DEVICE INPUT (SET WITH INIT) +; .DW PIO_OUT ; IY+10 ADDR FOR DEVICE OUTPUT (SET WITH INIT) +; .DW PIO_IST ; IY+12 ADDR FOR DEVICE INPUT STATUS (SET WITH INIT) +; .DW PIO_OST ; IY+14 ADDR FOR DEVICE OUTPUT STATUS (SET WITH INIT) +; .DW PIO_INITDEV ; IY+16 ADDR FOR INITIALIZE DEVICE ROUTINE (SET AT ASSEMBLY, FIXED) +; .DW PIO_QUERY ; IY+18 ADDR FOR QUERY DEVICE RECORD ROUTINE (SET AT ASSEMBLY, FIXED) +; .DW PIO_DEVICE ; IY+20 ADDR FOR DEVICE TYPE ROUTINE (SET AT ASSEMBLY, FIXED) +; .FILL 10 +; +; SETUP PARAMETER WORD: +; +; +-------------------------------+ +-------+-----------+---+-------+ +; | BIT CONTROL | | MODE | | A | INT | +; +-------------------------------+ --------------------+-----------+ +; F E D C B A 9 8 7 6 5 4 3 2 1 0 +; -- MSB (D REGISTER) -- -- LSB (E REGISTER) -- +; +; +; MSB = BIT CONTROL MAP USE IN MODE 3 +; +; MODE B7 B6 = 00 Mode 0 Output +; 01 Mode 1 Input +; 10 Mode 2 Bidir +; 11 Mode 3 Bit Mode +; +; INTERRUPT ALLOCATED B2 = 0 NOT ALLOCATED +; = 1 IS ALLOCATED +; +; WHICH IVT IS ALLOCATES B1 B0 00 IVT_PIO0 +; 01 IVT_PIO1 +; 10 IVT_PIO2 +; 11 IVT_PIO3 +; +#DEFINE DEFPIO(MPIOBASE,MPIOCH0,MPIOCH1,MPIOCH0X,MPIOCH1X,MPIOIN0,MPIOIN1) \ +#DEFCONT \ .DB 0 +#DEFCONT \ .DB PIO_ZPIO +#DEFCONT \ .DB 0 +#DEFCONT \ .DB MPIOBASE +#DEFCONT \ .DB (MPIOCH0|MPIOIN0) +#DEFCONT \ .DB MPIOCH0X +#DEFCONT \ .DW 0 +#DEFCONT \ .DW 0,0,0,0, PIO_INITDEV,PIO_QUERY,PIO_DEVICE +#DEFCONT \ .FILL 2 +#DEFCONT \ .DB 0 +#DEFCONT \ .DB PIO_ZPIO +#DEFCONT \ .DB 1 +#DEFCONT \ .DB MPIOBASE+1 +#DEFCONT \ .DB (MPIOCH1|MPIOIN1) +#DEFCONT \ .DB MPIOCH1X +#DEFCONT \ .DW 0 +#DEFCONT \ .DW 0,0,0,0, PIO_INITDEV,PIO_QUERY,PIO_DEVICE +#DEFCONT \ .FILL 2 +; +; i8255 PORT TABLE - EACH ENTRY IS FOR 1 CHIP I.E. THREE PORTS +; +#DEFINE DEFPPI(MPPIBASE,MPPICH1,MPPICH2,MPPICH3,MPPICH1X,MPPICH2X,MPPICH3X) \ +#DEFCONT \ .DB 0 +#DEFCONT \ .DB PIO_8255 +#DEFCONT \ .DB 0 +#DEFCONT \ .DB MPPIBASE +#DEFCONT \ .DB (MPPICH1|00001000B) +#DEFCONT \ .DB MPPICH1X +#DEFCONT \ .DW 0 +#DEFCONT \ .DW PPI_IN,PPI_OUT,PPI_IST,PPI_OST,PPI_INITDEV,PPI_QUERY,PPI_DEVICE +#DEFCONT \ .FILL 2 +#DEFCONT \ .DB 0 +#DEFCONT \ .DB PIO_8255 +#DEFCONT \ .DB 1 +#DEFCONT \ .DB MPPIBASE+2 +#DEFCONT \ .DB (MPPICH2|00010000B) +#DEFCONT \ .DB MPPICH2X +#DEFCONT \ .DW 0 +#DEFCONT \ .DW PPI_IN,PPI_OUT,PPI_IST,PPI_OST,PPI_INITDEV,PPI_QUERY,PPI_DEVICE +#DEFCONT \ .FILL 2 +#DEFCONT \ .DB 0 +#DEFCONT \ .DB PIO_8255 +#DEFCONT \ .DB 2 +#DEFCONT \ .DB MPPIBASE+4 +#DEFCONT \ .DB (MPPICH3|00100000B) +#DEFCONT \ .DB MPPICH3X +#DEFCONT \ .DW 0 +#DEFCONT \ .DW PPI_IN,PPI_OUT,PPI_IST,PPI_OST,PPI_INITDEV,PPI_QUERY,PPI_DEVICE +#DEFCONT \ .FILL 2 +; +; HERE WE ACTUALLY DEFINE THE HARDWARE THAT THE HBIOS CAN ACCESS +; THE INIT ROUTINES READ AND SET THE INITIAL MODES FROM THIS INFO +; +PIO_CFG: +; +#IF PIO_4P +DEFPIO(PIO4BASE+0,M_Output,M_Input,M_BitAllOut,M_BitAllOut,INT_N,INT_N) +DEFPIO(PIO4BASE+4,M_Input,M_Input,M_BitAllOut,M_BitAllOut,INT_N,INT_N) +DEFPIO(PIO4BASE+8,M_Output,M_Output,M_BitAllOut,M_BitAllOut,INT_N,INT_N) +DEFPIO(PIO4BASE+12,M_Output,M_Output,M_BitAllOut,M_Output,INT_N,INT_N) +#ENDIF +#IF PIO_ZP +DEFPIO(PIOZBASE+0,M_Input,M_Input,M_BitAllOut,M_BitAllOut,INT_N,INT_N) +DEFPIO(PIOZBASE+4,M_Output,M_Output,M_BitAllOut,M_BitAllOut,INT_N,INT_N) +#ENDIF +; PPI_SBC & (PLATFORM == PLT_SBC) & (PPIDEMODE != PPIDEMODE_SBC)) + +#IF PPI_SBC +DEFPPI(PPIBASE,M_Output,M_Output,M_Output,M_BitAllOut,M_BitAllOut,M_BitAllOut) +#ENDIF +; +PIO_CNT .EQU ($ - PIO_CFG) / CFG_SIZ +; +;------------------------------------------------------------------- +; WHEN WE GET HERE IY POINTS TO THE PIO_CFG TABLE WE ARE WORKING ON. +; C IS THE UNIT NUMBER +;------------------------------------------------------------------- +; +;PIO_INITUNIT: +; LD A,C ; SET THE UNIT NUMBER +; LD (IY),A +; +; LD DE,-1 ; LEAVE CONFIG ALONE +; CALL PIO_INITDEV ; IMPLEMENT IT AND RETURN +; XOR A ; SIGNAL SUCCESS +; RET ; AND RETURN +; +PIO_INIT: +; CALL SPK_BEEP + LD B,PIO_CNT ; COUNT OF POSSIBLE PIO UNITS + LD C,0 ; INDEX INTO PIO CONFIG TABLE +PIO_INIT1: + PUSH BC ; SAVE LOOP CONTROL + +; LD A,C ; PHYSICAL UNIT TO A +; RLCA ; MULTIPLY BY CFG TABLE ENTRY SIZE (32 BYTES) +; RLCA ; ... +; RLCA ; ... TO GET OFFSET INTO CFG TABLE +; RLCA +;; RLCA +; LD HL,PIO_CFG ; POINT TO START OF CFG TABLE +; PUSH AF +; CALL ADDHLA ; HL := ENTRY ADDRESS +; POP AF +; CALL ADDHLA ; HL := ENTRY ADDRESS +; PUSH HL ; COPY CFG DATA PTR +; POP IY ; ... TO IY +; POP IY ; ... TO IY + + CALL IDXCFG + + LD A,(IY+1) ; GET PIO TYPE + OR A ; SET FLAGS + CALL NZ,PIO_PRTCFG ; PRINT IF NOT ZERO + +; PUSH DE +; LD DE,$FFFF ; INITIALIZE DEVICE/CHANNEL +; CALL PIO_INITDEV ; BASED ON DPW +; POP DE + + POP BC ; RESTORE LOOP CONTROL + INC C ; NEXT UNIT + DJNZ PIO_INIT1 ; LOOP TILL DONE +; + XOR A ; SIGNAL SUCCESS + RET ; DONE +; +SET_PORT: + ; DEVICE TYPE IS I/O PORT SO JUST WRITE $00 TO IT + LD C,(IY+3) + OUT (C),A + XOR A + RET diff --git a/Source/HBIOS/plt_mk4.inc b/Source/HBIOS/plt_mk4.inc deleted file mode 100644 index ce4d0f79..00000000 --- a/Source/HBIOS/plt_mk4.inc +++ /dev/null @@ -1,16 +0,0 @@ -; -; MARK IV HARDWARE DEFINITIONS -; -RAMBIAS .EQU 512 ; RAM STARTS AT 512K -; -MK4_BASE .EQU $80 ; I/O BASE ADDRESS FOR ONBOARD PERIPHERALS -; -MK4_IDE .EQU MK4_BASE + $00 ; IDE REGISTERS ($00-$07, $0E-$0F) -MK4_XAR .EQU MK4_BASE + $08 ; EXTERNAL ADDRESS REGISTER (XAR) -MK4_SD .EQU MK4_BASE + $09 ; SD CARD CONTROL REGISTER -MK4_RTC .EQU MK4_BASE + $0A ; RTC INTERFACE REGISTER -; -RTC .EQU MK4_RTC ; GENERIC ALIAS FOR RTC PORT -; -Z180_BASE .EQU $40 ; I/O BASE ADDRESS FOR INTERNAL Z180 REGISTERS -#INCLUDE "z180.inc" diff --git a/Source/HBIOS/plt_n8.inc b/Source/HBIOS/plt_n8.inc deleted file mode 100644 index b9f087a8..00000000 --- a/Source/HBIOS/plt_n8.inc +++ /dev/null @@ -1,24 +0,0 @@ -; -; N8 HARDWARE DEFINITIONS -; -RAMBIAS .EQU 0 ; RAM STARTS AT 0K -; -N8_BASE .EQU $80 ; I/O BASE ADDRESS FOR ONBOARD PERIPHERALS -; -N8_PPI0 .EQU N8_BASE + $00 ; FIRST PARALLEL PORT -N8_PPI1 .EQU N8_BASE + $04 ; SECOND PARALLEL PORT -N8_RTC .EQU N8_BASE + $08 ; RTC LATCH AND BUFFER -N8_FDC .EQU N8_BASE + $0C ; FLOPPY DISK CONTROLLER -N8_UTIL .EQU N8_BASE + $10 ; FLOPPY DISK UTILITY -N8_ACR .EQU N8_BASE + $14 ; AUXILLARY CONTROL REGISTER -N8_RMAP .EQU N8_BASE + $16 ; ROM PAGE REGISTER -N8_VDP .EQU N8_BASE + $18 ; VIDEO DISPLAY PROCESSOR (TMS9918A) -N8_PSG .EQU N8_BASE + $1C ; PROGRAMMABLE SOUND GENERATOR (AY-3-8910) -; -N8_DEFACR .EQU $1B ; DEFAULT VALUE FOR ACR REGISTER -; -RTC .EQU N8_RTC ; GENERIC ALIAS FOR RTC PORT -PPIBASE .EQU N8_PPI0 ; GENERIC ALIAS FOR PRIMARY PARALLEL PORT -; -Z180_BASE .EQU $40 ; I/O BASE ADDRESS FOR INTERNAL Z180 REGISTERS -#INCLUDE "z180.inc" diff --git a/Source/HBIOS/plt_rc.inc b/Source/HBIOS/plt_rc.inc deleted file mode 100644 index 49bd0aa1..00000000 --- a/Source/HBIOS/plt_rc.inc +++ /dev/null @@ -1,11 +0,0 @@ -; -; RC2014 HARDWARE DEFINITIONS -; -MPGSEL_0 .EQU $78 ; BANK_0 PAGE SELECT REGISTER (WRITE ONLY) -MPGSEL_1 .EQU $79 ; BANK_1 PAGE SELECT REGISTER (WRITE ONLY) -MPGSEL_2 .EQU $7A ; BANK_2 PAGE SELECT REGISTER (WRITE ONLY) -MPGSEL_3 .EQU $7B ; BANK_3 PAGE SELECT REGISTER (WRITE ONLY) -MPGENA .EQU $7C ; PAGING ENABLE REGISTER - BIT 0 = 1 (WRITE ONLY) -; -RTC .EQU $C0 ; RTC PORT address -SIOBASE .EQU $80 ; RC OR SMB SIO DEFAULT diff --git a/Source/HBIOS/plt_rc180.inc b/Source/HBIOS/plt_rc180.inc deleted file mode 100644 index ee045790..00000000 --- a/Source/HBIOS/plt_rc180.inc +++ /dev/null @@ -1,13 +0,0 @@ -; -; RC2014 Z180 HARDWARE DEFINITIONS -; -MPGSEL_0 .EQU $78 ; BANK_0 PAGE SELECT REGISTER (WRITE ONLY) -MPGSEL_1 .EQU $79 ; BANK_1 PAGE SELECT REGISTER (WRITE ONLY) -MPGSEL_2 .EQU $7A ; BANK_2 PAGE SELECT REGISTER (WRITE ONLY) -MPGSEL_3 .EQU $7B ; BANK_3 PAGE SELECT REGISTER (WRITE ONLY) -MPGENA .EQU $7C ; PAGING ENABLE REGISTER - BIT 0 = 1 (WRITE ONLY) -; -RTC .EQU $0C ; ADDRESS OF RTC LATCH AND INPUT PORT -; -Z180_BASE .EQU $C0 ; I/O BASE ADDRESS FOR INTERNAL Z180 REGISTERS -#INCLUDE "z180.inc" diff --git a/Source/HBIOS/plt_sbc.inc b/Source/HBIOS/plt_sbc.inc deleted file mode 100644 index d0795108..00000000 --- a/Source/HBIOS/plt_sbc.inc +++ /dev/null @@ -1,22 +0,0 @@ -; -; SBC HARDWARE DEFINITIONS -; -SBC_BASE .EQU $60 ; I/O BASE ADDRESS FOR ONBOARD PERIPHERALS -; -#IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA)) -; BIT 7 OF MPCL_ROM SELECTS ROM/RAM (0=ROM, 1=RAM) -MPCL_RAM .EQU SBC_BASE + $18 ; MEMORY PAGER CONFIG LATCH - RAM (WRITE ONLY) -MPCL_ROM .EQU SBC_BASE + $1C ; MEMORY PAGER CONFIG LATCH - ROM (WRITE ONLY) -#ENDIF -; -#IF (PLATFORM == PLT_ZETA2) -MPGSEL_0 .EQU SBC_BASE + $18 ; BANK_0 PAGE SELECT REGISTER (WRITE ONLY) -MPGSEL_1 .EQU SBC_BASE + $19 ; BANK_1 PAGE SELECT REGISTER (WRITE ONLY) -MPGSEL_2 .EQU SBC_BASE + $1A ; BANK_2 PAGE SELECT REGISTER (WRITE ONLY) -MPGSEL_3 .EQU SBC_BASE + $1B ; BANK_3 PAGE SELECT REGISTER (WRITE ONLY) -MPGENA .EQU SBC_BASE + $1C ; PAGING ENABLE REGISTER - BIT 0 = 1 (WRITE ONLY) -#ENDIF -; -RTC .EQU SBC_BASE + $10 ; ADDRESS OF RTC LATCH AND INPUT PORT -PPIBASE .EQU SBC_BASE + $00 ; PPI 82C55 I/O IS DECODED TO PORT 60-67 -SIOBASE .EQU $B0 ; ZILOG PERIPHERALS DEFAULT ;PS diff --git a/Source/HBIOS/plt_una.inc b/Source/HBIOS/plt_una.inc deleted file mode 100644 index 71fe5208..00000000 --- a/Source/HBIOS/plt_una.inc +++ /dev/null @@ -1,3 +0,0 @@ -; -; UNA HARDWARE DEFINITIONS -; diff --git a/Source/HBIOS/ppide.asm b/Source/HBIOS/ppide.asm index 5ace5ce3..637ed394 100644 --- a/Source/HBIOS/ppide.asm +++ b/Source/HBIOS/ppide.asm @@ -4,31 +4,16 @@ ;============================================================================= ; ; TODO: -; - IMPLEMENT PPIDE_INITDEVICE -; - IMPLEMENT INTELLIGENT RESET, CHECK IF DEVICE IS ACTUALLY BROKEN BEFORE RESET ; - FIX SCALER CONSTANT +; - GOPARTNER NEEDS TO HANDLE "NO PARTNER" CONDITION ; -; -#IF (PPIDEMODE == PPIDEMODE_SBC) -PPIDE_IO_BASE .EQU $60 -#ENDIF -; -#IF ((PPIDEMODE == PPIDEMODE_DIO3) | (PPIDEMODE == PPIDEMODE_RC)) -PPIDE_IO_BASE .EQU $20 -#ENDIF -; -#IF (PPIDEMODE == PPIDEMODE_MFP) -PPIDE_IO_BASE .EQU $44 -#ENDIF -; -#IF (PPIDEMODE == PPIDEMODE_N8) -PPIDE_IO_BASE .EQU $80 -#ENDIF -; -PPIDE_IO_DATALO .EQU PPIDE_IO_BASE + 0 ; IDE DATA BUS LSB (8255 PORT A) -PPIDE_IO_DATAHI .EQU PPIDE_IO_BASE + 1 ; IDE DATA BUS MSB (8255 PORT B) -PPIDE_IO_CTL .EQU PPIDE_IO_BASE + 2 ; IDE ADDRESS BUS AND CONTROL SIGNALS (8255 PORT C) -PPIDE_IO_PPI .EQU PPIDE_IO_BASE + 3 ; 8255 CONTROL PORT +; NOTES: +; - WELL KNOWN PPIDE PORT ADDRESSES: +; $60 - SBC/ZETA ONBOARD PPI +; $20 - ECB DISKIO3, RC FAMILY +; $44 - ECB MULTI-FUNCTION PIC +; $80 - N8 ONBOARD PPI +; $4C - DYNO ONBOARD PPI ; ; THE CONTROL PORT OF THE 8255 IS PROGRAMMED AS NEEDED TO READ OR WRITE ; DATA ON THE IDE BUS. PORT C OF THE 8255 IS ALWAYS IN OUTPUT MODE BECAUSE @@ -45,8 +30,8 @@ PPIDE_DIR_WRITE .EQU %10000000 ; IDE BUS DATA OUTPUT MODE PPIDE_CTL_DA0 .EQU %00000001 ; DRIVE ADDRESS BUS - BIT 0 (DA0) PPIDE_CTL_DA1 .EQU %00000010 ; DRIVE ADDRESS BUS - BIT 1 (DA1) PPIDE_CTL_DA2 .EQU %00000100 ; DRIVE ADDRESS BUS - BIT 2 (DA2) -PPIDE_CTL_CS1FX .EQU %00001000 ; DRIVE CHIP SELECT 0 (ACTIVE LOW, INVERTED) -PPIDE_CTL_CS3FX .EQU %00010000 ; DRIVE CHIP SELECT 1 (ACTIVE LOW, INVERTED) +PPIDE_CTL_CS1 .EQU %00001000 ; DRIVE CHIP SELECT 0 (ACTIVE LOW, INVERTED) +PPIDE_CTL_CS3 .EQU %00010000 ; DRIVE CHIP SELECT 1 (ACTIVE LOW, INVERTED) PPIDE_CTL_DIOW .EQU %00100000 ; DRIVE I/O WRITE (ACTIVE LOW, INVERTED) PPIDE_CTL_DIOR .EQU %01000000 ; DRIVE I/O READ (ACTIVE LOW, INVERTED) PPIDE_CTL_RESET .EQU %10000000 ; DRIVE RESET (ACTIVE LOW, INVERTED) @@ -81,7 +66,7 @@ PPIDE_CTL_RESET .EQU %10000000 ; DRIVE RESET (ACTIVE LOW, INVERTED) ; | PPIDE_REG_STAT | 0x07 | R | STATUS REGISTER | ; | PPIDE_REG_CMD | 0x07 | W | COMMAND REGISTER (EXECUTE) | ; +-----------------------+-------+-------+-------------------------------+ -; * LBA0-4 ARE ALTERNATE DEFINITIONS OF SECT, CYL, AND DRVHD PORTS +; * LBA0-3 ARE ALTERNATE DEFINITIONS OF SECT, CYL, AND DRVHD PORTS ; ; === STATUS REGISTER === ; @@ -139,37 +124,23 @@ PPIDE_CTL_RESET .EQU %10000000 ; DRIVE RESET (ACTIVE LOW, INVERTED) ; ; CONTROL VALUES TO USE WHEN ACCESSING THE VARIOUS IDE DEVICE REGISTERS ; -PPIDE_REG_DATA .EQU PPIDE_CTL_CS1FX | $00 ; DATA INPUT/OUTPUT (R/W) -PPIDE_REG_ERR .EQU PPIDE_CTL_CS1FX | $01 ; ERROR REGISTER (R) -PPIDE_REG_FEAT .EQU PPIDE_CTL_CS1FX | $01 ; FEATURES REGISTER (W) -PPIDE_REG_COUNT .EQU PPIDE_CTL_CS1FX | $02 ; SECTOR COUNT REGISTER (R/W) -PPIDE_REG_SECT .EQU PPIDE_CTL_CS1FX | $03 ; SECTOR NUMBER REGISTER (R/W) -PPIDE_REG_CYLLO .EQU PPIDE_CTL_CS1FX | $04 ; CYLINDER NUM REGISTER (LSB) (R/W) -PPIDE_REG_CYLHI .EQU PPIDE_CTL_CS1FX | $05 ; CYLINDER NUM REGISTER (MSB) (R/W) -PPIDE_REG_DRVHD .EQU PPIDE_CTL_CS1FX | $06 ; DRIVE/HEAD REGISTER (R/W) -PPIDE_REG_LBA0 .EQU PPIDE_CTL_CS1FX | $03 ; LBA BYTE 0 (BITS 0-7) (R/W) -PPIDE_REG_LBA1 .EQU PPIDE_CTL_CS1FX | $04 ; LBA BYTE 1 (BITS 8-15) (R/W) -PPIDE_REG_LBA2 .EQU PPIDE_CTL_CS1FX | $05 ; LBA BYTE 2 (BITS 16-23) (R/W) -PPIDE_REG_LBA3 .EQU PPIDE_CTL_CS1FX | $06 ; LBA BYTE 3 (BITS 24-27) (R/W) -PPIDE_REG_STAT .EQU PPIDE_CTL_CS1FX | $07 ; STATUS REGISTER (R) -PPIDE_REG_CMD .EQU PPIDE_CTL_CS1FX | $07 ; COMMAND REGISTER (EXECUTE) (W) -PPIDE_REG_ALTSTAT .EQU PPIDE_CTL_CS3FX | $06 ; ALTERNATE STATUS REGISTER (R) -PPIDE_REG_CTRL .EQU PPIDE_CTL_CS3FX | $06 ; DEVICE CONTROL REGISTER (W) -PPIDE_REG_DRVADR .EQU PPIDE_CTL_CS3FX | $07 ; DRIVE ADDRESS REGISTER (R) -; -#IF (PPIDETRACE >= 3) -#DEFINE DCALL CALL -#ELSE -#DEFINE DCALL \; -#ENDIF -; -; UNIT MAPPING IS AS FOLLOWS: -; PPIDE0: PRIMARY MASTER -; PPIDE1: PRIMARY SLAVE -; PPIDE2: SECONDARY MASTER -; PPIDE3: SECONDARY SLAVE -; -PPIDE_DEVCNT .EQU 2 ; ASSUME ONLY PRIMARY INTERFACE +PPIDE_REG_DATA .EQU PPIDE_CTL_CS1 | $00 ; DATA INPUT/OUTPUT (R/W) +PPIDE_REG_ERR .EQU PPIDE_CTL_CS1 | $01 ; ERROR REGISTER (R) +PPIDE_REG_FEAT .EQU PPIDE_CTL_CS1 | $01 ; FEATURES REGISTER (W) +PPIDE_REG_COUNT .EQU PPIDE_CTL_CS1 | $02 ; SECTOR COUNT REGISTER (R/W) +PPIDE_REG_SECT .EQU PPIDE_CTL_CS1 | $03 ; SECTOR NUMBER REGISTER (R/W) +PPIDE_REG_CYLLO .EQU PPIDE_CTL_CS1 | $04 ; CYLINDER NUM REGISTER (LSB) (R/W) +PPIDE_REG_CYLHI .EQU PPIDE_CTL_CS1 | $05 ; CYLINDER NUM REGISTER (MSB) (R/W) +PPIDE_REG_DRVHD .EQU PPIDE_CTL_CS1 | $06 ; DRIVE/HEAD REGISTER (R/W) +PPIDE_REG_LBA0 .EQU PPIDE_CTL_CS1 | $03 ; LBA BYTE 0 (BITS 0-7) (R/W) +PPIDE_REG_LBA1 .EQU PPIDE_CTL_CS1 | $04 ; LBA BYTE 1 (BITS 8-15) (R/W) +PPIDE_REG_LBA2 .EQU PPIDE_CTL_CS1 | $05 ; LBA BYTE 2 (BITS 16-23) (R/W) +PPIDE_REG_LBA3 .EQU PPIDE_CTL_CS1 | $06 ; LBA BYTE 3 (BITS 24-27) (R/W) +PPIDE_REG_STAT .EQU PPIDE_CTL_CS1 | $07 ; STATUS REGISTER (R) +PPIDE_REG_CMD .EQU PPIDE_CTL_CS1 | $07 ; COMMAND REGISTER (EXECUTE) (W) +PPIDE_REG_ALTSTAT .EQU PPIDE_CTL_CS3 | $06 ; ALTERNATE STATUS REGISTER (R) +PPIDE_REG_CTRL .EQU PPIDE_CTL_CS3 | $06 ; DEVICE CONTROL REGISTER (W) +PPIDE_REG_DRVADR .EQU PPIDE_CTL_CS3 | $07 ; DRIVE ADDRESS REGISTER (R) ; ; COMMAND BYTES ; @@ -190,7 +161,7 @@ PPIDE_TYPEUNK .EQU 0 PPIDE_TYPEATA .EQU 1 PPIDE_TYPEATAPI .EQU 2 ; -; PPIDE DEVICE STATUS +; PPIDE DEVICE STATUS CODES ; PPIDE_STOK .EQU 0 PPIDE_STINVUNIT .EQU -1 @@ -203,44 +174,133 @@ PPIDE_STBSYTO .EQU -7 ; ; DRIVE SELECTION BYTES (FOR USE IN DRIVE/HEAD REGISTER) ; -PPIDE_DRVSEL: -PPIDE_DRVMASTER .DB %11100000 ; LBA, MASTER DEVICE -PPIDE_DRVSLAVE .DB %11110000 ; LBA, SLAVE DEVICE +;PPIDE_DRVSEL: +PPIDE_DRVMASTER .EQU %11100000 ; LBA, MASTER DEVICE +PPIDE_DRVSLAVE .EQU %11110000 ; LBA, SLAVE DEVICE ; ; PPIDE DEVICE CONFIGURATION ; -PPIDE_CFGSIZ .EQU 12 ; SIZE OF CFG TBL ENTRIES +PPIDE_CFGSIZ .EQU 18 ; SIZE OF CFG TBL ENTRIES ; ; PER DEVICE DATA OFFSETS ; PPIDE_DEV .EQU 0 ; OFFSET OF DEVICE NUMBER (BYTE) PPIDE_STAT .EQU 1 ; LAST STATUS (BYTE) PPIDE_TYPE .EQU 2 ; DEVICE TYPE (BYTE) -PPIDE_FLAGS .EQU 3 ; FLAG BITS BIT 0=CF, 1=LBA (BYTE) -PPIDE_MEDCAP .EQU 4 ; MEDIA CAPACITY (DWORD) -PPIDE_LBA .EQU 8 ; OFFSET OF LBA (DWORD) +PPIDE_ACC .EQU 3 ; ACCESS FLAG BITS BIT 0=MASTER, 1=8BIT (BYTE) +PPIDE_MED .EQU 4 ; MEDIA FLAG BITS BIT 0=CF, 1=LBA (BYTE) +PPIDE_MEDCAP .EQU 5 ; MEDIA CAPACITY (DWORD) +PPIDE_LBA .EQU 9 ; OFFSET OF LBA (DWORD) +PPIDE_DATALO .EQU 13 ; BASE PORT AND IDE DATA BUS LSB (8255 PORT A) (BYTE) +PPIDE_CTL .EQU 14 ; IDE ADDRESS BUS AND CONTROL SIGNALS (8255 PORT C)(BYTE) +PPIDE_PPI .EQU 15 ; 8255 CONTROL PORT(BYTE) +PPIDE_PARTNER .EQU 16 ; PARTNER DEVICE (MASTER <-> SLAVE) (WORD) +; +PPIDE_ACC_MAS .EQU %00000001 ; UNIT IS MASTER (ELSE SLAVE) +PPIDE_ACC_8BIT .EQU %00000010 ; UNIT WANTS 8 BIT I/O (ELSE 16 BIT) +; +PPIDE_MED_CF .EQU %00000001 ; MEDIA IS CF CARD +PPIDE_MED_LBA .EQU %00000010 ; MEDIA HAS LBA CAPABILITY +; +PPIDE_DEVCNT .EQU PPIDECNT * 2 ; PPIDE_CFGTBL: - ; DEVICE 0, PRIMARY MASTER - .DB 0 ; DRIVER DEVICE NUMBER +; +#IF (PPIDECNT >= 1) +; +PPIDE_DEV0M: ; DEVICE 0, MASTER + .DB 0 ; DRIVER RELATIVE DEVICE NUMBER (ASSIGNED DURING INIT) .DB 0 ; DEVICE STATUS .DB 0 ; DEVICE TYPE - .DB 0 ; FLAGS BYTE + .DB PPIDE_ACC_MAS | (PPIDE0A8BIT & PPIDE_ACC_8BIT) ; UNIT ACCESS FLAGS + .DB 0 ; MEDIA FLAGS .DW 0,0 ; DEVICE CAPACITY .DW 0,0 ; CURRENT LBA - ; DEVICE 1, PRIMARY SLAVE - .DB 1 ; DRIVER DEVICE NUMBER + .DB PPIDE0BASE ; DATALO + .DB PPIDE0BASE+2 ; CTL + .DB PPIDE0BASE+3 ; PPI + .DW PPIDE_DEV0S ; PARTNER +; +PPIDE_DEV0S: ; DEVICE 0, SLAVE + .DB 0 ; DRIVER RELATIVE DEVICE NUMBER (ASSIGNED DURING INIT) .DB 0 ; DEVICE STATUS .DB 0 ; DEVICE TYPE - .DB 0 ; FLAGS BYTE + .DB (PPIDE0B8BIT & PPIDE_ACC_8BIT) ; UNIT ACCESS FLAGS + .DB 0 ; MEDIA FLAGS .DW 0,0 ; DEVICE CAPACITY .DW 0,0 ; CURRENT LBA + .DB PPIDE0BASE ; DATALO + .DB PPIDE0BASE+2 ; CTL + .DB PPIDE0BASE+3 ; PPI + .DW PPIDE_DEV0M ; PARTNER +; +#ENDIF +; +#IF (PPIDECNT >= 2) +; +PPIDE_DEV1M: ; DEVICE 1, MASTER + .DB 0 ; DRIVER RELATIVE DEVICE NUMBER (ASSIGNED DURING INIT) + .DB 0 ; DEVICE STATUS + .DB 0 ; DEVICE TYPE + .DB PPIDE_ACC_MAS | (PPIDE1A8BIT & PPIDE_ACC_8BIT) ; UNIT ACCESS FLAGS + .DB 0 ; MEDIA FLAGS + .DW 0,0 ; DEVICE CAPACITY + .DW 0,0 ; CURRENT LBA + .DB PPIDE1BASE ; DATALO + .DB PPIDE1BASE+2 ; CTL + .DB PPIDE1BASE+3 ; PPI + .DW PPIDE_DEV1S ; PARTNER +; +PPIDE_DEV1S: ; DEVICE 1, SLAVE + .DB 0 ; DRIVER RELATIVE DEVICE NUMBER (ASSIGNED DURING INIT) + .DB 0 ; DEVICE STATUS + .DB 0 ; DEVICE TYPE + .DB (PPIDE1B8BIT & PPIDE_ACC_8BIT) ; UNIT ACCESS FLAGS + .DB 0 ; MEDIA FLAGS + .DW 0,0 ; DEVICE CAPACITY + .DW 0,0 ; CURRENT LBA + .DB PPIDE1BASE ; DATALO + .DB PPIDE1BASE+2 ; CTL + .DB PPIDE1BASE+3 ; PPI + .DW PPIDE_DEV1M ; PARTNER +; +#ENDIF +; +#IF (PPIDECNT >= 3) +; +PPIDE_DEV2M: ; DEVICE 2, MASTER + .DB 0 ; DRIVER RELATIVE DEVICE NUMBER (ASSIGNED DURING INIT) + .DB 0 ; DEVICE STATUS + .DB 0 ; DEVICE TYPE + .DB PPIDE_ACC_MAS | (PPIDE2A8BIT & PPIDE_ACC_8BIT) ; UNIT ACCESS FLAGS + .DB 0 ; MEDIA FLAGS + .DW 0,0 ; DEVICE CAPACITY + .DW 0,0 ; CURRENT LBA + .DB PPIDE2BASE ; DATALO + .DB PPIDE2BASE+2 ; CTL + .DB PPIDE2BASE+3 ; PPI + .DW PPIDE_DEV2S ; PARTNER +; +PPIDE_DEV2S: ; DEVICE 2, SLAVE + .DB 0 ; DRIVER RELATIVE DEVICE NUMBER (ASSIGNED DURING INIT) + .DB 0 ; DEVICE STATUS + .DB 0 ; DEVICE TYPE + .DB (PPIDE2B8BIT & PPIDE_ACC_8BIT) ; UNIT ACCESS FLAGS + .DB 0 ; MEDIA FLAGS + .DW 0,0 ; DEVICE CAPACITY + .DW 0,0 ; CURRENT LBA + .DB PPIDE2BASE ; DATALO + .DB PPIDE2BASE+2 ; CTL + .DB PPIDE2BASE+3 ; PPI + .DW PPIDE_DEV2M ; PARTNER +; +#ENDIF ; #IF ($ - PPIDE_CFGTBL) != (PPIDE_DEVCNT * PPIDE_CFGSIZ) .ECHO "*** INVALID PPIDE CONFIG TABLE ***\n" #ENDIF ; - .DB $FF ; END MARKER + .DB $FF ; END OF TABLE MARKER ; ; THE IDE_WAITXXX FUNCTIONS ARE BUILT TO TIMEOUT AS NEEDED SO DRIVER WILL ; NOT HANG IF DEVICE IS UNRESPONSIVE. DIFFERENT TIMEOUTS ARE USED DEPENDING @@ -259,9 +319,6 @@ PPIDE_TOFAST .EQU 10 ; FAST TIMEOUT IS 0.5 SECS ;============================================================================= ; PPIDE_INIT: - CALL NEWLINE ; FORMATTING - PRTS("PPIDE:$") ; LABEL FOR IO ADDRESS -; ; COMPUTE CPU SPEED COMPENSATED TIMEOUT SCALER ; AT 1MHZ, THE SCALER IS 218 (50000US / 229TS = 218) ; SCALER IS THEREFORE 218 * CPU SPEED IN MHZ @@ -270,58 +327,61 @@ PPIDE_INIT: CALL MULT8X16 ; HL := DE * A LD (PPIDE_TOSCALER),HL ; SAVE IT ; - PRTS(" IO=0x$") ; LABEL FOR IO ADDRESS - LD A,PPIDE_IO_BASE - CALL PRTHEXBYTE + XOR A ; ZERO ACCUM + LD (PPIDE_DEVNUM),A ; INIT DEV UNIT NUM FOR DYNAMIC ASSIGNMENT + LD IY,PPIDE_CFGTBL ; POINT TO START OF CONFIG TABLE ; -#IF (PPIDE8BIT) - PRTS(" 8BIT$") -#ENDIF - CALL PPIDE_DETECT ; CHECK FOR HARDWARE - JR Z,PPIDE_INIT00 ; CONTINUE IF PRESENT +PPIDE_INIT1: + LD A,(IY) ; LOAD FIRST BYTE TO CHECK FOR END + CP $FF ; CHECK FOR END OF TABLE VALUE + JR NZ,PPIDE_INIT2 ; IF NOT END OF TABLE, CONTINUE + XOR A ; SIGNAL SUCCESS + RET ; AND RETURN ; - ; HARDWARE NOT PRESENT - PRTS(" NOT PRESENT$") - OR $FF ; SIGNAL FAILURE - RET +PPIDE_INIT2: + BIT 0,(IY+PPIDE_ACC) ; MASTER? + JR Z,PPIDE_INIT4 ; IF NOT MASTER, SKIP AHEAD ; -PPIDE_INIT00: - PRTS(" DEVICES=$") - LD A,PPIDE_DEVCNT - CALL PRTDECB + CALL NEWLINE ; FORMATTING + PRTS("PPIDE:$") ; LABEL FOR IO ADDRESS ; -; SETUP THE DISPATCH TABLE ENTRIES + PRTS(" IO=0x$") ; LABEL FOR IO ADDRESS + LD A,(IY+PPIDE_DATALO) ; GET IO BASE ADDRES + CALL PRTHEXBYTE ; DISPLAY IT ; - LD B,PPIDE_DEVCNT ; LOOP CONTROL - LD IY,PPIDE_CFGTBL ; START OF CFG TABLE -PPIDE_INIT0: - PUSH BC ; SAVE LOOP CONTROL + CALL PPIDE_DETECT ; PROBE FOR INTERFACE + JR Z,PPIDE_INIT3 ; GOT IT, MOVE ON TO INIT UNITS + CALL PC_SPACE ; FORMATTING + LD DE,PPIDE_STR_NOPPI ; NO PPI MESSAGE + CALL WRITESTR ; DISPLAY IT + JR PPIDE_INIT4 ; SKIP CFG ENTRY +; +PPIDE_INIT3: + CALL PPIDE_RESET ; RESET THE BUS + CALL PPIDE_INIT5 ; DETECT/INIT MASTER + PUSH IY ; SAVE CFG PTR + CALL PPIDE_GOPARTNER ; SWITCH IY TO PARTNER CFG + CALL PPIDE_INIT5 ; DETECT/INIT SLAVE + POP IY ; RESTORE CFG PTR +; +PPIDE_INIT4: + LD DE,PPIDE_CFGSIZ ; SIZE OF CFG TABLE ENTRY + ADD IY,DE ; BUMP POINTER + JR PPIDE_INIT1 ; AND LOOP +; +PPIDE_INIT5: + ; UPDATE DRIVER RELATIVE UNIT NUMBER IN CONFIG TABLE + LD A,(PPIDE_DEVNUM) ; GET NEXT UNIT NUM TO ASSIGN + LD (IY+PPIDE_DEV),A ; UPDATE IT + INC A ; BUMP TO NEXT UNIT NUM TO ASSIGN + LD (PPIDE_DEVNUM),A ; SAVE IT +; + ; ADD UNIT TO GLOBAL DISK UNIT TABLE LD BC,PPIDE_FNTBL ; BC := FUNC TABLE ADR PUSH IY ; CFG ENTRY POINTER POP DE ; COPY TO DE CALL DIO_ADDENT ; ADD ENTRY TO GLOBAL DISK DEV TABLE - LD BC,PPIDE_CFGSIZ ; SIZE OF CFG ENTRY - ADD IY,BC ; BUMP IY TO NEXT ENTRY - POP BC ; RESTORE BC - DJNZ PPIDE_INIT0 ; LOOP AS NEEDED -; - ; INITIALIZE THE PPIDE INTERFACE NOW - CALL PPIDE_RESET ; DO HARDWARE SETUP/INIT - RET NZ ; ABORT IF RESET FAILS -; - ; DEVICE DISPLAY LOOP - LD B,PPIDE_DEVCNT ; LOOP ONCE PER DEVICE - LD IY,PPIDE_CFGTBL ; START OF CFG TABLE -PPIDE_INIT1: - PUSH BC ; SAVE LOOP CONTROL - CALL PPIDE_INIT2 ; DISPLAY UNIT INFO - LD BC,PPIDE_CFGSIZ ; SIZE OF CFG ENTRY - ADD IY,BC ; BUMP IY TO NEXT ENTRY - POP BC ; RESTORE LOOP CONTROL - DJNZ PPIDE_INIT1 ; LOOP UNTIL DONE - RET ; DONE ; -PPIDE_INIT2: ; CHECK FOR BAD STATUS LD A,(IY+PPIDE_STAT) ; GET STATUS OR A ; SET FLAGS @@ -329,13 +389,13 @@ PPIDE_INIT2: ; CALL PPIDE_PRTPREFIX ; PRINT DEVICE PREFIX ; -#IF (PPIDE8BIT) - PRTS(" 8BIT$") -#ENDIF + LD DE,PPIDE_STR_8BIT + BIT 1,(IY+PPIDE_ACC) ; 8 BIT ACCESS? + CALL NZ,WRITESTR ; ; PRINT LBA/NOLBA CALL PC_SPACE ; FORMATTING - BIT 1,(IY+PPIDE_FLAGS) ; TEST LBA FLAG + BIT 1,(IY+PPIDE_MED) ; TEST LBA FLAG LD DE,PPIDE_STR_NO ; POINT TO "NO" STRING CALL Z,WRITESTR ; PRINT "NO" BEFORE "LBA" IF LBA NOT SUPPORTED PRTS("LBA$") ; PRINT "LBA" REGARDLESS @@ -354,11 +414,10 @@ PPIDE_INIT2: CALL PRTDEC ; PRINT LOW WORD IN DECIMAL (HIGH WORD DISCARDED) PRTS("MB$") ; PRINT SUFFIX ; - XOR A ; SIGNAL SUCCESS - RET ; RETURN WITH A=0, AND Z SET + RET ; ;---------------------------------------------------------------------- -; PROBE FOR PPIDE HARDWARE +; PROBE FOR PPI HARDWARE ;---------------------------------------------------------------------- ; ; ON RETURN, ZF SET INDICATES HARDWARE FOUND @@ -371,14 +430,19 @@ PPIDE_DETECT: ; THEN THE BUS HOLD CIRCUITRY WILL READ BACK THE ZERO. SINCE ; WE ARE IN WRITE MODE, AN IDE CONTROLLER WILL NOT BE ABLE TO ; INTERFERE WITH THE VALUE BEING READ. +; LD A,PPIDE_DIR_WRITE ; SET DATA BUS DIRECTION TO WRITE - OUT (PPIDE_IO_PPI),A ; OUTPUT TO CONTROL WORD - LD C,PPIDE_IO_DATALO ; PPI PORT A + LD C,(IY+PPIDE_PPI) ; PPI CONTROL WORD + OUT (C),A ; WRITE IT +; + LD C,(IY+PPIDE_DATALO) ; PPI PORT A, DATALO XOR A ; VALUE ZERO OUT (C),A ; PUSH VALUE TO PORT IN A,(C) ; GET PORT VALUE - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF OR A ; SET FLAGS RET ; AND RETURN ; @@ -411,6 +475,7 @@ PPIDE_DEFMED: ; ; PPIDE_READ: + CALL HB_DSKREAD ; HOOK HBIOS DISK READ SUPERVISOR LD BC,PPIDE_RDSEC ; GET ADR OF SECTOR READ FUNC LD (PPIDE_IOFNADR),BC ; SAVE IT AS PENDING IO FUNC JR PPIDE_IO ; CONTINUE TO GENERIC IO ROUTINE @@ -418,6 +483,7 @@ PPIDE_READ: ; ; PPIDE_WRITE: + CALL HB_DSKWRITE ; HOOK HBIOS DISK WRITE SUPERVISOR LD BC,PPIDE_WRSEC ; GET ADR OF SECTOR WRITE FUNC LD (PPIDE_IOFNADR),BC ; SAVE IT AS PENDING IO FUNC JR PPIDE_IO ; CONTINUE TO GENERIC IO ROUTINE @@ -437,7 +503,7 @@ PPIDE_IO: #ENDIF PUSH BC ; SAVE COUNTERS CALL PPIDE_SELUNIT ; HARDWARE SELECTION OF TARGET UNIT - CALL PPIDE_CHKDEVICE ; CHECK DEVICE AND CLEAR STATUS + CALL PPIDE_CHKERR ; CHECK FOR ERR STATUS AND RESET IF SO POP BC ; RESTORE COUNTERS JR NZ,PPIDE_IO3 ; BAIL OUT ON ERROR PPIDE_IO1: @@ -478,7 +544,7 @@ PPIDE_STATUS: PPIDE_DEVICE: LD D,DIODEV_PPIDE ; D := DEVICE TYPE LD E,(IY+PPIDE_DEV) ; E := PHYSICAL DEVICE NUMBER - BIT 0,(IY+PPIDE_FLAGS) ; TEST CF BIT IN FLAGS + BIT 0,(IY+PPIDE_MED) ; TEST CF BIT IN FLAGS LD C,%00000000 ; ASSUME NON-REMOVABLE HARD DISK JR Z,PPIDE_DEVICE1 ; IF Z, WE ARE DONE LD C,%01001000 ; OTHERWISE REMOVABLE COMPACT FLASH @@ -573,14 +639,18 @@ PPIDE_SETFEAT: ;OUT (PPIDE_REG_DRVHD),A CALL PPIDE_OUT .DB PPIDE_REG_DRVHD - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF POP AF ;OUT (PPIDE_REG_FEAT),A ; SET THE FEATURE VALUE CALL PPIDE_OUT .DB PPIDE_REG_FEAT - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF LD A,PPIDE_CMD_SETFEAT ; CMD = SETFEAT LD (PPIDE_CMD),A ; SAVE IT JP PPIDE_RUNCMD ; RUN COMMAND AND EXIT @@ -596,8 +666,10 @@ PPIDE_IDENTIFY: ;OUT (PPIDE_REG_DRVHD),A CALL PPIDE_OUT .DB PPIDE_REG_DRVHD - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF LD A,PPIDE_CMD_IDDEV LD (PPIDE_CMD),A CALL PPIDE_RUNCMD @@ -617,8 +689,10 @@ PPIDE_RDSEC: ;OUT (PPIDE_REG_DRVHD),A CALL PPIDE_OUT .DB PPIDE_REG_DRVHD - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF CALL PPIDE_SETADDR ; SETUP CYL, TRK, HEAD LD A,PPIDE_CMD_READ LD (PPIDE_CMD),A @@ -639,8 +713,10 @@ PPIDE_WRSEC: ;OUT (PPIDE_REG_DRVHD),A CALL PPIDE_OUT .DB PPIDE_REG_DRVHD - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF CALL PPIDE_SETADDR ; SETUP CYL, TRK, HEAD LD A,PPIDE_CMD_WRITE LD (PPIDE_CMD),A @@ -652,31 +728,38 @@ PPIDE_WRSEC: ; ; PPIDE_SETADDR: - ; XXX ; SEND 3 LOWEST BYTES OF LBA IN REVERSE ORDER ; IDE_IO_LBA3 HAS ALREADY BEEN SET ; HSTLBA2-0 --> IDE_IO_LBA2-0 LD A,(IY+PPIDE_LBA+2) - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF CALL PPIDE_OUT .DB PPIDE_REG_LBA2 - +; LD A,(IY+PPIDE_LBA+1) - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF CALL PPIDE_OUT .DB PPIDE_REG_LBA1 - +; LD A,(IY+PPIDE_LBA+0) - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF CALL PPIDE_OUT .DB PPIDE_REG_LBA0 - +; LD A,1 - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF CALL PPIDE_OUT .DB PPIDE_REG_COUNT ; @@ -695,8 +778,10 @@ PPIDE_RUNCMD: RET NZ ; BAIL OUT ON TIMEOUT ; LD A,(PPIDE_CMD) ; GET THE COMMAND - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF ;OUT (PPIDE_REG_CMD),A ; SEND IT (STARTS EXECUTION) CALL PPIDE_OUT .DB PPIDE_REG_CMD @@ -724,49 +809,64 @@ PPIDE_GETBUF: ; ; SETUP PPI TO READ LD A,PPIDE_DIR_READ ; SET DATA BUS DIRECTION TO READ - OUT (PPIDE_IO_PPI),A ; DO IT + ;OUT (PPIDE_IO_PPI),A ; DO IT + LD C,(IY+PPIDE_PPI) ; PPI CONTROL WORD + OUT (C),A ; WRITE IT ; ; SELECT READ/WRITE IDE REGISTER LD A,PPIDE_REG_DATA ; DATA REGISTER - OUT (PPIDE_IO_CTL),A ; DO IT + ;OUT (PPIDE_IO_CTL),A ; DO IT + LD C,(IY+PPIDE_CTL) ; SET IDE ADDRESS + OUT (C),A ; DO IT LD E,A ; E := READ UNASSERTED XOR PPIDE_CTL_DIOR ; SWAP THE READ LINE BIT LD D,A ; D := READ ASSERTED ; ; LOOP SETUP + XOR A ; IMPORTANT, NEEDED FOR LOOP END COMPARISON LD B,0 ; 256 ITERATIONS - LD C,PPIDE_IO_DATALO ; SETUP C WITH IO PORT (LSB) -; -#IF (!PPIDE8BIT) - INC C ; PRE-INCREMENT C -#ENDIF -; - CALL PPIDE_GETBUF1 ; FIRST PASS (FIRST 256 BYTES) - CALL PPIDE_GETBUF1 ; SECOND PASS (LAST 256 BYTES) -; - ;; CLEAN UP - ;XOR A ; ZERO A - ;OUT (PPIDE_IO_CTL),A ; RELEASE ALL BUS SIGNALS -; + LD C,(IY+PPIDE_CTL) ; SET IDE ADDRESS +; + BIT 1,(IY+PPIDE_ACC) ; 8 BIT? + JR Z,PPIDE_GETBUF1 ; IF NOT, DO 16 BIT + CALL PPIDE_GETBUF8 ; FIRST PASS (FIRST 256 BYTES) + CALL PPIDE_GETBUF8 ; SECOND PASS (LAST 256 BYTES) + JR PPIDE_GETBUF2 ; CONTINUE +PPIDE_GETBUF1: + CALL PPIDE_GETBUF16 ; FIRST PASS (FIRST 256 BYTES) + CALL PPIDE_GETBUF16 ; SECOND PASS (LAST 256 BYTES) +PPIDE_GETBUF2: CALL PPIDE_WAITRDY ; PROBLEMS IF THIS IS REMOVED! RET NZ CALL PPIDE_GETRES JP NZ,PPIDE_IOERR RET ; -PPIDE_GETBUF1: ; START OF READ LOOP - LD A,D ; ASSERT READ - OUT (PPIDE_IO_CTL),A ; DO IT -#IF (!PPIDE8BIT) - DEC C - INI ; GET AND SAVE NEXT BYTE +PPIDE_GETBUF8: ; 8 BIT WIDE READ LOOP + ; ENTER W/ C = PPIDE_IO_CTL + OUT (C),D ; ASSERT READ + DEC C ; CTL -> MSB + DEC C ; MSB -> LSB + INI ; READ FROM LSB INC C ; LSB -> MSB -#ENDIF - INI ; GET AND SAVE NEXT BYTE - LD A,E ; DEASSERT READ - OUT (PPIDE_IO_CTL),A ; DO IT + INC C ; MSB -> CTL + OUT (C),E ; DEASSERT READ + CP B ; B == A == 0? + JR NZ,PPIDE_GETBUF8 ; LOOP UNTIL DONE + RET ; - JR NZ,PPIDE_GETBUF1 ; LOOP UNTIL DONE +PPIDE_GETBUF16: ; 16 BIT WIDE READ LOOP + ; ENTER W/ C = PPIDE_IO_CTL + OUT (C),D ; ASSERT READ + DEC C ; CTL -> MSB + DEC C ; MSB -> LSB + INI ; READ FROM LSB + INC C ; LSB -> MSB + INI ; READ MSB FOR 16 BIT + INC C ; MSB -> CTL + OUT (C),E ; DEASSERT READ + CP B ; B == A == 0? + JR NZ,PPIDE_GETBUF16 ; LOOP UNTIL DONE RET ; ; @@ -775,56 +875,69 @@ PPIDE_PUTBUF: #IF (PPIDETRACE >= 3) PRTS(" PUTBUF$") #ENDIF - +; ; WAIT FOR BUFFER CALL PPIDE_WAITDRQ ; WAIT FOR BUFFER READY RET NZ ; BAIL OUT IF TIMEOUT ; ; SETUP PPI TO WRITE LD A,PPIDE_DIR_WRITE ; SET DATA BUS DIRECTION TO WRITE - OUT (PPIDE_IO_PPI),A ; DO IT + ;OUT (PPIDE_IO_PPI),A ; DO IT + LD C,(IY+PPIDE_PPI) ; PPI CONTROL WORD + OUT (C),A ; WRITE IT ; ; SELECT READ/WRITE IDE REGISTER LD A,PPIDE_REG_DATA ; DATA REGISTER - OUT (PPIDE_IO_CTL),A ; DO IT + ;OUT (PPIDE_IO_CTL),A ; DO IT + LD C,(IY+PPIDE_CTL) ; SET IDE ADDRESS + OUT (C),A ; DO IT LD E,A ; E := WRITE UNASSERTED XOR PPIDE_CTL_DIOW ; SWAP THE READ LINE BIT LD D,A ; D := WRITE ASSERTED ; ; LOOP SETUP + XOR A ; IMPORTANT, NEEDED FOR LOOP END COMPARISON LD B,0 ; 256 ITERATIONS - LD C,PPIDE_IO_DATALO ; SETUP C WITH IO PORT (LSB) -; -#IF (!PPIDE8BIT) - INC C ; PRE-INCREMENT C -#ENDIF -; - CALL PPIDE_PUTBUF1 ; FIRST PASS (FIRST 256 BYTES) - CALL PPIDE_PUTBUF1 ; SECOND PASS (LAST 256 BYTES) -; - ;; CLEAN UP - ;XOR A ; ZERO A - ;OUT (PPIDE_IO_CTL),A ; RELEASE ALL BUS SIGNALS -; + LD C,(IY+PPIDE_CTL) ; SET IDE ADDRESS +; + BIT 1,(IY+PPIDE_ACC) ; 8 BIT? + JR Z,PPIDE_PUTBUF1 ; IF NOT, DO 16 BIT + CALL PPIDE_PUTBUF8 ; FIRST PASS (FIRST 256 BYTES) + CALL PPIDE_PUTBUF8 ; SECOND PASS (LAST 256 BYTES) + JR PPIDE_PUTBUF2 ; CONTINUE +PPIDE_PUTBUF1: + CALL PPIDE_PUTBUF16 ; FIRST PASS (FIRST 256 BYTES) + CALL PPIDE_PUTBUF16 ; SECOND PASS (LAST 256 BYTES) +PPIDE_PUTBUF2: CALL PPIDE_WAITRDY ; PROBLEMS IF THIS IS REMOVED! RET NZ CALL PPIDE_GETRES JP NZ,PPIDE_IOERR RET ; -PPIDE_PUTBUF1: ; START OF READ LOOP -#IF (!PPIDE8BIT) - DEC C - OUTI ; PUT NEXT BYTE ON THE BUS (LSB) - INC C -#ENDIF - OUTI - LD A,D ; ASSERT WRITE - OUT (PPIDE_IO_CTL),A ; DO IT - LD A,E ; DEASSERT WRITE - OUT (PPIDE_IO_CTL),A ; DO IT +PPIDE_PUTBUF8: ; 8 BIT WIDE WRITE LOOP + DEC C ; CTL -> MSB + DEC C ; MSB -> LSB + OUTI ; WRITE NEXT BYTE (LSB) + INC C ; LSB -> MSB + INC C ; MSB -> CTL + OUT (C),D ; ASSERT WRITE + OUT (C),E ; DEASSERT WRITE + CP B ; B == A == 0? + JR NZ,PPIDE_PUTBUF8 ; LOOP UNTIL DONE + RET ; - JR NZ,PPIDE_PUTBUF1 ; LOOP UNTIL DONE +PPIDE_PUTBUF16: ; 16 BIT WIDE WRITE LOOP + DEC C ; CTL -> MSB + DEC C ; MSB -> LSB + OUTI ; WRITE NEXT BYTE (LSB) + INC C ; LSB -> MSB + OUTI ; WRITE NEXT BYTE (MSB) + INC C ; MSB -> CTL + OUT (C),D ; ASSERT WRITE + OUT (C),E ; DEASSERT WRITE + CP B ; B == A == 0? + JR NZ,PPIDE_PUTBUF16 ; LOOP UNTIL DONE RET ; ; @@ -833,16 +946,20 @@ PPIDE_GETRES: ;IN A,(PPIDE_REG_STAT) ; READ STATUS CALL PPIDE_IN .DB PPIDE_REG_STAT - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF AND %00000001 ; ERROR BIT SET? RET Z ; NOPE, RETURN WITH ZF ; ;IN A,(PPIDE_REG_ERR) ; READ ERROR REGISTER CALL PPIDE_IN .DB PPIDE_REG_ERR - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF OR $FF ; FORCE NZ TO SIGNAL ERROR RET ; RETURN ; @@ -853,18 +970,27 @@ PPIDE_GETRES: ; SOFT RESET OF ALL DEVICES ON BUS ; PPIDE_RESET: +#IF (PPIDETRACE >= 3) + CALL PPIDE_PRTPREFIX + PRTS(" RESET$") +#ENDIF ; ; SETUP PPI TO READ LD A,PPIDE_DIR_READ ; SET DATA BUS DIRECTION TO READ - OUT (PPIDE_IO_PPI),A ; DO IT + ;OUT (PPIDE_IO_PPI),A ; DO IT + LD C,(IY+PPIDE_PPI) ; PPI CONTROL WORD + OUT (C),A ; WRITE IT ; ; PULSE IDE RESET LINE LD A,PPIDE_CTL_RESET - OUT (PPIDE_IO_CTL),A + ;OUT (PPIDE_IO_CTL),A + LD C,(IY+PPIDE_CTL) ; SET IDE ADDRESS + OUT (C),A LD DE,20 CALL VDELAY XOR A - OUT (PPIDE_IO_CTL),A + ;OUT (PPIDE_IO_CTL),A + OUT (C),A LD DE,20 CALL VDELAY ; @@ -883,27 +1009,17 @@ PPIDE_RESET: LD DE,150000/16 ; ~???MS CALL VDELAY ; - ;; CLEAR OUT ALL DATA (FOR ALL UNITS) - ;LD HL,PPIDE_UDATA - ;LD BC,PPIDE_UDLEN - ;XOR A - ;CALL FILL -; - PUSH IY ; SAVE CURRENT DEVICE CFG PTR -; - ; PROBE / INITIALIZE ALL UNITS - LD B,PPIDE_DEVCNT ; NUMBER OF UNITS TO TRY - LD IY,PPIDE_CFGTBL ; START OF CFG TABLE -PPIDE_RESET1: - PUSH BC ; SAVE LOOP CONTROL - CALL PPIDE_INITUNIT ; PROBE/INIT UNIT - LD BC,PPIDE_CFGSIZ ; SIZE OF CFG ENTRY - ADD IY,BC ; BUMP IY TO NEXT ENTRY - POP BC ; RESTORE BC - DJNZ PPIDE_RESET1 ; LOOP AS NEEDED -; - POP IY ; RECOVER DEVICE CFG PTR -; + ; INITIALIZE THE INDIVIDUAL UNITS (MASTER AND SLAVE). + ; BASED ON TESTING, IT APPEARS THAT THE MASTER UNIT MUST + ; BE DONE FIRST OR THIS BEHAVES BADLY. + PUSH IY ; SAVE CFG PTR + BIT 0,(IY+PPIDE_ACC) ; MASTER? + CALL Z,PPIDE_GOPARTNER ; IF NOT, SWITCH TO MASTER + CALL PPIDE_INITUNIT ; INIT CURRENT UNIT + CALL PPIDE_GOPARTNER ; POINT TO SLAVE + CALL PPIDE_INITUNIT ; INIT PARTNER UNIT + POP IY ; RECOVER ORIG CFG PTR +; XOR A ; SIGNAL SUCCESS RET ; AND DONE ; @@ -917,9 +1033,10 @@ PPIDE_INITUNIT: LD (HL),PPIDE_TONORM ; SET NORMAL TIMEOUT CALL PPIDE_PROBE ; DO PROBE - CALL Z,PPIDE_INITDEV ; IF FOUND, ATTEMPT TO INIT DEVICE + RET NZ ; JUST RETURN IF NOTHING THERE - RET + CALL PPIDE_INITDEV ; IF FOUND, ATTEMPT TO INIT DEVICE + RET ; DONE ; ; TAKE ANY ACTIONS REQUIRED TO SELECT DESIRED PHYSICAL UNIT ; @@ -928,18 +1045,15 @@ PPIDE_SELUNIT: CALL PPIDE_PRTPREFIX PRTS(" SELUNIT$") #ENDIF - PUSH HL ; SAVE HL, IT IS DESTROYED BELOW - PUSH IY - POP BC - LD A,(IY+PPIDE_DEV) ; GET DEVICE - AND $01 ; LS BIT DETERMINES MASTER/SLAVE - LD HL,PPIDE_DRVSEL - CALL ADDHLA - LD A,(HL) ; LOAD DRIVE/HEAD VALUE - POP HL ; RECOVER HL + BIT 0,(IY+PPIDE_ACC) ; MASTER? + JR Z,PPIDE_SELUNIT1 ; HANDLE SLAVE + LD A,PPIDE_DRVMASTER ; MASTER + JR PPIDE_SELUNIT2 +PPIDE_SELUNIT1: + LD A,PPIDE_DRVSLAVE ; SLAVE +PPIDE_SELUNIT2: LD (PPIDE_DRVHD),A ; SAVE IT -; - XOR A + XOR A ; SUCCESS RET ; ; @@ -954,8 +1068,10 @@ PPIDE_PROBE: ;OUT (IDE_IO_DRVHD),A CALL PPIDE_OUT .DB PPIDE_REG_DRVHD - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF CALL DELAY ; DELAY ~16US ; @@ -968,16 +1084,22 @@ PPIDE_PROBE: ; THERE, THEN THE VALUE WRITTEN TO PPI PORT A IS IGNORED ; BECAUSE THE WRITE SIGNAL IS NEVER PULSED. XOR A - OUT (PPIDE_IO_DATALO),A + ;OUT (PPIDE_IO_DATALO),A + LD C,(IY+PPIDE_DATALO) ; PPI PORT A, DATALO + OUT (C),A ; IN A,(PPIDE_REG_STAT) ; GET STATUS CALL PPIDE_IN .DB PPIDE_REG_STAT - DCALL PC_SPACE - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE +#ENDIF OR A JP Z,PPIDE_NOMEDIA ; - DCALL PPIDE_REGDUMP +#IF (PPIDETRACE >= 3) + CALL PPIDE_REGDUMP +#ENDIF ; ;JR PPIDE_PROBE1 ; *DEBUG* ; @@ -985,44 +1107,64 @@ PPIDE_PROBE0: CALL PPIDE_WAITBSY ; WAIT FOR BUSY TO CLEAR JP NZ,PPIDE_NOMEDIA ; CONVERT TIMEOUT TO NO MEDIA AND RETURN ; - DCALL PPIDE_REGDUMP +#IF (PPIDETRACE >= 3) + CALL PPIDE_REGDUMP +#ENDIF ; ; CHECK STATUS ; IN A,(PPIDE_REG_STAT) ; GET STATUS CALL PPIDE_IN .DB PPIDE_REG_STAT - DCALL PC_SPACE - DCALL PRTHEXBYTE ; IF DEBUG, PRINT STATUS +#IF (PPIDETRACE >= 3) + CALL PC_SPACE + CALL PRTHEXBYTE ; IF DEBUG, PRINT STATUS +#ENDIF OR A ; SET FLAGS TO TEST FOR ZERO JP Z,PPIDE_NOMEDIA ; CONTINUE IF NON-ZERO ; ; CHECK SIGNATURE - DCALL PC_SPACE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE +#ENDIF ;IN A,(PPIDE_REG_COUNT) CALL PPIDE_IN .DB PPIDE_REG_COUNT - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PRTHEXBYTE +#ENDIF CP $01 JP NZ,PPIDE_NOMEDIA - DCALL PC_SPACE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE +#ENDIF ;IN A,(PPIDE_REG_SECT) CALL PPIDE_IN .DB PPIDE_REG_SECT - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PRTHEXBYTE +#ENDIF CP $01 JP NZ,PPIDE_NOMEDIA - DCALL PC_SPACE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE +#ENDIF ;IN A,(PPIDE_REG_CYLLO) CALL PPIDE_IN .DB PPIDE_REG_CYLLO - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PRTHEXBYTE +#ENDIF CP $00 JP NZ,PPIDE_NOMEDIA - DCALL PC_SPACE +#IF (PPIDETRACE >= 3) + CALL PC_SPACE +#ENDIF ;IN A,(PPIDE_REG_CYLHI) CALL PPIDE_IN .DB PPIDE_REG_CYLHI - DCALL PRTHEXBYTE +#IF (PPIDETRACE >= 3) + CALL PRTHEXBYTE +#ENDIF CP $00 JP NZ,PPIDE_NOMEDIA ; @@ -1041,52 +1183,32 @@ PPIDE_INITDEV: OR A ; SET FLAGS JP Z,PPIDE_NOMEDIA ; EXIT SETTING NO MEDIA STATUS ; - ; CLEAR OUT UNIT SPECIFIC DATA, BUT PRESERVE THE EXISTING - ; VALUE OF THE UNIT TYPE WHICH WAS ESTABLISHED BY THE DEVICE - ; PROBES WHEN THE PPIDE BUS WAS RESET - ;PUSH AF ; SAVE UNIT TYPE VALUE FROM ABOVE - ;LD A,(IY+PPIDE_DEV) ; GET CURRENT DEVICE NUMBER - ;PUSH AF ; ... AND SAVE IT - ;;PUSH HL ; SAVE UNIT TYPE FIELD POINTER - ;;PPIDE_DPTR(0) ; SET HL TO START OF UNIT DATA - ;;LD BC,PPIDE_UDLEN - ;;XOR A - ;;CALL FILL - ;;POP HL ; RECOVER UNIT TYPE FIELD POINTER - ;;POP AF ; RECOVER UNIT TYPE VALUE - ;PUSH IY ; SET HL TO - ;POP HL ; ... START OF DEVICE INSTANCE DATA - ;LD BC,PPIDE_CFGSIZ ; SIZE OF CONFGI DATA TO CLEAR - ;XOR A ; FILL WITH ZERO - ;CALL FILL ; DO IT - ;POP AF ; RECOVER DEVICE NUMBER VALUE - ;LD (IY+PPIDE_DEV),A ; ... AND PUT IT BACK - ;POP AF ; RECOVER DEVICE TYPE VALUE - ;LD (IY+PPIDE_TYPE),A ; ... AND PUT IT BACK -; -#IF (PPIDE8BIT) + BIT 1,(IY+PPIDE_ACC) ; 8 BIT ACCESS? + JR Z,PPIDE_INITDEV0 ; NO, DO 16 BIT INIT LD A,PPIDE_FEAT_ENABLE8BIT ; FEATURE VALUE = ENABLE 8-BIT PIO -#ELSE - LD A,PPIDE_FEAT_DISABLE8BIT ; FEATURE VALUE = DISABLE 8-BIT PIO -#ENDIF CALL PPIDE_SETFEAT ; SET FEATURE - -#IF (PPIDE8BIT) + RET NZ ; BAIL OUT ON ERROR + JR PPIDE_INITDEV00 ; CONTINUE +; +PPIDE_INITDEV0: ; "REAL" IDE DRIVES MAY NOT ACCEPT THE DISABLE8BIT FEATURE COMMAND, ; SO IT IS ONLY AN ERROR IF WE ARE ATTEMPTING TO ENABLE8BIT. - ; CREDIT TO ED BRINDLEY FOR THIS CORRECTION. - RET NZ ; BAIL OUT ON ERROR -#ENDIF + ; CREDIT TO ED BRINDLEY FOR THIS CORRECTION. SO ERROR RETURN IGNORED HERE. + LD A,PPIDE_FEAT_DISABLE8BIT ; FEATURE VALUE = ENABLE 8-BIT PIO + CALL PPIDE_SETFEAT ; SET FEATURE, IGNORE ERRORS +; +PPIDE_INITDEV00: ; CALL PPIDE_IDENTIFY ; EXECUTE PPIDENTIFY COMMAND RET NZ ; BAIL OUT ON ERROR ; LD DE,HB_WRKBUF ; POINT TO BUFFER - DCALL DUMP_BUFFER ; DUMP IT IF DEBUGGING +#IF (PPIDETRACE >= 3) + CALL DUMP_BUFFER ; DUMP IT IF DEBUGGING +#ENDIF +; + LD (IY+PPIDE_MED),0 ; CLEAR MEDIA FLAGS ; - XOR A - LD (IY+PPIDE_FLAGS),0 ; CLEAR FLAGS - ; DETERMINE IF CF DEVICE LD HL,HB_WRKBUF ; FIRST WORD OF IDENTIFY DATA HAS CF FLAG LD A,$8A ; FIRST BYTE OF MARKER IS $8A @@ -1096,14 +1218,14 @@ PPIDE_INITDEV: LD A,$84 ; SECOND BYTE OF MARKER IS $84 CP (HL) ; COMPARE JR NZ,PPIDE_INITDEV1 ; IF NOT MATCH, NOT CF - SET 0,(IY+PPIDE_FLAGS) ; SET FLAGS BIT FOR CF MEDIA + SET 0,(IY+PPIDE_MED) ; SET FLAGS BIT FOR CF MEDIA ; PPIDE_INITDEV1: ; DETERMINE IF LBA CAPABLE LD A,(HB_WRKBUF+98+1) ; GET BYTE WITH LBA BIT FROM BUFFER BIT 1,A ; CHECK THE LBA BIT JR Z,PPIDE_INITDEV2 ; NOT SET, BYPASS - SET 1,(IY+PPIDE_FLAGS) ; SET FLAGS BIT FOR LBA + SET 1,(IY+PPIDE_MED) ; SET FLAGS BIT FOR LBA ; PPIDE_INITDEV2: ; GET DEVICE CAPACITY AND SAVE IT @@ -1123,15 +1245,24 @@ PPIDE_INITDEV2: ; RET ; RETURN, A=0, Z SET ; +; SWITCH IY POINTER FROM CURRENT UNIT CFG TO PARTNER UNIT CFG +; +PPIDE_GOPARTNER: + PUSH HL ; SAVE HL + LD L,(IY+PPIDE_PARTNER) ; GET PARTNER ENTRY + LD H,(IY+PPIDE_PARTNER+1) ; ... + PUSH HL ; MOVE HL + POP IY ; ... TO IY + POP HL ; RESTORE INCOMING HL + RET ; AND DONE ; +; CHECK CURRENT DEVICE FOR ERROR STATUS AND ATTEMPT TO RECOVER +; VIA RESET IF DEVICE IS IN ERROR. ; -PPIDE_CHKDEVICE: +PPIDE_CHKERR: LD A,(IY+PPIDE_STAT) ; GET STATUS OR A ; SET FLAGS - RET Z ; RETURN IF ALL IS WELL -; - ; ATTEMPT TO REINITIALIZE HERE??? - JP PPIDE_ERR + CALL NZ,PPIDE_RESET ; IF ERROR STATUS, RESET BUS RET ; ; @@ -1140,7 +1271,7 @@ PPIDE_WAITRDY: LD A,(PPIDE_TIMEOUT) ; GET TIMEOUT IN 0.05 SECS LD B,A ; PUT IN OUTER LOOP VAR PPIDE_WAITRDY1: - LD DE,(PPIDE_TOSCALER) ; CPU SPPED SCALER TO INNER LOOP VAR + LD DE,(PPIDE_TOSCALER) ; CPU SPEED SCALER TO INNER LOOP VAR PPIDE_WAITRDY2: ;IN A,(PPIDE_REG_STAT) ; READ STATUS CALL PPIDE_IN @@ -1162,7 +1293,7 @@ PPIDE_WAITDRQ: LD A,(PPIDE_TIMEOUT) ; GET TIMEOUT IN 0.05 SECS LD B,A ; PUT IN OUTER LOOP VAR PPIDE_WAITDRQ1: - LD DE,(PPIDE_TOSCALER) ; CPU SPPED SCALER TO INNER LOOP VAR + LD DE,(PPIDE_TOSCALER) ; CPU SPEED SCALER TO INNER LOOP VAR PPIDE_WAITDRQ2: ;IN A,(PPIDE_REG_STAT) ; READ STATUS CALL PPIDE_IN @@ -1184,7 +1315,7 @@ PPIDE_WAITBSY: LD A,(PPIDE_TIMEOUT) ; GET TIMEOUT IN 0.05 SECS LD B,A ; PUT IN OUTER LOOP VAR PPIDE_WAITBSY1: - LD DE,(PPIDE_TOSCALER) ; CPU SPPED SCALER TO INNER LOOP VAR + LD DE,(PPIDE_TOSCALER) ; CPU SPEED SCALER TO INNER LOOP VAR PPIDE_WAITBSY2: ;IN A,(PPIDE_REG_STAT) ; READ STATUS CALL PPIDE_IN ; 17TS + 170TS @@ -1199,49 +1330,66 @@ PPIDE_WAITBSY2: DJNZ PPIDE_WAITBSY1 ; ----- JP PPIDE_BSYTO ; EXIT WITH BSYTO ERR ; 229TS ; -; -; +; READ A VALUE FROM THE DEVICE POINTED TO BY IY AND RETURN IT IN A +; PPIDE_IN: - LD A,PPIDE_DIR_READ ; SET DATA BUS DIRECTION TO READ ; 7TS - OUT (PPIDE_IO_PPI),A ; DO IT ; 11TS EX (SP),HL ; GET PARM POINTER ; 19TS PUSH BC ; SAVE INCOMING BC ; 11TS + LD A,PPIDE_DIR_READ ; SET DATA BUS DIRECTION TO READ ; 7TS + ;OUT (PPIDE_IO_PPI),A ; DO IT ; 11TS + LD C,(IY+PPIDE_PPI) ; PPI CONTROL WORD + OUT (C),A ; WRITE IT +; LD B,(HL) ; GET CTL PORT VALUE ; 7TS - LD C,PPIDE_IO_CTL ; SETUP PORT TO WRITE ; 7TS + ;LD C,PPIDE_IO_CTL ; SETUP PORT TO WRITE ; 7TS + ;LD C,(IY+PPIDE_CTL) ; SET IDE ADDRESS + DEC C ; SET IDE ADDRESS OUT (C),B ; SET ADDRESS LINES ; 12TS - SET 6,B ; TURN ON WRITE BIT ; 8TS - OUT (C),B ; ASSERT WRITE LINE ; 12TS - ;NOP - ;NOP - IN A,(PPIDE_IO_DATALO) ; GET DATA VALUE FROM DEVICE ; 11TS - ;NOP - ;NOP - RES 6,B ; CLEAR WRITE BIT ; 8TS - OUT (C),B ; DEASSERT WRITE LINE ; 12TS + SET 6,B ; TURN ON READ BIT ; 8TS + OUT (C),B ; ASSERT READ LINE ; 12TS +; + ;IN A,(PPIDE_IO_DATALO) ; GET DATA VALUE FROM DEVICE ; 11TS + DEC C + DEC C + IN A,(C) ; GET DATA VALUE FROM DEVICE + INC C + INC C +; + RES 6,B ; CLEAR READ BIT ; 8TS + OUT (C),B ; DEASSERT READ LINE ; 12TS POP BC ; RECOVER INCOMING BC ; 10TS INC HL ; POINT PAST PARM ; 6TS EX (SP),HL ; RESTORE STACK ; 19TS RET ; 10TS -; ; ----- -; ; 170TS ; +; OUTPUT VALUE IN A TO THE DEVICE POINTED TO BY IY +; PPIDE_OUT: + ; *** TODO *** FIX ORDER OF SET/CLEAR WRITE LINE + EX (SP),HL ; GET PARM POINTER + PUSH BC ; SAVE INCOMING BC PUSH AF ; PRESERVE INCOMING VALUE LD A,PPIDE_DIR_WRITE ; SET DATA BUS DIRECTION TO WRITE - OUT (PPIDE_IO_PPI),A ; DO IT + ;OUT (PPIDE_IO_PPI),A ; DO IT + LD C,(IY+PPIDE_PPI) ; PPI CONTROL WORD + OUT (C),A ; WRITE IT POP AF ; RECOVER VALUE TO WRITE - EX (SP),HL ; GET PARM POINTER - PUSH BC ; SAVE INCOMING BC +; LD B,(HL) ; GET IDE ADDRESS VALUE - LD C,PPIDE_IO_CTL ; SETUP PORT TO WRITE + ;LD C,PPIDE_IO_CTL ; SETUP PORT TO WRITE + ;LD C,(IY+PPIDE_CTL) ; SET IDE ADDRESS + DEC C ; SET IDE ADDRESS OUT (C),B ; SET ADDRESS LINES SET 5,B ; TURN ON WRITE BIT OUT (C),B ; ASSERT WRITE LINE - ;NOP - ;NOP - OUT (PPIDE_IO_DATALO),A ; SEND DATA VALUE TO DEVICE - ;NOP - ;NOP +; + DEC C + DEC C + ;OUT (PPIDE_IO_DATALO),A ; SEND DATA VALUE TO DEVICE + OUT (C),A ; SEND DATA VALUE TO DEVICE + INC C + INC C +; RES 5,B ; CLEAR WRITE BIT OUT (C),B ; DEASSERT WRITE LINE POP BC ; RECOVER INCOMING BC @@ -1351,26 +1499,40 @@ PPIDE_PRTSTAT3: PPIDE_REGDUMP: PUSH AF PUSH BC + push DE CALL PC_SPACE CALL PC_LBKT LD A,PPIDE_DIR_READ ; SET DATA BUS DIRECTION TO READ - OUT (PPIDE_IO_PPI),A ; DO IT - LD C,PPIDE_REG_CMD + ;OUT (PPIDE_IO_PPI),A ; DO IT + LD C,(IY+PPIDE_PPI) ; PPI CONTROL WORD + OUT (C),A ; WRITE IT + LD C,(IY+PPIDE_CTL) ; SET IDE ADDRESS + LD E,PPIDE_REG_CMD LD B,7 PPIDE_REGDUMP1: - LD A,C ; REGISTER ADDRESS - OUT (PPIDE_IO_CTL),A ; SET IT + LD A,E ; REGISTER ADDRESS + ;OUT (PPIDE_IO_CTL),A ; SET IT + OUT (C),A ; REGISTER ADDRESS XOR PPIDE_CTL_DIOR ; SET BIT TO ASSERT READ LINE - OUT (PPIDE_IO_CTL),A ; ASSERT READ - IN A,(PPIDE_IO_DATALO) ; GET VALUE + ;OUT (PPIDE_IO_CTL),A ; ASSERT READ + OUT (C),A ; ASSERT READ + ;IN A,(PPIDE_IO_DATALO) ; GET VALUE + DEC C ; CTL -> MSB + DEC C ; MSB -> LSB + IN A,(C) ; GET VALUE + INC C ; LSB -> MSB + INC C ; MSB -> CTL CALL PRTHEXBYTE ; DISPLAY IT - LD A,C ; RELOAD ADDRESS W/ READ UNASSERTED - OUT (PPIDE_IO_CTL),A ; AND SET IT - DEC C ; NEXT LOWER REGISTER + ;LD A,C ; RELOAD ADDRESS W/ READ UNASSERTED + ;OUT (PPIDE_IO_CTL),A ; AND SET IT + OUT (C),E ; RELOAD ADDRESS W/ READ UNASSERTED + ;DEC C ; NEXT LOWER REGISTER + DEC E ; NEXT LOWER REGISTER DEC B ; DEC LOOP COUNTER CALL NZ,PC_SPACE ; FORMATTING JR NZ,PPIDE_REGDUMP1 ; LOOP AS NEEDED CALL PC_RBKT ; FORMATTING + POP DE POP BC POP AF RET @@ -1427,6 +1589,8 @@ PPIDE_STR_STBSYTO .TEXT "BUSY TIMEOUT$" PPIDE_STR_STUNK .TEXT "UNKNOWN ERROR$" ; PPIDE_STR_NO .TEXT "NO$" +PPIDE_STR_NOPPI .TEXT "PPI NOT PRESENT$" +PPIDE_STR_8BIT .TEXT " 8-BIT$" ; ;============================================================================= ; DATA STORAGE @@ -1440,3 +1604,5 @@ PPIDE_IOFNADR .DW 0 ; PENDING IO FUNCTION ADDRESS PPIDE_DRVHD .DB 0 ; CURRENT DRIVE/HEAD MASK ; PPIDE_DSKBUF .DW 0 ; ACTIVE DISK BUFFER +; +PPIDE_DEVNUM .DB 0 ; TEMP DEVICE NUM USED DURING INIT diff --git a/Source/HBIOS/ppk.asm b/Source/HBIOS/ppk.asm index 48cfd8d1..6672782b 100644 --- a/Source/HBIOS/ppk.asm +++ b/Source/HBIOS/ppk.asm @@ -786,8 +786,10 @@ PPK_DECNEW: ; START NEW KEYPRESS (CLEAR ALL STATUS BITS) LD (PPK_STATUS),A ; CLEAR STATUS JP PPK_DEC1 ; RESTART THE ENGINE ; +#IF (PPKKBLOUT == KBD_US) ;__________________________________________________________________________________________________ -; MAPPING TABLES +; +; MAPPING TABLES US/ENGLISH ;__________________________________________________________________________________________________ ; PPK_MAPSTD: ; SCANCODE IS INDEX INTO TABLE TO RESULTANT LOOKUP KEYCODE @@ -825,8 +827,56 @@ PPK_MAPEXT: ; PAIRS ARE [SCANCODE,KEYCODE] FOR EXTENDED SCANCODES PPK_MAPNUMPAD: ; KEYCODE TRANSLATION FROM NUMPAD RANGE TO STD ASCII/KEYCODES .DB $F3,$F7,$F5,$F8,$FF,$F9,$F2,$F6,$F4,$F0,$F1,$2F,$2A,$2D,$2B,$0D .DB $31,$32,$33,$34,$35,$36,$37,$38,$39,$30,$2E,$2F,$2A,$2D,$2B,$0D +#ENDIF +#IF (PPKKBLOUT == KBD_DE) +;__________________________________________________________________________________________________ +; +; MAPPING TABLES GERMAN +;__________________________________________________________________________________________________ +; +PPK_MAPSTD: ; SCANCODE IS INDEX INTO TABLE TO RESULTANT LOOKUP KEYCODE ROW + +; Column 0 1 2 3 4 5 6 7 8 9 A B C D E F ; Special adjustments listed below + .DB $FF,$E8,$FF,$E4,$E2,$E0,$E1,$EB,$FF,$E9,$E7,$E5,$E3,$09,'^',$FF ;0 for German keyboard keys that give + .DB $FF,$B4,$B0,$FF,$B2,'q','1',$FF,$FF,$FF,'y','s','a','w','2',$FF ;1 different characters than are printed + .DB $FF,'c','x','d','e','4','3',$FF,$FF,' ','v','f','t','r','5',$FF ;2 on the keys. + .DB $FF,'n','b','h','g','z','6',$FF,$FF,$FF,'m','j','u','7','8',$FF ;3 'german key' --> 'new occupied with' + .DB $FF,',','k','i','o','0','9',$FF,$FF,'.','-','l','[','p',$5C,$FF ;4 Assembler ERROR: '\'-->$5C ; 'ö'-->'[' + .DB $FF,$FF,'@',$FF,']','|',$FF,$FF,$BC,$B1,$0D,'+',$FF,'#',$FF,$FF ;5 'ä'-->'@' ; 'ü'-->']' + .DB $FF,'<',$FF,$FF,$FF,$FF,$08,$FF,$FF,$C0,$FF,$C3,$C6,'<',$FF,$FF ;6 + .DB $C9,$CA,$C1,$C4,$C5,$C7,$1B,$BD,$FA,$CE,$C2,$CD,$CC,$C8,$BE,$FF ;7 + .DB $FF,$FF,$FF,$E6,$EC ;8 + +PPK_MAPSIZ .EQU ($ - PPK_MAPSTD) ; +PPK_MAPSHIFT: ; SCANCODE IS INDEX INTO TABLE TO RESULTANT LOOKUP KEYCODE WHEN SHIFT ACTIVE + + .DB $FF,$E8,$FF,$E4,$E2,$E0,$E1,$EB,$FF,$E9,$E7,$E5,$E3,$09,'~',$FF ; '°' --> '~' + .DB $FF,$B4,$B0,$FF,$B2,'Q','!',$FF,$FF,$FF,'Y','S','A','W',$22,$FF + .DB $FF,'C','X','D','E','$',$20,$FF,$FF,' ','V','F','T','R','%',$FF ; '§'-->$20; '§'=Paragraph not used in CP/M + .DB $FF,'N','B','H','G','Z','&',$FF,$FF,$FF,'M','J','U','/','(',$FF + .DB $FF,';','K','I','O','=',')',$FF,$FF,':','_','L','{','P','?',$FF ; 'Ö'-->'{' + .DB $FF,$FF,'@',$FF,'}','`',$FF,$FF,$BC,$B1,$0D,'*',$FF,$27,$FF,$FF ; 'Ä'-->'@' ; 'Ü'-->'}' + .DB $FF,'>',$FF,$FF,$FF,$FF,$08,$FF,$FF,$D0,$FF,$D3,$D6,'>',$FF,$FF + .DB $D9,$DA,$D1,$D4,$D5,$D7,$1B,$BD,$FA,$DE,$D2,$DD,$DC,$D8,$BE,$FF + .DB $FF,$FF,$FF,$E6,$EC + +PPK_MAPEXT: ; PAIRS ARE [SCANCODE,KEYCODE] FOR EXTENDED SCANCODES + .DB $11,$B5, $14,$B3, $1F,$B6, $27,$B7 + .DB $2F,$EF, $37,$FA, $3F,$FB, $4A,$CB ; All keys listed below are customized for Wordstar. + .DB $5A,$CF, $5E,$FC, $69,$06, $6B,$13 ; n.a , n.a , word right , n.a. + .DB $6C,$01, $70,$16, $71,$07, $72,$18 ; Word left , Toggle Insert/Overwrite , Del Char , Cursor down + .DB $74,$04, $75,$05, $7A,$1A, $7C,$ED ; Cursor right , Cursor up , Page down + .DB $7D,$17, $7E,$FD, $00,$00 ; Page up , n.a. , END PPK_MAPEXT (Pairs end) +; +PPK_MAPNUMPAD: ; KEYCODE TRANSLATION FROM NUMPAD RANGE TO STD ASCII/KEYCODES + + .DB $F3,$F7,$F5,$F8,$FF,$F9,$F2,$F6,$F4,$F0,$F1,$2F,$2A,$2D,$2B,$0D + .DB $31,$32,$33,$34,$35,$36,$37,$38,$39,$30,$2E,$2F,$2A,$2D,$2B,$0D +; +#ENDIF ;__________________________________________________________________________________________________ +; ; KEYCODE VALUES RETURNED BY THE DECODER ;__________________________________________________________________________________________________ ; diff --git a/Source/HBIOS/ppp.asm b/Source/HBIOS/ppp.asm index ec4782a6..0b45b1eb 100644 --- a/Source/HBIOS/ppp.asm +++ b/Source/HBIOS/ppp.asm @@ -353,7 +353,7 @@ PPPCON_QUERY: PPPCON_DEVICE: LD D,CIODEV_PPPCON ; D := DEVICE TYPE LD E,0 ; E := DEVICE NUM, ALWAYS 0 - LD C,$FF ; $FF MEANS TERM W/ NO ATTACHED VDA + LD C,$BF ; C := DEVICE TYPE, 0xBF IS PROP TERM XOR A ; SIGNAL SUCCESS RET ; @@ -504,6 +504,7 @@ PPPSD_DEFMED: ; ; PPPSD_READ: + CALL HB_DSKREAD ; HOOK HBIOS DISK READ SUPERVISOR LD BC,PPPSD_RDSEC ; GET ADR OF SECTOR READ FUNC LD (PPPSD_IOFNADR),BC ; SAVE IT AS PENDING IO FUNC JR PPPSD_IO ; CONTINUE TO GENERIC IO ROUTINE @@ -511,6 +512,7 @@ PPPSD_READ: ; ; PPPSD_WRITE: + CALL HB_DSKWRITE ; HOOK HBIOS DISK WRITE SUPERVISOR LD BC,PPPSD_WRSEC ; GET ADR OF SECTOR READ FUNC LD (PPPSD_IOFNADR),BC ; SAVE IT AS PENDING IO FUNC JR PPPSD_IO ; CONTINUE TO GENERIC IO ROUTINE @@ -689,7 +691,7 @@ PPPSD_DEVICE: PPPSD_MEDIA: ; REINITIALIZE THE CARD HERE TO DETERMINE PRESENCE CALL PPPSD_INITCARD -#IF (PPPSDTRACE == 1) +#IF (PPPSDTRACE >= 3) CALL PPPSD_PRTERR ; PRINT ANY ERRORS #ENDIF LD E,MID_HD ; ASSUME WE ARE OK diff --git a/Source/HBIOS/prefix.asm b/Source/HBIOS/prefix.asm deleted file mode 100644 index 0916b3ef..00000000 --- a/Source/HBIOS/prefix.asm +++ /dev/null @@ -1,221 +0,0 @@ -;=============================================================================== -; PREFIX.ASM -; -; CP/M DISK FORMATS ALLOW FOR RESERVED TRACKS THAT CONTAIN AN IMAGE OF THE -; OPERATING SYSTEM TO BE LOADED WHEN THE DISK IS BOOTED. THE OPERATING SYSTEM -; IMAGE ITSELF IS NORMALLY PREFIXED BY A 1-N SECTORS CONTAINING OS BOOTSTRAP -; CODE AND DISK METADATA. -; -; THE RETROBREW COMPUTING GROUP HAS BEEN USING A CONVENTION OF PREFIXING THE -; OS IMAGE WITH 3 SECTORS (512 BYTES X 3 FOR A TOTAL OF 1536 BYTES): -; -; SECTOR 1: IBM-PC STYLE BOOT BLOCK CONTAINING BOOTSTRAP, -; PARTITION TABLE, AND BOOT SIGNATURE -; SECTOR 2: RESERVED -; SECTOR 3: METADATA -; -; THE HARDWARE BIOS IS EXPECTED TO READ AND LOAD THE FIRST TWO SECTORS FROM THE -; DISK TO MEMORY ADDRESS $8000 AND JUMP TO THAT LOCATION TO BEGIN THE BOOT -; PROCESS. THE BIOS IS EXPECTED TO VERIFY THAT A STANDARD BOOT SIGNATURE -; OF $55, $AA IS PRESENT AT OFFSET $1FE-$1FF. IF THE SIGNATURE IS NOT FOUND, -; THE BIOS SHOULD ASSUME THE DISK HAS NOT BEEN PROPERLY INITIALIZED AND SHOULD -; NOT JUMP TO THE LOAD ADDRESS. -; -;=============================================================================== -; -#INCLUDE "std.asm" -BYT .EQU 1 ; used to describe METADATA_SIZE below -WRD .EQU 2 -; -SECTOR_SIZE .EQU 512 -BLOCK_SIZE .EQU 128 -PREFIX_SIZE .EQU (3 * SECTOR_SIZE) ; 3 SECTORS -METADATA_SIZE .EQU BYT+WRD+(4*BYT)+16+BYT+WRD+WRD+WRD+WRD ; (as defined below) -; -PARTTBL_LOC .EQU $1BE -PARTTBL_SIZ .EQU $40 -BOOTSIG_LOC .EQU $1FE -; -;------------------------------------------------------------------------------- -; SECTOR 1 -; -; THIS SECTOR FOLLOWS THE CONVENTIONS OF AN IBM-PC MBR CONTAINING THE OS -; BOOTSTRAP CODE, PARTITION TABLE, AND BOOT SIGNATURE -; -;---------------------------------------------------------------------------- -; -; THE FOLLOWING BOOTSTRAP CODE IS BUILT TO ASSUME IT WILL BE EXECUTED AT A STARTING -; ADDRESS OF $8000. -; - .ORG $8000 - JR BOOT -; -BOOT: - LD DE,STR_LOAD ; LOADING STRING - CALL PRTSTR ; PRINT - CALL PRTDOT ; PROGRESS -; - LD BC,$00FC ; UNA FUNC: GET BOOTSTRAP HISTORY - CALL $FFFD ; CALL UNA - JR NZ,ERROR ; HANDLE ERROR - CALL PRTDOT ; PROGRESS - LD B,L ; MOVE BOOT UNIT ID TO B -; - LD C,$41 ; UNA FUNC: SET LBA - LD DE,0 ; HI WORD ALWAYS ZERO - LD HL,3 ; IMAGE STARTS AT FOURTH SECTOR - CALL $FFFD ; SET LBA - JR NZ,ERROR ; HANDLE ERROR - CALL PRTDOT ; PROGRESS -; - LD C,$42 ; UNA FUNC: READ SECTORS - LD DE,$D000 ; STARTING ADDRESS FOR IMAGE - LD L,22 ; READ 22 SECTORS - CALL $FFFD ; DO READ - JR NZ,ERROR ; HANDLE ERROR - CALL PRTDOT ; PROGRESS -; - LD DE,STR_DONE ; DONE MESSAGE - CALL PRTSTR ; PRINT IT -; - LD D,B ; PASS BOOT UNIT TO OS - LD E,0 ; ASSUME LU IS ZERO - JP CPM_ENT ; GO TO CPM -; -PRTCHR: - PUSH BC - PUSH DE - LD BC,$0012 ; UNIT 0, WRITE CHAR - LD E,A ; CHAR TO PRINT - CALL $FFFD ; PRINT - POP DE - POP BC - RET -; -PRTSTR: - PUSH BC - PUSH HL - LD BC,$0015 ; UNIT 0, WRITE CHARS UNTIL TERMINATOR - LD L,0 ; TERMINATOR IS NULL - CALL $FFFD ; PRINT - POP HL - POP BC - RET -; -PRTDOT: - LD A,'.' ; DOT CHARACTER - JR PRTCHR ; PRINT AND RETURN -; -; PRINT THE HEX BYTE VALUE IN A -; -PRTHEXBYTE: - PUSH AF - PUSH DE - CALL HEXASCII - LD A,D - CALL PRTCHR - LD A,E - CALL PRTCHR - POP DE - POP AF - RET -; -; CONVERT BINARY VALUE IN A TO ASCII HEX CHARACTERS IN DE -; -HEXASCII: - LD D,A - CALL HEXCONV - LD E,A - LD A,D - RLCA - RLCA - RLCA - RLCA - CALL HEXCONV - LD D,A - RET -; -; CONVERT LOW NIBBLE OF A TO ASCII HEX -; -HEXCONV: - AND 0FH ;LOW NIBBLE ONLY - ADD A,90H - DAA - ADC A,40H - DAA - RET -; -ERROR: - LD DE,STR_ERR ; POINT TO ERROR STRING - CALL PRTSTR ; PRINT IT - HALT ; HALT -; -; DATA -; -STR_LOAD .DB "\r\nLoading",0 -STR_DONE .DB "\r\n",0 -STR_ERR .DB " Read Error!",0 -; - .ORG $ - $8000 ; RESTORE ORG - .FILL PARTTBL_LOC - $ ; FILL TO START OF PARTITION TABLE -; -; RESERVE SPACE FOR STANDARD IBM-PC PARTITION TABLE. ALTHOUGH A -; PARTITION TABLE IS NOT RELEVANT FOR A FLOPPY DISK, IT DOES NO HARM. -; THE CONTENTS OF THE PARTITION TABLE MUST BE MANAGED BY FDISK80. -; -PARTTBL .FILL PARTTBL_SIZ,$00 ; PARTITION TABLE, FILL WITH ZEROES -; -; THE END OF THE FIRST SECTOR MUST CONTAIN THE TWO BYTE BOOT -; SIGNATURE. -; -BOOTSIG .DB $55,$AA ; STANDARD BOOT SIGNATURE -; -;------------------------------------------------------------------------------- -; SECTOR 2 -; -; THIS SECTOR HAS NOT BEEN DEFINED AND IS RESERVED. -; -;---------------------------------------------------------------------------- -; - .FILL 512,0 ; JUST FILL SECTOR WITH ZEROES -; -;------------------------------------------------------------------------------- -; SECTOR 3 -; -; OS AND DISK METADATA -; -;---------------------------------------------------------------------------- -; - .FILL 128 * 3,0 ; FIRST 384 BYTES ARE NOT YET DEFINED -; -; THE FOLLOWING TWO BYTES ARE AN ADDITIONAL SIGNATURE THAT IS VERIFIED BY -; SOME HARDWARE BIOSES. -; -PR_SIG .DB $5A,$A5 ; SIGNATURE GOES HERE -; -; FIRST CHUNK OF METADATA IMMEDIATELY FOLLOWS THE SIGNATURE BYTES -; -PR_PLATFORM .DB PLATFORM ; PLATFORM ID (SEE STD.ASM) -PR_DEVICE .DB 0 ; ? (PROBABLY UNUSED) -PR_FORMATTER .DB 0,0,0,0,0,0,0,0 ; ? (PROBABLY UNUSED) -PR_DRIVE .DB 0 ; ? (PROBABLY UNUSED) -PR_LOG_UNIT .DW 0 ; ? (PROBABLY UNUSED) -; -; FILLER TO PLACE SECOND CHUNK OF METADATA AT THE END OF THE SECTOR -; - .FILL ((PREFIX_SIZE - METADATA_SIZE) - $),00H -; -; SECOND CHUNK OF METADATA -; -PR_WP .DB 0 ; WRITE PROTECT BOOLEADN -PR_UPDSEQ .DW 0 ; PREFIX UPDATE SEQUENCE NUMBER (DEPRECATED?) -PR_VER .DB RMJ,RMN,RUP,RTP ; OS BUILD VERSION -PR_LABEL .DB "Unlabeled Drive ","$" ; DISK LABEL (EXACTLY 16 BYTES!!!) - .DW 0 ; DEPRECATED -PR_LDLOC .DW CPM_LOC ; ADDRESS TO START LOADING OS -PR_LDEND .DW CPM_END ; ADDRESS TO STOP LOADING OS -PR_ENTRY .DW CPM_ENT ; ADDRESS TO ENTER OS -; -; -; - .END diff --git a/Source/HBIOS/prp.asm b/Source/HBIOS/prp.asm index 9444eafd..bb95606f 100644 --- a/Source/HBIOS/prp.asm +++ b/Source/HBIOS/prp.asm @@ -126,7 +126,7 @@ PRPCON_COLS .EQU 80 ; PROPELLER VGA DISPLAY COLS PRPCON_INIT: ; CALL NEWLINE - PRTS("PRPCON: $") + PRTS("PRPCON:$") ; ; DISPLAY CONSOLE DIMENSIONS CALL PC_SPACE @@ -226,7 +226,7 @@ PRPCON_QUERY: PRPCON_DEVICE: LD D,CIODEV_PRPCON ; D := DEVICE TYPE LD E,0 ; E := DEVICE NUM, ALWAYS 0 - LD C,$FF ; $FF MEANS TERM W/ NO ATTACHED VDA + LD C,$BF ; C := DEVICE TYPE, 0xBF IS PROP TERM XOR A ; SIGNAL SUCCESS RET ; @@ -401,6 +401,7 @@ PRPSD_DEFMED: ; ; PRPSD_READ: + CALL HB_DSKREAD ; HOOK HBIOS DISK READ SUPERVISOR LD BC,PRPSD_RDSEC ; GET ADR OF SECTOR READ FUNC LD (PRPSD_IOFNADR),BC ; SAVE IT AS PENDING IO FUNC JR PRPSD_IO ; CONTINUE TO GENERIC IO ROUTINE @@ -408,6 +409,7 @@ PRPSD_READ: ; ; PRPSD_WRITE: + CALL HB_DSKWRITE ; HOOK HBIOS DISK WRITE SUPERVISOR LD BC,PRPSD_WRSEC ; GET ADR OF SECTOR READ FUNC LD (PRPSD_IOFNADR),BC ; SAVE IT AS PENDING IO FUNC JR PRPSD_IO ; CONTINUE TO GENERIC IO ROUTINE @@ -545,7 +547,7 @@ PRPSD_DEVICE: PRPSD_MEDIA: ; REINITIALIZE THE CARD HERE CALL PRPSD_INITCARD -#IF (PRPSDTRACE == 1) +#IF (PRPSDTRACE >= 3) CALL PRPSD_PRTERR ; PRINT ANY ERRORS #ENDIF LD E,MID_HD ; ASSUME WE ARE OK diff --git a/Source/HBIOS/rf.asm b/Source/HBIOS/rf.asm index d8009e09..2ecba90f 100644 --- a/Source/HBIOS/rf.asm +++ b/Source/HBIOS/rf.asm @@ -5,37 +5,66 @@ ; ; ; -RF_U0IO .EQU $A0 -RF_U1IO .EQU $A4 +RF_U0IO .EQU $A0 ; BASED ADDRESS OF RAMFLOPPY 1 +RF_U1IO .EQU $A4 ; BASED ADDRESS OF RAMFLOPPY 2 +RF_U2IO .EQU $A8 ; BASED ADDRESS OF RAMFLOPPY 3 +RF_U3IO .EQU $AC ; BASED ADDRESS OF RAMFLOPPY 4 ; ; IO PORT OFFSETS ; -RF_DAT .EQU 0 -RF_AL .EQU 1 -RF_AH .EQU 2 -RF_ST .EQU 3 +RF_DAT .EQU 0 ; DATA IN/OUT ONLT TO SRAM - R/W +RF_AL .EQU 1 ; ADDRESS LOW FOR RAMF MEMORY - W/O +RF_AH .EQU 2 ; ADDRESS HIGH FOR RAMF MEMORY - W/O +RF_ST .EQU 3 ; STATUS PORT - R/O ; -; MD DEVICE CONFIGURATION +; RF DEVICE CONFIGURATION ; RF_DEVCNT .EQU RFCNT ; NUMBER OF RF DEVICES SUPPORTED -RF_CFGSIZ .EQU 6 ; SIZE OF CFG TBL ENTRIES +RF_CFGSIZ .EQU 8 ; SIZE OF CFG TBL ENTRIES +RF_MAXRF .EQU 4 ; MAXIMUM NUMBERS OF DEVICES SUPPORTED ; RF_DEV .EQU 0 ; OFFSET OF DEVICE NUMBER (BYTE) RF_STAT .EQU 1 ; OFFSET OF STATUS (BYTE) RF_LBA .EQU 2 ; OFFSET OF LBA (DWORD) +RF_IOAD .EQU 7 ; OFFSET OF DEVICE IO ADDRESS + +#IF ($RF_DEVCNT > RF_MAXRF) + .ECHO "*** ONLY 4 RAM FLOPPY DEVICES SUPPORTED ***\n" +RF_DEVCNT .SET RF_MAXRF +#ENDIF ; ; DEVICE CONFIG TABLE (RAM DEVICE FIRST TO MAKE IT ALWAYS FIRST DRIVE) ; RF_CFGTBL: - ; DEVICE 0 ($A0) + ; DEVICE 0 .DB 0 ; DRIVER DEVICE NUMBER .DB 0 ; DEVICE STATUS .DW 0,0 ; CURRENT LBA -#IF (RF_DEVCNT >= 2) - ; DEVICE 1 ($A4) + .DB 0 ; UNUSED + .DB RF_U0IO ; DEVICE BASE ADDR +#IF (RF_DEVCNT > 1) + ; DEVICE 1 .DB 1 ; DEVICE NUMBER .DB 0 ; DEVICE STATUS .DW 0,0 ; CURRENT LBA + .DB 0 ; UNUSED + .DB RF_U1IO ; DEVICE BASE ADDR +#ENDIF +#IF (RF_DEVCNT > 2) + ; DEVICE 2 + .DB 2 ; DRIVER DEVICE NUMBER + .DB 0 ; DEVICE STATUS + .DW 0,0 ; CURRENT LBA + .DB 0 ; UNUSED + .DB RF_U2IO ; DEVICE BASE ADDR +#ENDIF +; ; DEVICE 3 +#IF (RF_DEVCNT > 3) + .DB 3 ; DEVICE NUMBER + .DB 0 ; DEVICE STATUS + .DW 0,0 ; CURRENT LBA + .DB 0 ; UNUSED + .DB RF_U3IO ; DEVICE BASE ADDR #ENDIF ; #IF ($ - RF_CFGTBL) != (RF_DEVCNT * RF_CFGSIZ) @@ -48,38 +77,12 @@ RF_CFGTBL: ; RF_INIT: CALL NEWLINE ; FORMATTING - PRTS("RF: IO=0x$") - LD A,RF_U0IO - CALL PRTHEXBYTE - PRTS(" DEVICES=$") - LD A,RF_DEVCNT - CALL PRTDECB - PRTS(" WP=$") - IN A,(RF_U0IO+3) - AND 1 - JR Z,NO_WP1 - PRTS("ON$") - JR D2_WP1 -NO_WP1: - PRTS("OFF$") -D2_WP1: - #IF (RF_DEVCNT >= 2) - IN A,(RF_U1IO+3) - AND 1 - JR Z,NO_WP2 - PRTS("-ON$") - JR D2_WP2 -NO_WP2: - PRTS("-OFF$") -D2_WP2: - - #ENDIF -; -; SETUP THE DIO TABLE ENTRIES -; + PRTS("RF:$") + LD B,RF_DEVCNT ; LOOP CONTROL LD IY,RF_CFGTBL ; START OF CFG TABLE RF_INIT0: + CALL RF_UNIT PUSH BC ; SAVE LOOP CONTROL LD BC,RF_FNTBL ; BC := FUNC TABLE ADR PUSH IY ; CFG ENTRY POINTER @@ -89,10 +92,30 @@ RF_INIT0: ADD IY,BC ; BUMP IY TO NEXT ENTRY POP BC ; RESTORE BC DJNZ RF_INIT0 ; LOOP AS NEEDED + + PRTS(" DEVICES=$") ; DISPLAY NUMBER + LD A,RF_DEVCNT ; OF DEVICES + CALL PRTDECB ; XOR A ; INIT SUCCEEDED RET ; RETURN ; +RF_UNIT: + PRTS(" IO=0x$") ; DISPLAY + LD A,(IY+RF_IOAD) ; PORT AND + CALL PRTHEXBYTE ; WRITE + PRTS(" WP=$") ; PROTECT + ADD A,RF_ST ; STATUS OF + LD C,A ; THIS DEVICE + IN A,(C) + AND 1 + JR Z,RF_NO_WP1 + PRTS("ON$") + RET +RF_NO_WP1: + PRTS("OFF$") + RET +; ; ; RF_FNTBL: @@ -154,7 +177,7 @@ RF_GEOM: RF_DEVICE: LD D,DIODEV_RF ; D := DEVICE TYPE LD E,(IY+RF_DEV) ; E := PHYSICAL DEVICE NUMBER - LD C,%00110000 ; C := ATTRIBUTES, NON-REMOVALBE RAM FLOPPY + LD C,%00110000 ; C := ATTRIBUTES, NON-REMOVABLE RAM FLOPPY XOR A ; SIGNAL SUCCESS RET ; @@ -182,6 +205,7 @@ RF_SEEK: ; ; RF_READ: + CALL HB_DSKREAD ; HOOK HBIOS DISK READ SUPERVISOR LD BC,RF_RDSEC ; GET ADR OF SECTOR READ FUNC LD (RF_RWFNADR),BC ; SAVE IT AS PENDING IO FUNC JR RF_RW ; CONTINUE TO GENERIC R/W ROUTINE @@ -189,6 +213,7 @@ RF_READ: ; ; RF_WRITE: + CALL HB_DSKWRITE ; HOOK HBIOS DISK WRITE SUPERVISOR LD BC,RF_WRSEC ; GET ADR OF SECTOR WRITE FUNC LD (RF_RWFNADR),BC ; SAVE IT AS PENDING IO FUNC CALL RF_CHKWP ; WRITE PROTECTED? @@ -263,32 +288,20 @@ RF_WRSEC: ; ; RF_SETIO: - LD A,(IY+RF_DEV) ; GET DEVICE NUM - OR A ; SET FLAGS - JR NZ,RF_SETIO1 - LD A,RF_U0IO - JR RF_SETIO3 -RF_SETIO1: - DEC A - JR NZ,RF_SETIO2 - LD A,RF_U1IO - JR RF_SETIO3 -RF_SETIO2: - CALL PANIC ; INVALID UNIT -RF_SETIO3: - LD (RF_IO),A - RET + LD A,(IY+RF_IOAD) ; GET THE IO PORT + LD (RF_IO),A ; OF THE DEVICE WE + RET ; ARE WORKING ON ; ; ; -RF_SETADR: - LD A,(RF_IO) - OR RF_AL - LD C,A - LD A,(IY+RF_LBA+0) - OUT (C),A - LD A,(IY+RF_LBA+1) - INC C +RF_SETADR: ; OUTPUT THE + LD A,(RF_IO) ; LOGICAL BLOCK + OR RF_AL ; ADDRESS TO THE + LD C,A ; TO THE MSB AND + LD A,(IY+RF_LBA+0) ; LSB SECTRK + OUT (C),A ; REGISTERS. + LD A,(IY+RF_LBA+1) ; BYTE COUNTER + INC C ; IS RESET OUT (C),A RET ; @@ -305,7 +318,7 @@ RF_CHKWP: ; ; ; -RF_IO .DB 0 +RF_IO .DB 0 ; PORT ADDRESS OF ACTIVE DEVICE RF_RWFNADR .DW 0 ; RF_DSKBUF .DW 0 diff --git a/Source/HBIOS/romldr.asm b/Source/HBIOS/romldr.asm index 8ed0b30d..747dd032 100644 --- a/Source/HBIOS/romldr.asm +++ b/Source/HBIOS/romldr.asm @@ -1,1081 +1,1619 @@ ; -;================================================================================================== -; ROMWBW LOADER -;================================================================================================== -; -; THE LOADER CODE IS INVOKED IMMEDIATELY AFTER HBIOS COMPLETES SYSTEM INITIALIZATION. -; IT IS RESPONSIBLE FOR LOADING A RUNNABLE IMAGE (OPERATING SYSTEM, ETC.) INTO MEMORY -; AND TRANSFERRING CONTROL TO THAT IMAGE. THE IMAGE MAY COME FROM ROM (ROMBOOT), -; RAM (APPBOOT/IMGBOOT) OR FROM DISK (DISK BOOT). -; -; IN THE CASE OF A ROM BOOT, THE SELECTED EXECUTABLE IMAGE IS COPIED FROM ROM -; INTO A THE DEFAULT RAM AND THEN CONTROL IS PASSED TO THE STARTING ADDRESS -; IN RAM. IN THE CASE OF AN APPBOOT OR IMGBOOT STARTUP (SEE HBIOS.ASM) -; THE SOURCE OF THE IMAGE MAY BE RAM. -; -; IN THE CASE OF A DISK BOOT, SECTOR 2 (THE THIRD SECTOR) OF THE DISK DEVICE WILL -; BE READ -- THIS IS REFERRED TO AS THE BOOT INFO SECTOR AND IS EXPECTED TO HAVE -; THE FORMAT DEFINED AT BL_INFOSEC BELOW. THE LAST THREE WORDS OF DATA IN THIS -; SECTOR DETERMINE THE FINAL DESTINATION STARTING AND ENDING ADDRESS FOR THE DISK -; LOAD OPERATION AS WELL AS THE ENTRY POINT TO TRANSFER CONTROL TO. THE ACTUAL -; IMAGE TO BE LOADED *MUST* BE ON THE DISK IN THE SECTORS IMMEDIATELY FOLLOWING -; THE BOOT INFO SECTOR. THIS MEANS THE IMAGE TO BE LOADED MUST BEGIN IN SECTOR -; 3 (THE FOURTH SECTOR) AND OCCUPY SECTORS CONTIGUOUSLY AFTER THAT. -; -; THE CODE BELOW RELOCATES ITSELF AT STARTUP TO THE START OF COMMON RAM -; AT $8000. THIS MEANS THAT THE CODE, DATA, AND STACK WILL ALL STAY -; WITHIN $8000-$8FFF. SINCE ALL CODE IMAGES LIKE TO BE LOADED EITHER -; HIGH OR LOW (NEVER IN THE MIDDLE), THE $8000-$8FFF LOCATION TENDS -; TO AVOID THE PROBLEM WHERE THE CODE IS OVERLAID DURING THE LOADING -; OF THE DESIRED EXECUTABLE IMAGE. -; -; INCLUDE GENERIC STUFF -; -#INCLUDE "std.asm" -; -INT_IM1 .EQU $FF00 -; -BID_CUR .EQU -1 ; SPECIAL BANK ID VALUE INDICATES CURRENT BANK -; - .ORG 0 -; -;================================================================================================== -; NORMAL PAGE ZERO SETUP, RET/RETI/RETN AS APPROPRIATE -;================================================================================================== -; - JP $100 ; RST 0: JUMP TO BOOT CODE - .FILL (008H - $),0FFH -#IF (PLATFORM == PLT_UNA) - JP $FFFD ; RST 8: INVOKE UBIOS FUNCTION -#ELSE - JP HB_INVOKE ; RST 8: INVOKE HBIOS FUNCTION -#ENDIF - .FILL (010H - $),0FFH - RET ; RST 10 - .FILL (018H - $),0FFH - RET ; RST 18 - .FILL (020H - $),0FFH - RET ; RST 20 - .FILL (028H - $),0FFH - RET ; RST 28 - .FILL (030H - $),0FFH - RET ; RST 30 - .FILL (038H - $),0FFH -#IF (PLATFORM == PLT_UNA) - RETI ; RETURN W/ INTS DISABLED -#ELSE - #IF (INTMODE == 1) - JP INT_IM1 ; JP TO INTERRUPT HANDLER IN HI MEM - #ELSE - RETI ; RETURN W/ INTS DISABLED - #ENDIF -#ENDIF - .FILL (066H - $),0FFH - RETN ; NMI -; - .FILL (100H - $),0FFH ; PAD REMAINDER OF PAGE ZERO -; -;================================================================================================== -; STARTUP AND LOADER INITIALIZATION -;================================================================================================== -; - DI ; NO INTERRUPTS FOR NOW -; - ; RELOCATE TO START OF COMMON RAM AT $8000 - LD HL,0 - LD DE,$8000 - LD BC,LDR_SIZ - LDIR - JP START +;======================================================================= +; RomWBW Loader +;======================================================================= +; +; The loader code is invoked immediately after HBIOS completes system +; initialization. it is responsible for loading a runnable image +; (operating system, etc.) into memory and transferring control to that +; image. The image may come from ROM (romboot), RAM (appboot/imgboot) +; or from disk (disk boot). +; +; In the case of a ROM boot, the selected executable image is copied +; from ROM into the default RAM and then control is passed to the +; starting address in RAM. In the case of an appboot or imgboot +; startup (see hbios.asm) the source of the image may be RAM. +; +; In the case of a disk boot, sector 2 (the third sector) of the disk +; device will be read -- this is the boot info sector and is expected +; to have the format defined at bl_infosec below. the last three words +; of data in this sector determine the final destination starting and +; ending address for the disk load operation as well as the entry point +; to transfer control to. The actual image to be loaded *must* be on +; the disk in the sectors immediately following the boot info sector. +; This means the image to be loaded must begin in sector 3 (the fourth +; sector) and occupy sectors contiguously after that. +; +; The code below relocates itself at startup to the start of common RAM +; at $8000. This means that the code, data, and stack will all stay +; within $8000-$8FFF. Since all code images like to be loaded either +; high or low (never in the middle), the $8000-$8FFF location tends to +; avoid the problem where the code is overlaid during the loading of +; the desired executable image. +; +#INCLUDE "std.asm" ; standard RomWBW constants +; +#ifndef BOOT_DEFAULT +#define BOOT_DEFAULT "H" +#endif +; +bel .equ 7 ; ASCII bell +bs .equ 8 ; ASCII backspace +lf .equ 10 ; ASCII linefeed +cr .equ 13 ; ASCII carriage return +; +cmdbuf .equ $80 ; cmd buf is in second half of page zero +cmdmax .equ 60 ; max cmd len (arbitrary), must be < bufsiz +bufsiz .equ $80 ; size of cmd buf +; +int_im1 .equ $FF00 ; IM1 vector target for RomWBW HBIOS proxy +; +bid_cur .equ -1 ; used below to indicate current bank +; + .org 0 ; we expect to be loaded at $0000 +; +;======================================================================= +; Normal page zero setup, ret/reti/retn as appropriate +;======================================================================= +; + jp $100 ; rst 0: jump to boot code + .fill ($08 - $) +#if (BIOS == BIOS_WBW) + jp HB_INVOKE ; rst 8: invoke HBIOS function +#else + jp $FFFD ; rst 8: invoke UBIOS function +#endif + .fill ($10 - $) + ret ; rst 10 + .fill ($18 - $) + ret ; rst 18 + .fill ($20 - $) + ret ; rst 20 + .fill ($28 - $) + ret ; rst 28 + .fill ($30 - $) + ret ; rst 30 + .fill ($38 - $) +#if (BIOS == BIOS_WBW) + #if (INTMODE == 1) + jp int_im1 ; go to handler in hi mem + #else + ret ; return w/ ints left disabled + #endif +#else + ret ; return w/ ints disabled +#endif + .fill ($66 - $) + retn ; nmi +; + .fill ($100 - $) ; pad remainder of page zero +; +;======================================================================= +; Startup and loader initialization +;======================================================================= +; +; Note: at startup, we should not assume which bank we are operating in. +; + ; Relocate to start of common ram at $8000 + ld hl,0 + ld de,$8000 + ld bc,LDR_SIZ + ldir +; + jp start ; .ORG $8000 + $ ; -START: LD SP,BL_STACK ; SETUP STACK -; -#IF (PLATFORM != PLT_UNA) - CALL DELAY_INIT ; INIT DELAY FUNCTIONS -#ENDIF -; -#IF (PLATFORM == PLT_UNA) -; ; COPY UNA BIOS PAGE ZERO TO USER BANK, LEAVE USER BANK ACTIVE -; LD BC,$01FB ; UNA FUNC = SET BANK -; LD DE,BID_BIOS ; UBIOS_PAGE (SEE PAGES.INC) -; CALL $FFFD ; DO IT (RST 08 NOT YET INSTALLED) -; PUSH DE ; SAVE PREVIOUS BANK -;; -; LD HL,0 ; FROM ADDRESS 0 (PAGE ZERO) -; LD DE,$9000 ; USE $9000 AS BOUNCE BUFFER -; LD BC,256 ; ONE PAGE IS 256 BYTES -; LDIR ; DO IT -;; -; LD BC,$01FB ; UNA FUNC = SET BANK -; ;POP DE ; RECOVER OPERATING BANK -; LD DE,BID_USR ; TO USER BANK -; CALL $FFFD ; DO IT (RST 08 NOT YET INSTALLED) -;; -; LD HL,$9000 ; USE $9000 AS BOUNCE BUFFER -; LD DE,0 ; TO PAGE ZERO OF OPERATING BANK -; LD BC,256 ; ONE PAGE IS 256 BYTES -; LDIR ; DO IT -;; -;; ; INSTALL UNA INVOCATION VECTOR FOR RST 08 -;; ; *** IS THIS REDUNDANT? *** -;; LD A,$C3 ; JP INSTRUCTION -;; LD (8),A ; STORE AT 0x0008 -;; LD HL,($FFFE) ; UNA ENTRY VECTOR -;; LD (9),HL ; STORE AT 0x0009 -;; -; LD BC,$01FB ; UNA FUNC = SET BANK -; POP DE ; RECOVER OPERATING BANK -; CALL $FFFD ; DO IT (RST 08 NOT YET INSTALLED) -#ELSE - ; PREP THE USER BANK (SETUP PAGE ZERO) - LD B,BF_SYSSETCPY ; HBIOS FUNC: SETUP BANK COPY - LD D,BID_USR ; D = DEST BANK = USER BANK - LD E,BID_BIOS ; E = SRC BANK = BIOS BANK - LD HL,256 ; HL = COPY LEN = 1 PAGE = 256 BYTES - RST 08 ; DO IT - LD B,BF_SYSBNKCPY ; HBIOS FUNC: PERFORM BANK COPY - LD HL,0 ; COPY FROM BIOS ADDRESS 0 - LD DE,0 ; TO USER ADDRESS 0 - RST 08 ; DO IT -#ENDIF - EI -; -;================================================================================================== -; BOOT LOADER MENU DISPLAY -;================================================================================================== -; - LD DE,STR_BANNER ; DISPLAY BOOT BANNER -; -MENU: - CALL WRITESTR ; DISPLAY MESSAGE OR ERROR - CALL NEWLINE2 -; -#IF (DSKYENABLE) - CALL DSKY_RESET - ; DISPLAY DSKY BOOT MESSAGE - LD HL,MSG_SEL ; POINT TO BOOT MESSAGE - CALL DSKY_SHOWSEG ; DISPLAY MESSAGE -#ENDIF -; -#IF (BOOTTYPE == BT_AUTO) - ; INITIALIZE BOOT TIMEOUT DOWNCOUNTER - LD BC,100 * BOOT_TIMEOUT - LD (BL_TIMEOUT),BC -#ENDIF -; - ; DISPLAY ROM MENU ENTRIES - PRTS("ROM: $") - LD B,MENU_N ; B IS LOOP COUNTER, # OF ENTRIES - LD HL,MENU_S ; HL POINTS TO START OF ENTRY -MENU1: - ; PROCESS A TABLE ENTRY - PUSH HL ; COPY HL TO - POP DE ; ... DE FOR USE AS CHAR PTR -MENU2: - LD A,(DE) ; GET NEXT CHAR - INC DE ; BUMP CHAR PTR FOR FUTURE - CP '$' ; TERMINATOR? - JR Z,MENU4 ; IF YES, DONE WITH THIS ENTRY - CP '~' ; HOT KEY PREFIX? - JR NZ,MENU3 ; IF NOT, JUST SKIP AHEAD - CALL PC_LPAREN ; L PAREN BEFORE HOT KEY - LD A,(DE) ; GET THE ACTUAL HOT KEY - INC DE ; BUMP CHAR PTR FOR FUTURE - CALL COUT ; OUTPUT HOT KEY - LD A,')' ; R PAREN WILL PRINT BELOW -MENU3: - CALL COUT ; OUTPUT CHAR - JR MENU2 ; AND LOOP -MENU4: - ; END OF AN ENTRY - CALL PC_SPACE ; PRINT SEPARATOR - LD A,MENU_V ; LOAD ENTRY LENGTH - CALL ADDHLA ; BUMP HL TO NEXT ENTRY - DJNZ MENU1 ; LOOP UNTIL COUNT EXPIRES -; - ; DISPLAY AVAILABLE DISK DRIVES - PRTS("\r\nDisk: $") - CALL PRTALL ; PRINT DRIVE LIST -; - LD DE,STR_BOOTSEL - CALL WRITESTR -; -;================================================================================================== -; BOOT SELECTION PROCESSING -;================================================================================================== -; -SEL: - ; HANDLE SERIAL CONSOLE INPUT - CALL CST ; CHECK CONSOLE INPUT - OR A ; ZERO? - JR Z,SEL1 ; IF NOT, CONTINUE - CALL CINUC ; GET THE KEY - CALL COUT ; ECHO KEY - JR MATS ; AND HANDLE IT -; -SEL1: -#IF (DSKYENABLE) - ; HANDLE DSKY KEY INPUT - CALL DSKY_STAT ; CHECK DSKY INPUT - OR A ; TEST FOR ZERO - JR Z,SEL2 ; IF ZERO, NO KEY PRESSED - CALL DSKY_GETKEY ; GET PENDING KEY PRESS - JR MATK ; AND HANDLE IT -#ENDIF -; -SEL2: -#IF (BOOTTYPE == BT_AUTO) - ; CHECK FOR AUTOBOOT TIMEOUT - LD DE,625 ; DELAY FOR 10MS TO MAKE TIMEOUT CALC EASY - CALL VDELAY ; 16US * 625 = 10MS - LD BC,(BL_TIMEOUT) ; CHECK/INCREMENT TIMEOUT - DEC BC - LD (BL_TIMEOUT),BC - LD A,B - OR C - JP NZ,SEL3 -; - LD A,BOOT_DEFAULT ; TIMEOUT EXPIRED, - JR MATS ; PERFORM DEFAULT BOOT ACTION -#ENDIF -; -SEL3: - ; NO USER SELECTION YET - JR SEL ; LOOP -; -;================================================================================================== -; ROM MENU TABLE MATCHING -;================================================================================================== -; -MATS: ; MATCH SERIAL INPUT TO MENU - CP 'R' - JP Z,REBOOT - LD B,MENU_N - LD DE,MENU_S+10-MENU_V - LD HL,MENU_V -MATS1: EX DE,HL - ADD HL,DE - CP (HL) - EX DE,HL - JR Z,MATS2 - DJNZ MATS1 ; FALL THRU IF IT DOES NOT MATCH ROM MENU - SUB '0' ; CONVERT ASCII TO BINARY - JR MATD ; AND TRY DISK BOOT -MATS2: INC DE ; SKIP MENU SEL CHAR - INC DE ; SKIP DSKY SEL CHAR - JP GOROM ; BOOT FROM ROM -; -#IF (DSKYENABLE) -MATK: ; MATCH DSKY INPUT TO MENU - CP KY_BO - JP Z,REBOOT - LD B,MENU_N - LD DE,MENU_S+11-MENU_V - LD HL,MENU_V -MATK1: EX DE,HL - ADD HL,DE - CP (HL) - EX DE,HL - JR Z,MATK2 - DJNZ MATK1 ; FALL THRU IF IT DOES NOT MATCH DSKY MENU - JR MATD ; TRY DISK BOOT -MATK2: INC DE ; SKIP DSKY SEL CHAR - JP GOROM ; BOOT FROM ROM -#ENDIF -; -MATD: ; CHECK FOR DISK BOOT REQUEST - CP 10 ; 0-9, DISK DEVICE - JR NC,MATX ; NOT VALID, HANDLE IT BELOW - JP GOBOOTDISK ; BOOT FROM DISK -; -MATX: - ; SET ERROR STRING AND LOOP - LD DE,STR_INVALID ; INVALID SEL MSG - JP MENU ; RESTART MENU LOOP -; -;================================================================================================== -; ROM MENU TABLE -;================================================================================================== -; -#DEFINE MENU_L(M1,M2,M3,M4,M5,M6,M7,M8,M9,M10) \ -#DEFCONT \ .DB M1 -#DEFCONT \ .DB M2 -#IF (DSKYENABLE) -#DEFCONT \ .DB M3 -#ELSE -#DEFCONT \ .DB $FF -#ENDIF -#DEFCONT \ .DW M4 -#DEFCONT \ .DW M5 -#DEFCONT \ .DW M6 -#DEFCONT \ .DW M7 -#DEFCONT \ .DB M8 -#DEFCONT \ .DB M9 -#DEFCONT \ .DB M10 -; -; NOTE: THE FORMATTING OF THE FOLLOWING IS CRITICAL. TASM DOES NOT PASS MACRO ARGUMENTS WELL. -; ENSURE STD.ASM HOLDS THE DEFINITIONS FOR *_LOC, *_SIZ *_END AND ANY CODE GENERATED WHICH DOES NOT -; INCLUDE STD.ASM IS SYNCED. -; -; NOTE: THE LOADABLE ROM IMAGES ARE PLACED IN ROM BANKS BID_IMG0 AND BID_IMG1. HOWEVER, ROMWBW -; SUPPORTS A MECHANISM TO LOAD A COMPLETE NEW SYSTEM DYNAMICALLY AS A RUNNABLE APPLICATION -; (SEE APPBOOT AND IMGBOOT IN HBIOS.ASM). IN THIS CASE, THE CONTENTS OF BID_IMG0 WILL -; PRE-LOADED INTO THE CURRENTLY EXECUTING RAM BANK THEREBY ALLOWING THOSE IMAGES TO BE -; DYNAMICALLY LOADED AS WELL. TO SUPPORT THIS CONCEPT, A PSEUDO-BANK CALLED BID_CUR -; IS USED TO SPECIFY THE IMAGES NORMALLY FOUND IN BID_IMG0. IN GOROM, THIS SPECIAL -; VALUE WILL CAUSE THE ASSOCIATED IMAGE TO BE LOADED FROM THE CURRENTLY EXECUTING BANK -; WHICH WILL BE CORRECT REGARDLESS OF THE LOAD MODE. IMAGES IN OTHER BANKS (BID_IMG1) -; WILL ALWAYS BE LOADED DIRECTLY FROM ROM. -; -; name menu dsky dest-exec source dest-addr img-size source-bank dest desc -; DB DB DB DW DW DW DW DB DB DB -MENU_S: MENU_L("~Monitor$ ", "M", KY_CL, MON_SERIAL, 1000h, MON_LOC, MON_SIZ, BID_CUR, BID_USR, "Monitor$ ") -MENU_1: MENU_L("~CP/M$ ", "C", KY_BK, CPM_ENT, 2000h, CPM_LOC, CPM_SIZ, BID_CUR, BID_USR, "CP/M 80 v2.2$") - MENU_L("~Z-System$", "Z", KY_FW, CPM_ENT, 5000h, CPM_LOC, CPM_SIZ, BID_CUR, BID_USR, "ZSDOS v1.1$ ") -#IF (PLATFORM != PLT_UNA) - MENU_L("~Forth$ ", "F", KY_EX, FTH_LOC, 0000h, FTH_LOC, FTH_SIZ, BID_IMG1, BID_USR, "Camel Forth$ ") - MENU_L("~BASIC$ ", "B", KY_DE, BAS_LOC, 1700h, BAS_LOC, BAS_SIZ, BID_IMG1, BID_USR, "Nascom BASIC$") - MENU_L("~T-BASIC$ ", "T", KY_EN, TBC_LOC, 3700h, TBC_LOC, TBC_SIZ, BID_IMG1, BID_USR, "Tasty BASIC$ ") -#ENDIF -#IF (DSKYENABLE) - MENU_L("~DSKY$ ", "D", KY_GO, MON_DSKY, 1000h, MON_LOC, MON_SIZ, BID_CUR, BID_USR, "DSKY Monitor$") -#ENDIF - MENU_L("$ ", "E", $FF, EGG_LOC, 0E00h, EGG_LOC, EGG_SIZ, BID_CUR, BID_USR, "Easter Egg$ ") -; -MENU_E .EQU $ ; END OF TABLE -MENU_V .EQU MENU_1 - MENU_S ; LENGTH OF EACH MENU RECORD -MENU_N .EQU ((MENU_E - MENU_S) / MENU_V) ; NUMBER OF MENU ITEMS -; -;================================================================================================== -; SYSTEM REBOOT HANDLER -;================================================================================================== -; -REBOOT: LD DE,STR_REBOOT ; POINT TO MESSAGE - CALL WRITESTR ; PRINT IT -#IF (DSKYENABLE) - LD HL,MSG_BOOT ; POINT TO BOOT MESSAGE - CALL DSKY_SHOWSEG ; DISPLAY MESSAGE -#ENDIF - LD A,BID_BOOT ; BOOT BANK - LD HL,0 ; ADDRESS ZERO - CALL HB_BNKCALL ; DOES NOT RETURN -; -;================================================================================================== -; ROM IMAGE LOAD HANDLER -;================================================================================================== -; -; AT ENTRY, DE POINTS TO THE EXEC ADR FIELD OF THE ACTIVE ROM -; TABLE ENTRY -; -; ROM IMAGES MUST NOT OVERLAY THE SPACE OCCUPIED BY THE LOADER WHICH -; IS $8000-$8FFF. -; -GOROM: PUSH DE ; SAVE ROM TABLE ENTRY EXEC ADR PTR - LD DE,STR_BOOTROM ; ROM LOADING MSG PREFIX - CALL WRITESTR ; PRINT IT -#IF (DSKYENABLE) - LD HL,MSG_LOAD ; POINT TO LOAD MESSAGE - CALL DSKY_SHOWSEG ; DISPLAY MESSAGE -#ENDIF - POP HL ; EXEC ADR TO HL - PUSH HL ; AND RESAVE IT - LD A,10 ; OFFSET TO IMAGE DESC - CALL ADDHLA ; APPLY IT - EX DE,HL ; MOVE TO DE, ORIG VALUE TO HL - CALL WRITESTR ; AND PRINT IT - PRTS("...$") ; ADD SOME DOTS - POP HL ; RESTORE EXEC ADR TO HL -; - LD B,5 ; PUT NEXT FIVE WORDS ON STACK -GOROM1: LD E,(HL) ; (1) EXEC ADR - INC HL ; (2) SOURCE ADR - LD D,(HL) ; (3) DEST ADR - INC HL ; (4) IMAGE SIZE - PUSH DE ; (5) SRC/DEST BANKS - DJNZ GOROM1 ; LOOP TILL DONE -; -#IF (PLATFORM == PLT_UNA) -; -; NOTE: UNA HAS NO INTERBANK MEMORY COPY, SO WE CAN ONLY LOAD -; IMAGES FROM THE CURRENT BANK. A SIMPLE LDIR IS USED TO -; RELOCATE THE IMAGES. AT SOME POINT AN UNA INTERBANK COPY -; SHOULD BE IMPLEMENTED HERE. -; - ; COPY IMAGE TO IT'S RUNNING LOCATION - POP HL ; POP AND DISCARD BANKS - POP BC ; GET IMAGE SIZE TO BC - POP DE ; GET DESTINATION ADR TO DE - POP HL ; GET SOURCE ADR TO HL - LDIR ; MOVE IT -; - ; RECORD BOOT INFO - LD BC,$00FB ; GET LOWER PAGE ID - RST 08 ; DE := LOWER PAGE ID == BOOT ROM PAGE - LD L,1 ; BOOT DISK UNIT IS ROM (UNIT ID = 1) - LD BC,$01FC ; UNA FUNC: SET BOOTSTRAP HISTORY - RST 08 ; CALL UNA -; - ; LAUNCH IMAGE W/ USER BANK ACTIVE - ; NOTE: UNA EXEC CHAIN CALL USES ADDRESS ON TOS - CALL NEWLINE2 - LD DE,BID_USR ; TARGET BANK ID - PUSH DE ; ... ON STACK - ;DI ; ENTER WITH INTS DISABLED - JP $FFF7 ; UNA INTER-PAGE EXEC CHAIN -#ELSE -; -; NOTE: CHECK FOR SPECIAL CASE WHERE SOURCE BANK IS BID_CUR. IN THIS CASE -; WE COPY THE IMAGE FROM THE BANK THAT WE ARE CURRENTLY RUNNING IN. THIS -; IS DONE TO SUPPORT THE APPBOOT AND IMGBOOT MODES AS DEFINED IN HBIOS. -; IN THE CASE OF THESE MODES IT IS INTENDED THAT THE IMAGES BE LOADED -; FROM THE CURRENT RAM BANK AND NOT FROM THEIR NORMAL ROM LOCATIONS. -; - ; COPY IMAGE TO IT'S RUNNING LOCATION - POP DE ; GET BANKS (E=SRC, D=DEST) - POP HL ; GET IMAGE SIZE - LD A,E ; SOURCE BANK TO A - CP BID_CUR ; SPECIAL CASE, BID_CUR? - JR NZ,GOROM2 ; IF NOT, GO RIGHT TO COPY - LD A,(HB_CURBNK) ; GET CURRENT BANK - LD E,A ; AND SUBSTITUE THE VALUE -GOROM2: LD B,BF_SYSSETCPY ; HBIOS FUNC: SETUP BANK COPY - RST 08 ; DO IT - POP DE ; GET DEST ADR - POP HL ; GER SOURCE ADR - LD B,BF_SYSBNKCPY ; HBIOS FUNC: PERFORM BANK COPY - RST 08 ; DO IT -; - ; RECORD BOOT INFO - LD A,(HB_CURBNK) ; GET CURRENT BANK ID FROM PROXY DATA - LD B,BF_SYSSET ; HB FUNC: SET HBIOS PARAMETER - LD C,BF_SYSSET_BOOTINFO ; HB SUBFUNC: SET BOOT INFO - LD L,A ; ... AND SAVE AS BOOT BANK - LD DE,$0100 ; BOOT VOLUME (UNIT, SLICE) - RST 08 -; -#IF (DSKYENABLE) - LD HL,MSG_GO ; POINT TO BOOT MESSAGE - CALL DSKY_SHOWSEG ; DISPLAY MESSAGE -#ENDIF -; - ; LAUNCH IMAGE W/ USER BANK ACTIVE - CALL NEWLINE2 - LD A,BID_USR ; ACTIVATE USER BANK - POP HL ; RECOVER EXEC ADDRESS - ;DI ; ENTER WITH INTS DISABLED - CALL HB_BNKCALL ; AND GO - HALT ; WE SHOULD NEVER RETURN!!! -#ENDIF -; -;================================================================================================== -; DISK BOOT HANDLER -;================================================================================================== -; -GOBOOTDISK: - LD (BL_BOOTID),A - LD DE,STR_BOOTDISK - CALL WRITESTR - CALL PRTHEXBYTE - PRTS("...$") -#IF (DSKYENABLE) - LD HL,MSG_LOAD ; POINT TO LOAD MESSAGE - CALL DSKY_SHOWSEG ; DISPLAY MESSAGE -#ENDIF -; - LD DE,STR_BOOTDISK1 ; DISK BOOT MESSAGE - CALL WRITESTR ; PRINT IT -; -#IF (PLATFORM == PLT_UNA) - LD A,(BL_BOOTID) ; GET BOOT DEVICE ID - LD B,A ; MOVE TO B -; - ; LOAD SECTOR 2 (BOOT INFO) - LD C,$41 ; UNA FUNC: SET LBA - LD DE,0 ; HI WORD OF LBA IS ALWAYS ZERO - LD HL,2 ; LOAD STARTING INFO SECTOR 2 - RST 08 ; SET LBA - JP NZ,DB_ERR ; HANDLE ERROR -; - LD C,$42 ; UNA FUNC: READ SECTORS - LD DE,BL_INFOSEC ; DEST OF CPM IMAGE - LD L,1 ; SECTORS TO READ - RST 08 ; DO READ - JP NZ,DB_ERR ; HANDLE ERROR -#ELSE - ; CHECK FOR VALID DRIVE LETTER - LD A,(BL_BOOTID) ; BOOT DEVICE TO A - PUSH AF ; SAVE BOOT DEVICE - LD B,BF_SYSGET - LD C,BF_SYSGET_DIOCNT - RST 08 ; E := DISK UNIT COUNT - POP AF ; RESTORE BOOT DEVICE - CP E ; CHECK MAX (INDEX - COUNT) - JP NC,DB_NODISK ; HANDLE INVALID SELECTION -; - ; SET THE BOOT UNIT AND SLICE - LD A,(BL_BOOTID) ; GET BOOTID - LD (BL_DEVICE),A ; STORE IT - XOR A ; LU ALWAYS ZERO - LD (BL_LU),A ; STORE IT -; - ; SENSE MEDIA - LD A,(BL_DEVICE) ; GET DEVICE/UNIT - LD C,A ; STORE IN C - LD B,BF_DIOMEDIA ; DRIVER FUNCTION = DISK MEDIA - LD E,1 ; ENABLE MEDIA CHECK/DISCOVERY - RST 08 ; CALL HBIOS - JP NZ,DB_ERR ; HANDLE ERROR -; - ; SEEK TO SECTOR 2 OF LU - LD A,(BL_LU) ; GET LU SPECIFIED - LD E,A ; LU INDEX - LD H,65 ; 65 TRACKS PER LU - CALL MULT8 ; HL := H * E - LD DE,$02 ; HEAD 0, SECTOR 2 - LD B,BF_DIOSEEK ; SETUP FOR NEW SEEK CALL - LD A,(BL_DEVICE) ; GET BOOT DISK UNIT - LD C,A ; PUT IN C - RST 08 ; DO IT - JP NZ,DB_ERR ; HANDLE ERROR -; - ; READ - LD B,BF_DIOREAD ; FUNCTION IN B - LD A,(BL_DEVICE) ; GET BOOT DISK UNIT - LD C,A ; PUT IN C - LD HL,BL_INFOSEC ; READ INTO INFO SEC BUFFER - LD DE,1 ; TRANSFER ONE SECTOR - RST 08 ; DO IT - JP NZ,DB_ERR ; HANDLE ERROR -; -#ENDIF -; - ; CHECK SIGNATURE - LD DE,(BB_SIG) ; GET THE SIGNATURE - LD A,$A5 ; FIRST BYTE SHOULD BE $A5 - CP D ; COMPARE - JP NZ,DB_NOBOOT ; ERROR IF NOT EQUAL - LD A,$5A ; SECOND BYTE SHOULD BE $5A - CP E ; COMPARE - JP NZ,DB_NOBOOT ; ERROR IS NOT EQUAL -; - ; PRINT CPMLOC VALUE - PRTS("\r\nLoc=$") - LD BC,(BB_CPMLOC) - CALL PRTHEXWORD -; - ; PRINT CPMEND VALUE - PRTS(" End=$") - LD BC,(BB_CPMEND) - CALL PRTHEXWORD -; - ; PRINT CPMENT VALUE - PRTS(" Ent=$") - LD BC,(BB_CPMENT) - CALL PRTHEXWORD -; - ; PRINT DISK LABEL - PRTS(" Label=$") - LD DE,BB_LABEL ; if it is there, then a printable - LD A,(BB_TERM) ; Display Disk Label if Present - CP '$' ; (dwg 2/7/2012) - CALL Z,WRITESTR ; label is there as well even if spaces. -; - LD DE,STR_LOADING ; LOADING MESSAGE - CALL WRITESTR ; PRINT IT -; - ; COMPUTE NUMBER OF SECTORS TO LOAD - LD HL,(BB_CPMEND) ; HL := END - LD DE,(BB_CPMLOC) ; DE := START - OR A ; CLEAR CARRY - SBC HL,DE ; HL := LENGTH TO LOAD - LD A,H ; DETERMINE 512 BYTE SECTOR COUNT - RRCA ; ... BY DIVIDING MSB BY TWO - LD (BL_COUNT),A ; ... AND SAVE IT -; -#IF (PLATFORM == PLT_UNA) -; - ; READ OS IMAGE INTO MEMORY - LD C,$42 ; UNA FUNC: READ SECTORS - LD A,(BL_BOOTID) ; GET BOOT DEVICE ID - LD B,A ; MOVE TO B - LD DE,(BB_CPMLOC) ; DEST OF CPM IMAGE - LD A,(BL_COUNT) ; GET SECTORS TO READ - LD L,A ; SECTORS TO READ - RST 08 ; DO READ - JP NZ,DB_ERR ; HANDLE ERROR -; - ; PASS BOOT DEVICE/UNIT/LU TO CBIOS COLD BOOT - LD DE,-1 ; BOOT ROM PAGE, -1 FOR N/A - LD A,(BL_BOOTID) ; GET BOOT DISK UNIT ID - LD L,A ; PUT IN L - LD BC,$01FC ; UNA FUNC: SET BOOTSTRAP HISTORY - RST 08 ; CALL UNA - JP NZ,DB_ERR ; HANDLE ERROR -; - ; JUMP TO COLD BOOT ENTRY - LD HL,(BB_CPMENT) ; GET THE ENTRY POINT - PUSH HL ; PUT ON STACK FOR UNA CHAIN FUNC - LD DE,BID_USR ; TARGET BANK ID IS USER BANK - PUSH DE ; PUT ON STACK FOR UNA CHAIN FUNC - ;DI ; ENTER WITH INTS DISABLED - JP $FFF7 ; UNA INTER-PAGE EXEC CHAIN -; -#ELSE -; - ; READ OS IMAGE INTO MEMORY - LD B,BF_DIOREAD ; FUNCTION IN B - LD A,(BL_DEVICE) ; GET BOOT DISK UNIT - LD C,A ; PUT IN C - LD HL,(BB_CPMLOC) ; LOAD ADDRESS - LD D,0 - LD A,(BL_COUNT) ; GET SECTORS TO READ - LD E,A ; NUMBER OF SECTORS TO LOAD - RST 08 - JP NZ,DB_ERR ; HANDLE ERRORS -; - ; PASS BOOT DEVICE/UNIT/LU TO CBIOS COLD BOOT - LD B,BF_SYSSET ; HB FUNC: SET HBIOS PARAMETER - LD C,BF_SYSSET_BOOTINFO ; HB SUBFUNC: SET BOOT INFO - LD A,(HB_CURBNK) ; GET CURRENT BANK ID FROM PROXY DATA - LD L,A ; ... AND SAVE AS BOOT BANK - LD A,(BL_DEVICE) ; LOAD BOOT DEVICE/UNIT - LD D,A ; SAVE IN D - LD A,(BL_LU) ; LOAD BOOT LU - LD E,A ; SAVE IN E - RST 08 - JP NZ,DB_ERR ; HANDLE ERRORS -; -#IF (DSKYENABLE) - LD HL,MSG_GO ; POINT TO BOOT MESSAGE - CALL DSKY_SHOWSEG ; DISPLAY MESSAGE -#ENDIF -; - ; JUMP TO COLD BOOT ENTRY - LD A,BID_USR ; ACTIVATE USER BANK - LD HL,(BB_CPMENT) ; OS ENTRY ADDRESS - ;DI ; ENTER WITH INTS DISABLED - CALL HB_BNKCALL ; AND GO - HALT ; WE SHOULD NEVER RETURN!!! -; -#ENDIF -; -DB_NODISK: - ; SELDSK DID NOT LIKE DRIVE SELECTION - LD DE,STR_NODISK - JP MENU -; -DB_NOBOOT: - ; DISK IS NOT BOOTABLE - LD DE,STR_NOBOOT - JP MENU -; -DB_ERR: - ; I/O ERROR DURING BOOT ATTEMPT - LD DE,STR_BOOTERR - JP MENU -; -#IF (PLATFORM == PLT_UNA) -; -; PRINT LIST OF ALL DRIVES UNDER UNA -; -PRTALL: - LD B,0 ; START WITH UNIT 0 -; -PRTALL1: ; LOOP THRU ALL UNITS AVAILABLE - LD C,$48 ; UNA FUNC: GET DISK TYPE - LD L,0 ; PRESET UNIT COUNT TO ZERO - RST 08 ; CALL UNA, B IS ASSUMED TO BE UNTOUCHED!!! - LD A,L ; UNIT COUNT TO A - OR A ; PAST END? - RET Z ; WE ARE DONE - PUSH BC ; SAVE UNIT - CALL PRTDRV ; PROCESS THE UNIT - POP BC ; RESTORE UNIT - INC B ; NEXT UNIT - JR PRTALL1 ; LOOP -; -; PRINT THE UNA UNIT INFO -; ON INPUT B HAS UNIT -; -PRTDRV: - PUSH BC ; SAVE UNIT - PUSH DE ; SAVE DISK TYPE - LD A,'(' ; NEWLINE AND SPACING - CALL COUT ; PRINT IT - LD A,B ; DRIVE LETTER TO A - ADD A,'0' ; MAKE IT DISPLAY NUMERIC - CALL COUT ; PRINT IT - LD A,')' ; DRIVE LETTER COLON - CALL COUT ; PRINT IT - POP DE ; RECOVER DISK TYPE - LD A,D ; DISK TYPE TO A - CP $40 ; RAM/ROM? - JR Z,PRTDRV1 ; HANDLE RAM/ROM - LD DE,DEVIDE ; ASSUME IDE - CP $41 ; IDE? - JR Z,PRTDRV2 ; PRINT IT - LD DE,DEVPPIDE ; ASSUME PPIDE - CP $42 ; PPIDE? - JR Z,PRTDRV2 ; PRINT IT - LD DE,DEVSD ; ASSUME SD - CP $43 ; SD? - JR Z,PRTDRV2 ; PRINT IT - LD DE,DEVDSD ; ASSUME DSD - CP $44 ; DSD? - JR Z,PRTDRV2 ; PRINT IT - LD DE,DEVUNK ; OTHERWISE UNKNOWN - JR PRTDRV2 -; -PRTDRV1: ; HANDLE RAM/ROM - LD C,$45 ; UNA FUNC: GET DISK INFO - LD DE,BL_INFOSEC ; 512 BYTE BUFFER - RST 08 ; CALL UNA - BIT 7,B ; TEST RAM DRIVE BIT - LD DE,DEVROM ; ASSUME ROM - JR Z,PRTDRV2 ; IF SO, PRINT IT - LD DE,DEVRAM ; OTHERWISE RAM - JR PRTDRV2 ; PRINT IT -; -PRTDRV2: ; PRINT DEVICE - POP BC ; RECOVER UNIT - CALL WRITESTR ; PRINT DEVICE NAME - LD A,B ; UNIT TO A - ADD A,'0' ; MAKE IT PRINTABLE NUMERIC - CALL COUT ; PRINT IT - LD A,',' ; DEVICE NAME SEPARATOR - CALL COUT ; PRINT IT - RET ; DONE -; -DEVRAM .DB "RAM$" -DEVROM .DB "ROM$" -DEVIDE .DB "IDE$" -DEVPPIDE .DB "PPIDE$" -DEVSD .DB "SD$" -DEVDSD .DB "DSD$" -DEVUNK .DB "UNK$" -; -#ELSE -; -; PRINT LIST OF ALL DRIVES -; -PRTALL: -; - LD B,BF_SYSGET - LD C,BF_SYSGET_DIOCNT - RST 08 ; E := DISK UNIT COUNT - LD B,E ; COUNT TO B - LD A,B ; COUNT TO A - OR A ; SET FLAGS - RET Z ; BAIL OUT IF ZERO - LD C,0 ; INIT DEVICE INDEX -; -PRTALL1: - LD A,'(' ; FORMATTING - CALL COUT ; PRINT IT - LD A,C ; INDEX TO A - ADD A,'0' ; MAKE NUMERIC CHAR - CALL COUT ; PRINT IT - LD A,')' ; FORMATTING - CALL COUT ; PRINT IT - PUSH BC ; SAVE LOOP CONTROL - LD B,BF_DIODEVICE ; HBIOS FUNC: REPORT DEVICE INFO - RST 08 ; CALL HBIOS - CALL PRTDRV ; PRINT IT - POP BC ; RESTORE LOOP CONTROL - INC C ; BUMP INDEX - DJNZ PRTALL1 ; LOOP AS NEEDED - RET ; DONE -; -; PRINT THE DRIVER DEVICE/UNIT INFO -; ON INPUT D HAS DRIVER ID, E HAS DRIVER MODE/UNIT -; DESTROY NO REGISTERS OTHER THAN A -; -PRTDRV: - PUSH DE ; PRESERVE DE - PUSH HL ; PRESERVE HL - LD A,D ; LOAD DEVICE/UNIT - RRCA ; ROTATE DEVICE - RRCA ; ... BITS - RRCA ; ... INTO - RRCA ; ... LOWEST 4 BITS - AND $0F ; ISOLATE DEVICE BITS - ADD A,A ; MULTIPLE BY TWO FOR WORD TABLE - LD HL,DEVTBL ; POINT TO START OF DEVICE NAME TABLE - CALL ADDHLA ; ADD A TO HL TO POINT TO TABLE ENTRY - LD A,(HL) ; DEREFERENCE HL TO LOC OF DEVICE NAME STRING - INC HL ; ... - LD D,(HL) ; ... - LD E,A ; ... - CALL WRITESTR ; PRINT THE DEVICE NMEMONIC - POP HL ; RECOVER HL - POP DE ; RECOVER DE - LD A,E ; LOAD DRIVER MODE/UNIT - AND $0F ; ISOLATE UNIT - CALL PRTDECB ; PRINT IT - CALL PC_SPACE ; FORMATTING - ;LD A,E ; LOAD LU - ;CALL PRTDECB ; PRINT IT - RET -; -DEVTBL: ; DEVICE TABLE - .DW DEV00, DEV01, DEV02, DEV03 - .DW DEV04, DEV05, DEV06, DEV07 - .DW DEV08, DEV09, DEV10, DEV11 - .DW DEV12, DEV13, DEV14, DEV15 -; -DEVUNK .DB "???$" -DEV00 .DB "MD$" -DEV01 .DB "FD$" -DEV02 .DB "RAMF$" -DEV03 .DB "IDE$" -DEV04 .DB "ATAPI$" -DEV05 .DB "PPIDE$" -DEV06 .DB "SD$" -DEV07 .DB "PRPSD$" -DEV08 .DB "PPPSD$" -DEV09 .DB "HDSK$" -DEV10 .EQU DEVUNK -DEV11 .EQU DEVUNK -DEV12 .EQU DEVUNK -DEV13 .EQU DEVUNK -DEV14 .EQU DEVUNK -DEV15 .EQU DEVUNK -; -#ENDIF -; -;================================================================================================== -; STRINGS -;================================================================================================== -; -STR_BANNER .DB "\r\n\r\n", PLATFORM_NAME, " Boot Loader$" -STR_BOOTSEL .DB "\r\n\r\nBoot Selection? $" -STR_BOOTDISK .DB "\r\n\r\nBooting Disk Unit $" -STR_BOOTROM .DB "\r\n\r\nLoading $" -STR_REBOOT .DB "\r\n\r\nRestarting System...$" -STR_INVALID .DB "\r\n\r\n*** Invalid Selection ***$" -STR_NODISK .DB "\r\n\r\nNo disk!$" -STR_NOBOOT .DB "\r\n\r\nDisk not bootable!$" -STR_BOOTERR .DB "\r\n\r\nBoot failure!$" -STR_BOOTDISK1 .DB "\r\n\r\nReading disk information...$" -STR_LOADING .DB "\r\n\r\nLoading...$" -; -#IF (DSKYENABLE) -MSG_SEL .DB $FF,$9D,$9D,$8F,$EC,$80,$80,$80 ; "Boot? " -MSG_BOOT .DB $FF,$9D,$9D,$8F,$00,$00,$00,$80 ; "Boot... " -MSG_LOAD .DB $8B,$9D,$FD,$BD,$00,$00,$00,$80 ; "Load... " -MSG_GO .DB $DB,$9D,$00,$00,$00,$80,$80,$80 ; "Go... " -#ENDIF +start: + ld sp,bl_stack ; setup private stack + call DELAY_INIT ; init delay functions +; +; Disable interrupts if IM1 is active because we are switching to page +; zero in user bank and it has not been prepared with IM1 vector yet. +; +#if (INTMODE == 1) + di +#endif +; +; Switch to user RAM bank +; +#if (BIOS == BIOS_WBW) + ld b,BF_SYSSETBNK ; HBIOS func: set bank + ld c,BID_USR ; select user bank + rst 08 ; do it + ld a,c ; previous bank to A + ld (bid_ldr),a ; save previous bank for later + cp BID_IMG0 ; starting from ROM? +#endif +; +#if (BIOS == BIOS_UNA) + ld bc,$01FB ; UNA func: set bank + ld de,BID_USR ; select user bank + rst 08 ; do it + ld (bid_ldr),de ; ... for later + ld a,d ; starting from ROM? + or e ; ... bank == 0? +#endif +; + ; For app mode startup, use alternate table + ld hl,ra_tbl ; assume ROM startup + jr z,start1 ; if so, ra_tbl OK, skip ahead + ld hl,ra_tbl_app ; not ROM boot, get app tbl loc +start1: + ld (ra_tbl_loc),hl ; and overlay pointer +; +; Copy original page zero into user page zero +; + ld hl,$8000 ; page zero was copied here + ld de,0 ; put it in user page zero + ld bc,$100 ; full page + ldir ; do it +; +; Page zero in user bank is ready for interrupts now. +; +#if (INTMODE == 1) + ei +#endif +; +;======================================================================= +; Loader prompt +;======================================================================= +; + call nl2 ; formatting + ld hl,str_banner ; display boot banner + call pstr ; do it + call clrbuf ; zero fill the cmd buffer +; +#if (BOOT_TIMEOUT > 0) + ; Initialize auto command timeout downcounter + or $FF ; auto cmd active value + ld (acmd_act),a ; set flag + ld bc,BOOT_TIMEOUT * 100 ; hundredths of seconds + ld (acmd_to),bc ; save auto cmd timeout + ;ld a,b ; check for + ;or c ; ... zero + ;jr nz,prompt ; not zero, prompt w/ timeout + ;call nl2 ; formatting + ;ld hl,str_boot ; command string prefix + ;call pstr ; show it + ;call autocmd ; else, handle w/o prompt + ;jr reprompt ; restart w/ autocmd disable +#endif +; +prompt: + ld hl,reprompt ; adr of prompt restart routine + push hl ; put it on stack + call nl2 ; formatting + ld hl,str_prompt ; display boot prompt + call pstr ; do it + call clrbuf ; zero fill the cmd buffer +; +#if (DSKYENABLE) + call DSKY_RESET ; clear DSKY + ld hl,msg_sel ; boot select msg + call DSKY_SHOWSEG ; show on DSKY +#endif +; +wtkey: + ; wait for a key or timeout + call cst ; check for keyboard key + jr nz,concmd ; if pending, do console command +; +#if (DSKYENABLE) + call DSKY_STAT ; check DSKY for keypress + or a ; set flags + jp nz,dskycmd ; if pending, do DSKY command +#endif +; +#if (BOOT_TIMEOUT > 0) + ; check for timeout and handle auto boot here + ld a,(acmd_act) ; get auto cmd active flag + or a ; set flags + jr z,wtkey ; if not active, just loop + ld bc,(acmd_to) ; load timeout value + ld a,b ; test for + or c ; ... zero + jr z,autocmd ; if so, handle it + dec bc ; decrement + ld (acmd_to),bc ; resave it + ld de,625 ; 16us * 625 = 10ms + call VDELAY ; 10ms delay +#endif +; + jr wtkey ; loop +; +reprompt: + xor a ; zero accum + ld (acmd_act),a ; set auto cmd inactive + jr prompt ; back to loader prompt +; +clrbuf: + ld hl,cmdbuf + ld b,bufsiz + xor a +clrbuf1: + ld (hl),a + djnz clrbuf1 + ret +; +;======================================================================= +; Process a command line from buffer +;======================================================================= +; +concmd: + call clrled ; clear LEDs +; + ; Get a command line from console and handle it + call rdln ; get a line from the user + ld de,cmdbuf ; point to buffer + call skipws ; skip whitespace + or a ; set flags to check for null + jr nz,runcmd ; got a cmd, process it + ; if no cmd entered, fall thru to process default cmd +; +autocmd: + ; Copy autocmd string to buffer and process it + ld hl,acmd ; auto cmd string + call pstr ; display it + ld hl,acmd ; auto cmd string + ld de,cmdbuf ; cmd buffer adr + ld bc,acmd_len ; auto cmd length + ldir ; copy to command line buffer +; +runcmd: + ; Process command line +; + ld de,cmdbuf ; point to start of buf + call skipws ; skip whitespace + or a ; check for null terminator + ret z ; if empty line, just bail out + ld a,(de) ; get character + call upcase ; make upper case +; + ; Attempt built-in commands + cp 'H' ; H = display help + jp z,help ; if so, do it + cp '?' ; '?' alias for help + jp z,help ; if so, do it + cp 'L' ; L = List ROM applications + jp z,applst ; if so, do it + cp 'D' ; D = device inventory + jp z,devlst ; if so, do it + cp 'R' ; R = reboot system + jp z,reboot ; if so, do it +; + ; Attempt ROM application launch + ld ix,(ra_tbl_loc) ; point to start of ROM app tbl + ld c,a ; save command in C +runcmd1: + ld a,(ix+ra_conkey) ; get match char + and ~$80 ; clear "hidden entry" bit + cp c ; compare + jp z,romload ; if match, load it + ld de,ra_entsiz ; table entry size + add ix,de ; bump IX to next entry + ld a,(ix) ; check for end + or (ix+1) ; ... of table + jr nz,runcmd1 ; loop till done +; + ; Attempt disk boot + ld de,cmdbuf ; start of buffer + call skipws ; skip whitespace + call isnum ; do we have a number? + jp nz,err_invcmd ; invalid format if empty + call getnum ; parse a number + jp c,err_invcmd ; handle overflow error + ld (bootunit),a ; save boot unit + xor a ; zero accum + ld (bootslice),a ; save default slice + call skipws ; skip possible whitespace + ld a,(de) ; get separator char + or a ; test for terminator + jp z,diskboot ; if so, boot the disk unit + cp '.' ; otherwise, is '.'? + jr z,runcmd2 ; yes, handle slice spec + cp ':' ; or ':'? + jr z,runcmd2 ; alt sep for slice spec + jp err_invcmd ; if not, format error +runcmd2: + inc de ; bump past separator + call skipws ; skip possible whitespace + call isnum ; do we have a number? + jp nz,err_invcmd ; if not, format error + call getnum ; get number + jp c,err_invcmd ; handle overflow error + ld (bootslice),a ; save boot slice + jp diskboot ; boot the disk unit/slice +; +;======================================================================= +; Process a DSKY command from key in A +;======================================================================= +; +#if (DSKYENABLE) +; +dskycmd: + call clrled ; clear LEDs +; + call DSKY_GETKEY ; get DSKY key + cp $FF ; check for error + ret z ; abort if so +; + ; Attempt built-in commands + cp KY_BO ; reboot system + jp z,reboot ; if so, do it +; + ; Attempt ROM application launch + ld ix,(ra_tbl_loc) ; point to start of ROM app tbl + ld c,a ; save DSKY key in C +dskycmd1: + ld a,(ix+ra_dskykey) ; get match char + cp c ; compare + jp z,romload ; if match, load it + ld de,ra_entsiz ; table entry size + add ix,de ; bump IX to next entry + ld a,(ix) ; check for end + or (ix+1) ; ... of table + jr nz,dskycmd1 ; loop till done +; + ; Attempt disk boot + ld a,c ; copy key to A + cp KY_F + 1 ; over max? + ret nc ; abort if so + ld (bootunit),a ; set as boot unit + xor a ; zero A + ld (bootslice),a ; boot slice always zero here + jp diskboot ; go do it +; +#endif +; +;======================================================================= +; Special command processing +;======================================================================= +; +; Display Help +; +help: + ld hl,str_help ; point to help string + call pstr ; display it + ret +; +; List ROM apps +; +applst: + ld hl,str_applst + call pstr + call nl + ld ix,(ra_tbl_loc) +applst1: + ; check for end of table + ld a,(ix) + or (ix+1) + ret z +; + ld a,(ix+ra_conkey) + bit 7,a + jr nz,applst2 + push af + call nl + ld a,' ' + call cout + call cout + pop af + call cout + ld a,':' + call cout + ld a,' ' + call cout + ld l,(ix+ra_name) + ld h,(ix+ra_name+1) + call pstr +; +applst2: + ld bc,ra_entsiz + add ix,bc + jr applst1 + ret +; +; Device list +; +devlst: + ld hl,str_devlst ; device list header string + call pstr ; display it + jp prtall ; do it +; +; Restart system +; +reboot: + ld hl,str_reboot ; point to message + call pstr ; print it + call LDELAY ; wait for message to display +; +#if (BIOS == BIOS_WBW) +; +#if (DSKYENABLE) + ld hl,msg_boot ; point to boot message + call DSKY_SHOWSEG ; display message +#endif +; + ; switch to rom bank 0 and jump to address 0 + ld a,BID_BOOT ; boot bank + ld hl,0 ; address zero + call HB_BNKCALL ; does not return +#endif +; +#if (BIOS == BIOS_UNA) + ; switch to rom bank 0 and jump to address 0 + ld bc,$01FB ; UNA func = set bank + ld de,0 ; ROM bank 0 + rst 08 ; do it + jp 0 ; jump to restart address +#endif +; +;======================================================================= +; Load and run a ROM application, IX=ROM app table entry +;======================================================================= +; +romload: +; + ; Notify user + ld hl,str_load + call pstr + ld l,(ix+ra_name) + ld h,(ix+ra_name+1) + call pstr +; +#if (DSKYENABLE) + ld hl,msg_load ; point to load message + call DSKY_SHOWSEG ; display message +#endif +; +#if (BIOS == BIOS_WBW) +; + ; Copy image to it's running location + ld a,(ix+ra_bnk) ; get image source bank id + cp bid_cur ; special value? + jr nz,romload1 ; if not, continue + ld a,(bid_ldr) ; else substitute +romload1: + push af ; save source bank + ld e,a ; source bank to E + ld d,BID_USR ; dest is user bank + ld l,(ix+ra_siz) ; HL := image size + ld h,(ix+ra_siz+1) ; ... + ld b,BF_SYSSETCPY ; HBIOS func: setup bank copy + rst 08 ; do it + ld a,'.' ; dot character + call cout ; show progress + ld e,(ix+ra_dest) ; DE := run dest adr + ld d,(ix+ra_dest+1) ; ... + ld l,(ix+ra_src) ; HL := image source adr + ld h,(ix+ra_src+1) ; ... + ld b,BF_SYSBNKCPY ; HBIOS func: bank copy + rst 08 ; do it + ld a,'.' ; dot character + call cout ; show progress +; + ; Record boot information + pop af ; recover source bank + ld l,a ; L := source bank + ld de,$0100 ; boot volume/slice + ld b,BF_SYSSET ; HBIOS func: system set + ld c,BF_SYSSET_BOOTINFO ; BBIOS subfunc: boot info + rst 08 ; do it + ld a,'.' ; dot character + call cout ; show progress +; +#endif +; +#if (BIOS == BIOS_UNA) +; +; Note: UNA has no interbank memory copy, so we can only load +; images from the current bank. We switch to the original bank +; use a simple ldir to relocate the image, then switch back to the +; user bank to launch. This will only work if the images are in +; the lower 32K and the relocation adr is in the upper 32K. +; + ; Switch to original bank + ld bc,$01FB ; UNA func: set bank + ld de,(bid_ldr) ; select user bank + rst 08 ; do it + ld a,'.' ; dot character + call cout ; show progress +; + ; Copy image to running location + ld l,(ix+ra_src) ; HL := image source adr + ld h,(ix+ra_src+1) ; ... + ld e,(ix+ra_dest) ; DE := run dest adr + ld d,(ix+ra_dest+1) ; ... + ld c,(ix+ra_siz) ; BC := image size + ld b,(ix+ra_siz+1) ; ... + ldir ; copy image + ld a,'.' ; dot character + call cout ; show progress +; + ; Switch back to user bank + ld bc,$01FB ; UNA func: set bank + ld de,(bid_ldr) ; select user bank + rst 08 ; do it + ld a,'.' ; dot character + call cout ; show progress +; + ; Record boot information + ld de,(bid_ldr) ; original bank + ld l,$01 ; encoded boot slice/unit + ld bc,$01FC ; UNA func: set bootstrap hist + rst 08 ; call una +; +#endif +; +#if (DSKYENABLE) + ld hl,msg_go ; point to go message + call DSKY_SHOWSEG ; display message +#endif +; + ld l,(ix+ra_ent) ; HL := app entry address + ld h,(ix+ra_ent+1) ; ... + jp (hl) ; go +; +;======================================================================= +; Boot disk unit/slice +;======================================================================= +; +diskboot: +; + ; Notify user + ld hl,str_boot1 + call pstr + ld a,(bootunit) + call PRTDECB + ld hl,str_boot2 + call pstr + ld a,(bootslice) + call PRTDECB +; +#if (DSKYENABLE) + ld hl,msg_load ; point to load message + call DSKY_SHOWSEG ; display message +#endif +; +#if (BIOS == BIOS_WBW) +; + ; Check that drive actually exists + ld c,a ; put in C for func call + ld b,BF_SYSGET ; HBIOS func: sys get + ld c,BF_SYSGET_DIOCNT ; HBIOS sub-func: disk count + rst 08 ; do it, E=disk count + ld a,(bootunit) ; get boot disk unit + cp e ; compare to count + jp nc,err_nodisk ; handle no disk err +; + ; If non-zero slice requested, confirm device can handle it + ld a,(bootslice) ; get slice + or a ; set flags + jr z,diskboot1 ; slice 0, skip slice check + ld a,(bootunit) ; get disk unit + ld c,a ; put in C for func call + ld b,BF_DIODEVICE ; HBIOS func: device info + rst 08 ; do it + ld a,d ; device type to A + cp DIODEV_IDE ; IDE is max slice device type + jp c,err_noslice ; no such slice, handle err +; +diskboot1: + ; Sense media + ld a,(bootunit) ; get boot disk unit + ld c,a ; put in C for func call + ld b,BF_DIOMEDIA ; HBIOS func: media + ld e,1 ; enable media check/discovery + rst 08 ; do it + jp nz,err_diskio ; handle error + call pdot ; show progress +; + ; Seek to boot info sector, third sector + ld a,(bootslice) ; get boot slice + ld e,a ; move to E for mult + ld h,65 ; 65 tracks per slice + call MULT8 ; hl := h * e + ld de,$0002 ; head 0, sector 2 + ld b,BF_DIOSEEK ; HBIOS func: seek + ld a,(bootunit) ; get boot disk unit + ld c,a ; put in C + rst 08 ; do it + jp nz,err_diskio ; handle error + call pdot ; show progress +; + ; Read sector into local buffer + ld b,BF_DIOREAD ; HBIOS func: disk read + ld a,(bootunit) ; get boot disk unit + ld c,a ; put in C for func call + ld hl,bl_infosec ; read into info sec buffer + ld d,BID_USR ; user bank + ld e,1 ; transfer one sector + rst 08 ; do it + jp nz,err_diskio ; handle error + call pdot ; show progress +; +#endif +; +#if (BIOS == BIOS_UNA) +; + ; Check that drive actually exists + ld a,(bootunit) ; get disk unit to boot + ld b,a ; put in B for func call + ld c,$48 ; UNA func: get disk type + rst 08 ; call UNA, B preserved + jp nz,err_nodisk ; handle error if no such disk +; + ; If non-zero slice requested, confirm device can handle it + ld a,(bootslice) ; get slice + or a ; set flags + jr z,diskboot1 ; slice 0, skip slice check + ld a,d ; disk type to A + cp $41 ; IDE? + jr z,diskboot1 ; if so, OK + cp $42 ; PPIDE? + jr z,diskboot1 ; if so, OK + cp $43 ; SD? + jr z,diskboot1 ; if so, OK + cp $44 ; DSD? + jr z,diskboot1 ; if so, OK + jp err_noslice ; no such slice, handle err +; +diskboot1: + ; Add slice offset + ld a,(bootslice) ; get boot slice, A is loop cnt + ld hl,0 ; DE:HL is LBA + ld de,0 ; ... initialize to zero + ld bc,16640 ; sectors per slice +diskboot2: + or a ; set flags to check loop ctr + jr z,diskboot4 ; done if counter exhausted + add hl,bc ; add one slice to low word + jr nc,diskboot3 ; check for carry + inc de ; if so, bump high word +diskboot3: + dec a ; dec loop downcounter + jr diskboot2 ; and loop +; +diskboot4: + ld (loadlba),hl ; save lba, low word + ld (loadlba+2),de ; save lba, high word +; + ; Seek to boot info sector, third sector + ld bc,2 ; sector offset + add hl,bc ; add to LBA value low word + jr nc,diskboot5 ; check for carry + inc de ; if so, bump high word +diskboot5: + ld a,(bootunit) ; get disk unit to boot + ld b,a ; put in B for func call + ld c,$41 ; UNA func: set lba + rst 08 ; set lba + jp nz,err_api ; handle error + call pdot ; show progress +; + ; Read sector into local buffer + ld c,$42 ; UNA func: read sectors + ld de,bl_infosec ; dest of cpm image + ld l,1 ; sectors to read + rst 08 ; do read + jp nz,err_diskio ; handle error +; +#endif +; + ; Check signature + ld de,(bb_sig) ; get signature read + ld a,$A5 ; expected value of first byte + cp d ; compare + jp nz,err_sig ; handle error + ld a,$5A ; expected value of second byte + cp e ; compare + jp nz,err_sig ; handle error +; + ; Print disk boot info + ; Volume "xxxxxxx" (0xXXXX-0xXXXX, entry @ 0xXXXX) + ld hl,str_binfo1 ; load string + call pstr ; print + push hl ; save string ptr + ld hl,bb_label ; point to label + call pvol ; print it + pop hl ; restore string ptr + call pstr ; print + push hl ; save string ptr + ld bc,(bb_cpmloc) ; get load loc + call PRTHEXWORD ; print it + pop hl ; restore string ptr + call pstr ; print + push hl ; save string ptr + ld bc,(bb_cpmend) ; get load end + call PRTHEXWORD ; print it + pop hl ; restore string ptr + call pstr ; print + push hl ; save string ptr + ld bc,(bb_cpment) ; get load end + call PRTHEXWORD ; print it + pop hl ; restore string ptr + call pstr ; print +; + ; Compute number of sectors to load + ld hl,(bb_cpmend) ; hl := end + ld de,(bb_cpmloc) ; de := start + or a ; clear carry + sbc hl,de ; hl := length to load + ld a,h ; determine 512 byte sector count + rra ; ... by dividing msb by two + ld (loadcnt),a ; ... and save it + call pdot ; show progress +; +#if (BIOS == BIOS_WBW) +; + ; Load image into memory + ld b,BF_DIOREAD ; HBIOS func: read sectors + ld a,(bootunit) ; get boot disk unit + ld c,a ; put in C + ld hl,(bb_cpmloc) ; load address + ld d,BID_USR ; user bank + ld a,(loadcnt) ; get sectors to read + ld e,a ; number of sectors to load + rst 08 ; do it + jp nz,err_diskio ; handle errors + call pdot ; show progress +; + ; Record boot unit/slice + ld b,BF_SYSSET ; hb func: set hbios parameter + ld c,BF_SYSSET_BOOTINFO ; hb subfunc: set boot info + ld a,(bid_ldr) ; original bank is boot bank + ld l,a ; ... and save as boot bank + ld a,(bootunit) ; load boot unit + ld d,a ; save in D + ld a,(bootslice) ; load boot slice + ld e,a ; save in E + rst 08 + jp nz,err_api ; handle errors + call pdot ; show progress +; +#endif +; +#if (BIOS == BIOS_UNA) +; + ; Start os load at sector 3 + ld hl,(loadlba) ; low word of saved LBA + ld de,(loadlba+2) ; high word of saved LBA + ld bc,3 ; offset for sector 3 + add hl,bc ; apply it + jr nc,diskboot6 ; check for carry + inc de ; bump high word if so +diskboot6: + ld c,$41 ; UNA func: set lba + ld a,(bootunit) ; get boot disk unit + ld b,a ; move to B + rst 08 ; set lba + jp nz,err_api ; handle error +; + ; Read OS image into memory + ld c,$42 ; UNA func: read sectors + ld a,(bootunit) ; get boot disk unit + ld b,a ; move to B + ld de,(bb_cpmloc) ; dest of cpm image + ld a,(loadcnt) ; get sectors to read + ld l,a ; sectors to read + rst 08 ; do read + jp nz,err_diskio ; handle error + call pdot ; show progress +; + ; Record boot unit/slice + ; UNA provides only a single byte to record the boot unit + ; so we encode the unit/slice into one byte by using the + ; high nibble for unit and low nibble for slice. + ld de,-1 ; boot rom page, -1 for n/a + ld a,(bootslice) ; get boot slice + and $0F ; 4 bits only + rlca ; rotate to high bits + rlca ; ... + rlca ; ... + rlca ; ... + ld l,a ; put in L + ld a,(bootunit) ; get boot disk unit + and $0F ; 4 bits only + or l ; combine + ld l,a ; back to L + ld bc,$01FC ; UNA func: set bootstrap hist + rst 08 ; call UNA + jp nz,err_api ; handle error + call pdot ; show progress +; +#endif +; +#if (DSKYENABLE) + ld hl,msg_go ; point to go message + call DSKY_SHOWSEG ; display message +#endif +; + ; Jump to entry vector + ld hl,(bb_cpment) ; get entry vector + jp (hl) ; and go there +; +;======================================================================= +; Utility functions +;======================================================================= +; +; Clear LEDs +; +clrled: +#if (BIOS == BIOS_WBW) + #if (DIAGENABLE) + xor a ; zero accum + out (DIAGPORT),a ; clear diag leds + #endif + #if (LEDENABLE) + or $FF ; led is inverted + out (LEDPORT),a ; clear led + #endif +#endif + ret +; +; Print string at HL on console, null terminated +; +pstr: + ld a,(hl) ; get next character + or a ; set flags + inc hl ; bump pointer regardless + ret z ; done if null + call cout ; display character + jr pstr ; loop till done +; +; Print volume label string at HL, '$' terminated, 16 chars max +; +pvol: + ld b,16 ; init max char downcounter +pvol1: + ld a,(hl) ; get next character + cp '$' ; set flags + inc hl ; bump pointer regardless + ret z ; done if null + call cout ; display character + djnz pvol1 ; loop till done + ret ; hit max of 16 chars +; +; Start a newline on console (cr/lf) +; +nl2: + call nl ; double newline +nl: + ld a,cr ; cr + call cout ; send it + ld a,lf ; lf + jp cout ; send it and return +; +; Print a dot on console +; +pdot: + push af + ld a,'.' + call cout + pop af + ret +; +; Read a string on the console +; +; Uses address $0080 in page zero for buffer +; Input is zero terminated +; +rdln: + ld de,cmdbuf ; init buffer address ptr +rdln_nxt: + call cin ; get a character + cp bs ; backspace? + jr z,rdln_bs ; handle it if so + cp cr ; return? + jr z,rdln_cr ; handle it if so +; + ; check for non-printing characters + cp ' ' ; first printable is space char + jr c,rdln_bel ; too low, beep and loop + cp '~'+1 ; last printable char + jr nc,rdln_bel ; too high, beep and loop +; + ; need to check for buffer overflow here!!! + ld hl,cmdbuf+cmdmax ; max cmd length + or a ; clear carry + sbc hl,de ; test for max + jr z,rdln_bel ; at max, beep and loop +; + ; good to go, echo and store character + call cout ; echo character input + ld (de),a ; save in buffer + inc de ; inc buffer ptr + jr rdln_nxt ; loop till done +; +rdln_bs: + ld hl,cmdbuf ; start of buffer + or a ; clear carry + sbc hl,de ; subtract from cur buf ptr + jr z,rdln_bel ; at buf start, just beep + ld hl,str_bs ; backspace sequence + call pstr ; send it + dec de ; backup buffer pointer + jr rdln_nxt ; and loop +; +rdln_bel: + ld a,bel ; Bell characters + call cout ; send it + jr rdln_nxt ; and loop +; +rdln_cr: + xor a ; null to A + ld (de),a ; store terminator + ret ; and return +; +; Skip whitespace at buffer adr in DE, returns with first +; non-whitespace character in A. +; +skipws: + ld a,(de) ; get next char + cp ' ' ; blank? + ret nz ; nope, done + inc de ; bump buffer pointer + jr skipws ; and loop ; -;================================================================================================== -; INCLUDES -;================================================================================================== -; -#DEFINE USEDELAY -#INCLUDE "util.asm" -; -#IF (DSKYENABLE) -#DEFINE DSKY_KBD -#INCLUDE "dsky.asm" -#ENDIF -; -;================================================================================================== -; CONSOLE CHARACTER I/O HELPER ROUTINES (REGISTERS PRESERVED) -;================================================================================================== -; -#IF (PLATFORM != PLT_UNA) -; -; OUTPUT CHARACTER FROM A -; -COUT: - ; SAVE ALL INCOMING REGISTERS - PUSH AF - PUSH BC - PUSH DE - PUSH HL -; - ; OUTPUT CHARACTER TO CONSOLE VIA HBIOS - LD E,A ; OUTPUT CHAR TO E - LD C,CIODEV_CONSOLE ; CONSOLE UNIT TO C - LD B,BF_CIOOUT ; HBIOS FUNC: OUTPUT CHAR - RST 08 ; HBIOS OUTPUTS CHARACTDR -; - ; RESTORE ALL REGISTERS - POP HL - POP DE - POP BC - POP AF - RET -; -; INPUT CHARACTER TO A -; -CIN: - ; SAVE INCOMING REGISTERS (AF IS OUTPUT) - PUSH BC - PUSH DE - PUSH HL -; - ; INPUT CHARACTER FROM CONSOLE VIA HBIOS - LD C,CIODEV_CONSOLE ; CONSOLE UNIT TO C - LD B,BF_CIOIN ; HBIOS FUNC: INPUT CHAR - RST 08 ; HBIOS READS CHARACTDR - LD A,E ; MOVE CHARACTER TO A FOR RETURN -; - ; RESTORE REGISTERS (AF IS OUTPUT) - POP HL - POP DE - POP BC - RET -; -; RETURN INPUT STATUS IN A (0 = NO CHAR, !=0 CHAR WAITING) -; -CST: - ; SAVE INCOMING REGISTERS (AF IS OUTPUT) - PUSH BC - PUSH DE - PUSH HL -; - ; GET CONSOLE INPUT STATUS VIA HBIOS - LD C,CIODEV_CONSOLE ; CONSOLE UNIT TO C - LD B,BF_CIOIST ; HBIOS FUNC: INPUT STATUS - RST 08 ; HBIOS RETURNS STATUS IN A -; - ; RESTORE REGISTERS (AF IS OUTPUT) - POP HL - POP DE - POP BC - RET -; -#ENDIF -; -#IF (PLATFORM == PLT_UNA) -; -; OUTPUT CHARACTER FROM A -; -COUT: - ; SAVE ALL INCOMING REGISTERS - PUSH AF - PUSH BC - PUSH DE - PUSH HL -; - ; OUTPUT CHARACTER TO CONSOLE VIA UBIOS - LD E,A - LD BC,$12 - RST 08 -; - ; RESTORE ALL REGISTERS - POP HL - POP DE - POP BC - POP AF - RET -; -; INPUT CHARACTER TO A -; -CIN: - ; SAVE INCOMING REGISTERS (AF IS OUTPUT) - PUSH BC - PUSH DE - PUSH HL -; - ; INPUT CHARACTER FROM CONSOLE VIA UBIOS - LD BC,$11 - RST 08 - LD A,E -; - ; RESTORE REGISTERS (AF IS OUTPUT) - POP HL - POP DE - POP BC - RET -; -; RETURN INPUT STATUS IN A (0 = NO CHAR, !=0 CHAR WAITING) -; -CST: - ; SAVE INCOMING REGISTERS (AF IS OUTPUT) - PUSH BC - PUSH DE - PUSH HL -; - ; GET CONSOLE INPUT STATUS VIA UBIOS - LD BC,$13 - RST 08 - LD A,E -; - ; RESTORE REGISTERS (AF IS OUTPUT) - POP HL - POP DE - POP BC - RET -; -#ENDIF -; -; READ A CONSOLE CHARACTER AND CONVERT TO UPPER CASE -; -CINUC: - CALL CIN - AND 7FH ; STRIP HI BIT - CP 'A' ; KEEP NUMBERS, CONTROLS - RET C ; AND UPPER CASE - CP 7BH ; SEE IF NOT LOWER CASE - RET NC - AND 5FH ; MAKE UPPER CASE - RET -; -;================================================================================================== -; FILL REMAINDER OF BANK -;================================================================================================== -; -SLACK: .EQU ($8000 + LDR_SIZ - $) - .FILL SLACK -; - .ECHO "LOADER space remaining: " - .ECHO SLACK - .ECHO " bytes.\n" -; -;================================================================================================== -; WORKING DATA STORAGE -;================================================================================================== - .ORG $8000 + LDR_SIZ -; - .DS 64 ; 32 LEVEL STACK -BL_STACK .EQU $ ; ... TOP IS HERE -; -BL_COUNT .DS 1 ; LOAD COUNTER -BL_TIMEOUT .DS 2 ; AUTOBOOT TIMEOUT COUNTDOWN COUNTER -BL_BOOTID .DS 1 ; BOOT DEVICE ID CHOSEN BY USER -BL_DEVICE .DS 1 ; DEVICE TO LOAD FROM -BL_LU .DS 1 ; LU TO LOAD FROM -; -; BOOT INFO SECTOR IS READ INTO AREA BELOW -; THE THIRD SECTOR OF A DISK DEVICE IS RESERVED FOR BOOT INFO -; -BL_INFOSEC .EQU $ - .DS (512 - 128) -BB_METABUF .EQU $ -BB_SIG .DS 2 ; SIGNATURE (WILL BE 0A55AH IF SET) -BB_PLATFORM .DS 1 ; FORMATTING PLATFORM -BB_DEVICE .DS 1 ; FORMATTING DEVICE -BB_FORMATTER .DS 8 ; FORMATTING PROGRAM -BB_DRIVE .DS 1 ; PHYSICAL DISK DRIVE # -BB_LU .DS 1 ; LOGICAL UNIT (LU) - .DS 1 ; MSB OF LU, NOW DEPRECATED - .DS (BB_METABUF + 128) - $ - 32 -BB_PROTECT .DS 1 ; WRITE PROTECT BOOLEAN -BB_UPDATES .DS 2 ; UPDATE COUNTER -BB_RMJ .DS 1 ; RMJ MAJOR VERSION NUMBER -BB_RMN .DS 1 ; RMN MINOR VERSION NUMBER -BB_RUP .DS 1 ; RUP UPDATE NUMBER -BB_RTP .DS 1 ; RTP PATCH LEVEL -BB_LABEL .DS 16 ; 16 CHARACTER DRIVE LABEL -BB_TERM .DS 1 ; LABEL TERMINATOR ('$') -BB_BILOC .DS 2 ; LOC TO PATCH BOOT DRIVE INFO TO (IF NOT ZERO) -BB_CPMLOC .DS 2 ; FINAL RAM DESTINATION FOR CPM/CBIOS -BB_CPMEND .DS 2 ; END ADDRESS FOR LOAD -BB_CPMENT .DS 2 ; CP/M ENTRY POINT (CBIOS COLD BOOT) -; - .END +; Uppercase character in A +; +upcase: + cp 'a' ; below 'a'? + ret c ; if so, nothing to do + cp 'z'+1 ; above 'z'? + ret nc ; if so, nothing to do + and ~$20 ; convert character to lower + ret ; done +; +; Get numeric chars at DE and convert to number returned in A +; Carry flag set on overflow +; +getnum: + ld c,0 ; C is working register +getnum1: + ld a,(de) ; get the active char + cp '0' ; compare to ascii '0' + jr c,getnum2 ; abort if below + cp '9' + 1 ; compare to ascii '9' + jr nc,getnum2 ; abort if above +; + ; valid digit, add new digit to C + ld a,c ; get working value to A + rlca ; multiply by 10 + ret c ; overflow, return with carry set + rlca ; ... + ret c ; overflow, return with carry set + add a,c ; ... + ret c ; overflow, return with carry set + rlca ; ... + ret c ; overflow, return with carry set + ld c,a ; back to C + ld a,(de) ; get new digit + sub '0' ; make binary + add a,c ; add in working value + ret c ; overflow, return with carry set + ld c,a ; back to C +; + inc de ; bump to next char + jr getnum1 ; loop +; +getnum2: ; return result + ld a,c ; return result in A + or a ; with flags set, CF is cleared + ret +; +; Is character in A numberic? NZ if not +; +isnum: + cp '0' ; compare to ascii '0' + jr c,isnum1 ; abort if below + cp '9' + 1 ; compare to ascii '9' + jr nc,isnum1 ; abort if above + cp a ; set Z + ret +isnum1: + or $FF ; set NZ + ret ; and done +; +;======================================================================= +; Console character I/O helper routines (registers preserved) +;======================================================================= +; +#if (BIOS == BIOS_WBW) +; +; Output character from A +; +cout: + ; Save all incoming registers + push af + push bc + push de + push hl +; + ; Output character to console via HBIOS + ld e,a ; output char to E + ld c,CIO_CONSOLE ; console unit to C + ld b,BF_CIOOUT ; HBIOS func: output char + rst 08 ; HBIOS outputs character +; + ; Restore all registers + pop hl + pop de + pop bc + pop af + ret +; +; Input character to A +; +cin: + ; Save incoming registers (AF is output) + push bc + push de + push hl +; + ; Input character from console via hbios + ld c,CIO_CONSOLE ; console unit to c + ld b,BF_CIOIN ; HBIOS func: input char + rst 08 ; HBIOS reads charactdr + ld a,e ; move character to A for return +; + ; Restore registers (AF is output) + pop hl + pop de + pop bc + ret +; +; Return input status in A (0 = no char, != 0 char waiting) +; +cst: + ; Save incoming registers (AF is output) + push bc + push de + push hl +; + ; Get console input status via HBIOS + ld c,CIO_CONSOLE ; console unit to C + ld b,BF_CIOIST ; HBIOS func: input status + rst 08 ; HBIOS returns status in A +; + ; Restore registers (AF is output) + pop hl + pop de + pop bc + ret +; +#endif +; +#if (BIOS == BIOS_UNA) +; +; Output character from A +; +cout: + ; Save all incoming registers + push af + push bc + push de + push hl +; + ; Output character to console via UBIOS + ld e,a + ld bc,$12 + rst 08 +; + ; Restore all registers + pop hl + pop de + pop bc + pop af + ret +; +; Input character to A +; +cin: + ; Save incoming registers (AF is output) + push bc + push de + push hl +; + ; Input character from console via UBIOS + ld bc,$11 + rst 08 + ld a,e +; + ; Restore registers (AF is output) + pop hl + pop de + pop bc + ret +; +; Return input status in A (0 = no char, != 0 char waiting) +; +cst: + ; Save incoming registers (AF is output) + push bc + push de + push hl +; + ; Get console input status via UBIOS + ld bc,$13 + rst 08 + ld a,e + or a +; + ; Restore registers (AF is output) + pop hl + pop de + pop bc + ret +; +#endif +; +; Generic console I/O +; +CIN .equ cin +COUT .equ cout +CST .equ cst +; +;======================================================================= +; Device inventory display +;======================================================================= +; +; Print list of all drives (WBW) +; +#if (BIOS == BIOS_WBW) +; +prtall: + call nl ; formatting + ld b,BF_SYSGET + ld c,BF_SYSGET_DIOCNT + rst 08 ; E := disk unit count + ld b,e ; count to B + ld a,b ; count to A + or a ; set flags + ret z ; bail out if zero + ld c,0 ; init device index +; +prtall1: + ld hl,str_disk ; prefix string + call pstr ; display it + ld a,c ; index + call PRTDECB ; print it + ld hl,str_on ; separator string + call pstr + push bc ; save loop control + ld b,BF_DIODEVICE ; HBIOS func: report device info + rst 08 ; call HBIOS + call prtdrv ; print it + pop bc ; restore loop control + inc c ; bump index + djnz prtall1 ; loop as needed + ret ; done +; +; Print the device info +; On input D has device type, E has device number +; Destroy no registers other than A +; +prtdrv: + push de ; preserve de + push hl ; preserve HL + ld a,d ; load device/unit + rrca ; rotate device + rrca ; ... bits + rrca ; ... into + rrca ; ... lowest 4 bits + and $0F ; isolate device bits + add a,a ; multiple by two for word table + ld hl,devtbl ; point to start of table + call ADDHLA ; add A to HL for table entry + ld a,(hl) ; deref HL for string adr + inc hl ; ... + ld h,(hl) ; ... + ld l,a ; ... + call pstr ; print the device nmemonic + pop hl ; recover HL + pop de ; recover DE + ld a,e ; device number + call PRTDECB ; print it + ld a,':' ; suffix + call cout ; print it + ret +; +devtbl: ; device table + .dw dev00, dev01, dev02, dev03 + .dw dev04, dev05, dev06, dev07 + .dw dev08, dev09, dev10, dev11 + .dw dev12, dev13, dev14, dev15 +; +devunk .db "???",0 +dev00 .db "MD",0 +dev01 .db "FD",0 +dev02 .db "RAMF",0 +dev03 .db "IDE",0 +dev04 .db "ATAPI",0 +dev05 .db "PPIDE",0 +dev06 .db "SD",0 +dev07 .db "PRPSD",0 +dev08 .db "PPPSD",0 +dev09 .db "HDSK",0 +dev10 .equ devunk +dev11 .equ devunk +dev12 .equ devunk +dev13 .equ devunk +dev14 .equ devunk +dev15 .equ devunk +; +#endif +; +; +; +#if (BIOS == BIOS_UNA) +; +; Print list of all drives (UNA) +; +prtall: + call nl ; formatting + ld b,0 ; start with unit 0 +; +prtall1: ; loop thru all units available + ld c,$48 ; UNA func: get disk type + ld l,0 ; preset unit count to zero + rst 08 ; call UNA, B preserved + ld a,l ; unit count to a + or a ; past end? + ret z ; we are done + push bc ; save unit + call prtdrv ; process the unit + pop bc ; restore unit + inc b ; next unit + jr prtall1 ; loop +; +; print the una unit info +; on input b has unit +; +prtdrv: + push bc ; save unit + push de ; save disk type + ld hl,str_disk ; prefix string + call pstr ; display it + ld a,b ; index + call PRTDECB ; print it + ld a,' ' ; formatting + call cout ; do it + ld a,'=' ; formatting + call cout ; do it + ld a,' ' ; formatting + call cout ; do it + pop de ; recover disk type + ld a,d ; disk type to a + cp $40 ; ram/rom? + jr z,prtdrv1 ; handle ram/rom + ld hl,devide ; assume ide + cp $41 ; ide? + jr z,prtdrv2 ; print it + ld hl,devppide ; assume ppide + cp $42 ; ppide? + jr z,prtdrv2 ; print it + ld hl,devsd ; assume sd + cp $43 ; sd? + jr z,prtdrv2 ; print it + ld hl,devdsd ; assume dsd + cp $44 ; dsd? + jr z,prtdrv2 ; print it + ld hl,devunk ; otherwise unknown + jr prtdrv2 +; +prtdrv1: ; handle ram/rom + ld c,$45 ; una func: get disk info + ld de,bl_infosec ; 512 byte buffer + rst 08 ; call una + bit 7,b ; test ram drive bit + ld hl,devrom ; assume rom + jr z,prtdrv2 ; if so, print it + ld hl,devram ; otherwise ram + jr prtdrv2 ; print it +; +prtdrv2: ; print device + pop bc ; recover unit + call pstr ; print device name + ld a,b ; unit to a + call PRTDECB ; print it + ld a,':' ; device name suffix + call cout ; print it + ret ; done +; +devram .db "RAM",0 +devrom .db "ROM",0 +devide .db "IDE",0 +devppide .db "PPIDE",0 +devsd .db "SD",0 +devdsd .db "DSD",0 +devunk .db "UNK",0 +; +#endif +; +;======================================================================= +; Error handlers +;======================================================================= +; +err_invcmd: + ld hl,str_err_invcmd + jr err +err_nodisk: + ld hl,str_err_nodisk + jr err +; +err_noslice: + ld hl,str_err_noslice + jr err +; +err_diskio: + ld hl,str_err_diskio + jr err +; +err_sig: + ld hl,str_err_sig + jr err +; +err_api: + ld hl,str_err_api + jr err +; +err: + push hl +; ld a,(acmd_act) ; get auto cmd active flag +; or a ; set flags +; call nz,showcmd ; if auto cmd act, show cmd +; ld a,bel ; bel character +; call cout ; beep + ld hl,str_err_prefix + call pstr + pop hl + jp pstr +; +str_err_prefix .db bel,"\r\n\r\n*** ",0 +str_err_invcmd .db "Invalid command",0 +str_err_nodisk .db "Disk unit not available",0 +str_err_noslice .db "Disk unit does not support slices",0 +str_err_diskio .db "Disk I/O failure",0 +str_err_sig .db "No system image on disk",0 +str_err_api .db "Unexpected hardware BIOS API failure",0 +; +;======================================================================= +; Includes +;======================================================================= +; +#define USEDELAY +#include "util.asm" +; +#if (DSKYENABLE) +#define DSKY_KBD +#include "dsky.asm" +#endif +; +;======================================================================= +; Working data storage (initialized) +;======================================================================= +; +acmd .db BOOT_DEFAULT ; auto cmd string + .db 0 +acmd_len .equ $ - acmd ; len of auto cmd +acmd_act .db $FF ; auto cmd active +acmd_to .dw BOOT_TIMEOUT ; auto cmd timeout +; +;======================================================================= +; Strings +;======================================================================= +; +str_banner .db PLATFORM_NAME," Boot Loader",0 +;str_prompt .db "Boot [(H)elp]: ",0 +str_prompt .db "Boot [H=Help]: ",0 +str_bs .db bs,' ',bs,0 +str_reboot .db "\r\n\r\nRestarting System...",0 +str_applst .db "\r\n\r\nROM Applications:",0 +str_devlst .db "\r\n\r\nDevices:",0 +str_invcmd .db "\r\n\r\n*** Invalid Command ***",bel,0 +str_load .db "\r\n\r\nLoading ",0 +str_disk .db "\r\n Disk Unit ",0 +str_on .db " on ",0 +str_boot1 .db "\r\n\r\nBooting Disk Unit ",0 +str_boot2 .db ", Slice ",0 +str_binfo1 .db "\r\n\r\nVolume ",$22,0 +str_binfo2 .db $22," [0x",0 +str_binfo3 .db "-0x",0 +str_binfo4 .db ", entry @ 0x",0 +str_binfo5 .db "]",0 +; +str_help .db "\r\n" + .db "\r\n L: List ROM Applications" + .db "\r\n D: Device Inventory" + .db "\r\n R: Reboot System" + .db "\r\n [.]: Boot Disk Unit/Slice" + .db 0 +; +#if (DSKYENABLE) +msg_sel .db $ff,$9d,$9d,$8f,$ec,$80,$80,$80 ; "boot? " +msg_boot .db $ff,$9d,$9d,$8f,$00,$00,$00,$80 ; "boot... " +msg_load .db $8b,$9d,$fd,$bd,$00,$00,$00,$80 ; "load... " +msg_go .db $db,$9d,$00,$00,$00,$80,$80,$80 ; "go... " +#endif +; +;======================================================================= +; ROM Application Table +;======================================================================= +; +; Macro ra_ent: +; +; WBW UNA +; p1: Application name string adr word (+0) word (+0) +; p2: Console keyboard selection key byte (+2) byte (+2) +; p3: DSKY selection key byte (+3) byte (+3) +; p4: Application image bank byte (+4) word (+4) +; p5: Application image source address word (+5) word (+6) +; p6: Application image dest load address word (+7) word (+8) +; p7: Application image size word (+9) word (+10) +; p8: Application entry address word (+11) word (+12) +; +#if (BIOS == BIOS_WBW) +ra_name .equ 0 +ra_conkey .equ 2 +ra_dskykey .equ 3 +ra_bnk .equ 4 +ra_src .equ 5 +ra_dest .equ 7 +ra_siz .equ 9 +ra_ent .equ 11 +#endif +; +#if (BIOS == BIOS_UNA) +ra_name .equ 0 +ra_conkey .equ 2 +ra_dskykey .equ 3 +ra_bnk .equ 4 +ra_src .equ 6 +ra_dest .equ 8 +ra_siz .equ 10 +ra_ent .equ 12 +#endif +; +#define ra_ent(p1,p2,p3,p4,p5,p6,p7,p8) \ +#defcont .dw p1 \ +#defcont .db p2 \ +#if (DSKYENABLE) +#defcont .db p3 \ +#else +#defcont .db $FF \ +#endif +#if (BIOS == BIOS_WBW) +#defcont .db p4 \ +#endif +#if (BIOS == BIOS_UNA) +#defcont .dw p4 \ +#endif +#defcont .dw p5 \ +#defcont .dw p6 \ +#defcont .dw p7 \ +#defcont .dw p8 +; +; Note: The formatting of the following is critical. TASM does not pass +; macro arguments well. Ensure std.asm holds the definitions for *_LOC, +; *_SIZ *_END and any code generated which does not include std.asm is +; synced. +; +; Note: The loadable ROM images are placed in ROM banks BID_IMG0 and +; BID_IMG1. However, RomWBW supports a mechanism to load a complete +; new system dynamically as a runnable application (see appboot and +; imgboot in hbios.asm). In this case, the contents of BID_IMG0 will +; be pre-loaded into the currently executing ram bank thereby allowing +; those images to be dynamically loaded as well. To support this +; concept, a pseudo-bank called bid_cur is used to specify the images +; normally found in BID_IMG0. In romload, this special value will cause +; the associated image to be loaded from the currently executing bank +; which will be correct regardless of the load mode. Images in other +; banks (BID_IMG1) will always be loaded directly from ROM. +; +ra_tbl: +; +; Name Key Dsky Bank Src Dest Size Entry +; --------- ------- ----- -------- ----- ------- ------- ---------- +ra_ent(str_mon, 'M', KY_CL, BID_IMG0, $1000, MON_LOC, MON_SIZ, MON_SERIAL) +ra_entsiz .equ $ - ra_tbl +ra_ent(str_cpm22, 'C', KY_BK, BID_IMG0, $2000, CPM_LOC, CPM_SIZ, CPM_ENT) +ra_ent(str_zsys, 'Z', KY_FW, BID_IMG0, $5000, CPM_LOC, CPM_SIZ, CPM_ENT) +#if (BIOS == BIOS_WBW) +ra_ent(str_fth, 'F', KY_EX, BID_IMG1, $0000, FTH_LOC, FTH_SIZ, FTH_LOC) +ra_ent(str_bas, 'B', KY_DE, BID_IMG1, $1700, BAS_LOC, BAS_SIZ, BAS_LOC) +ra_ent(str_tbas, 'T', KY_EN, BID_IMG1, $3700, TBC_LOC, TBC_SIZ, TBC_LOC) +ra_ent(str_play, 'P', $FF, BID_IMG1, $4000, GAM_LOC, GAM_SIZ, GAM_LOC) +ra_ent(str_user, 'U', $FF, BID_IMG1, $7000, USR_LOC, USR_SIZ, USR_LOC) +#endif +#if (DSKYENABLE) +ra_ent(str_dsky, 'Y'+$80, KY_GO, bid_cur, $1000, MON_LOC, MON_SIZ, MON_DSKY) +#endif +ra_ent(str_egg, 'E'+$80, $FF , bid_cur, $0E00, EGG_LOC, EGG_SIZ, EGG_LOC) + .dw 0 ; table terminator +; +ra_tbl_app: +; +; Name Key Dsky Bank Src Dest Size Entry +; --------- ------- ----- -------- ----- ------- ------- ---------- +ra_ent(str_mon, 'M', KY_CL, bid_cur, $1000, MON_LOC, MON_SIZ, MON_SERIAL) +ra_ent(str_zsys, 'Z', KY_FW, bid_cur, $2000, CPM_LOC, CPM_SIZ, CPM_ENT) +#if (DSKYENABLE) +ra_ent(str_dsky, 'Y'+$80, KY_GO, bid_cur, $1000, MON_LOC, MON_SIZ, MON_DSKY) +#endif +ra_ent(str_egg, 'E'+$80, $FF , bid_cur, $0E00, EGG_LOC, EGG_SIZ, EGG_LOC) + .dw 0 ; table terminator +; +str_mon .db "Monitor",0 +str_cpm22 .db "CP/M 2.2",0 +str_zsys .db "Z-System",0 +str_dsky .db "DSKY Monitor",0 +str_fth .db "Forth",0 +str_bas .db "BASIC",0 +str_tbas .db "Tasty BASIC",0 +str_play .db "Play a Game",0 +str_user .db "User App",0 +str_egg .db "",0 +; +;======================================================================= +; Pad remainder of ROM Loader +;======================================================================= +; +slack .equ ($8000 + LDR_SIZ - $) + .fill slack +; + .echo "LOADER space remaining: " + .echo slack + .echo " bytes.\n" +; +;======================================================================= +; Working data storage (uninitialized) +;======================================================================= +; + .ds 64 ; 32 level stack +bl_stack .equ $ ; ... top is here +; +#if (BIOS == BIOS_WBW) +bid_ldr .ds 1 ; bank at startup +#endif +#if (BIOS == BIOS_UNA) +bid_ldr .ds 2 ; bank at startup +loadlba .ds 4 ; lba for load, dword +#endif +; +ra_tbl_loc .ds 2 ; points to active ra_tbl +bootunit .ds 1 ; boot disk unit +bootslice .ds 1 ; boot disk slice +loadcnt .ds 1 ; num disk sectors to load +; +; Boot info sector is read into area below. +; The third sector of a disk device is reserved for boot info. +; +bl_infosec .equ $ + .ds (512 - 128) +bb_metabuf .equ $ +bb_sig .ds 2 ; signature (0xA55A if set) +bb_platform .ds 1 ; formatting platform +bb_device .ds 1 ; formatting device +bb_formatter .ds 8 ; formatting program +bb_drive .ds 1 ; physical disk drive # +bb_lu .ds 1 ; logical unit (lu) + .ds 1 ; msb of lu, now deprecated + .ds (bb_metabuf + 128) - $ - 32 +bb_protect .ds 1 ; write protect boolean +bb_updates .ds 2 ; update counter +bb_rmj .ds 1 ; rmj major version number +bb_rmn .ds 1 ; rmn minor version number +bb_rup .ds 1 ; rup update number +bb_rtp .ds 1 ; rtp patch level +bb_label .ds 16 ; 16 character drive label +bb_term .ds 1 ; label terminator ('$') +bb_biloc .ds 2 ; loc to patch boot drive info +bb_cpmloc .ds 2 ; final ram dest for cpm/cbios +bb_cpmend .ds 2 ; end address for load +bb_cpment .ds 2 ; CP/M entry point (cbios boot) +; + .end diff --git a/Source/HBIOS/sd.asm b/Source/HBIOS/sd.asm index 29a21200..b0b2e379 100644 --- a/Source/HBIOS/sd.asm +++ b/Source/HBIOS/sd.asm @@ -4,24 +4,24 @@ ;============================================================================= ; ; 1) TESTING -; - TRY TO TEST GOIDLE, FIND CARD THAT REQUIRES 2 REQUESTS -; - DUAL CARDS -; - TEST XC CARD TYPE DETECTION -; - TRY TO GET INIT TO FAIL, REMOVE DELAYS AT START OF GOIDLE? -; -;------------------------------------------------------------------------------ -; SD Signal Active JUHA N8 CSIO PPI UART DSD MK4 -; ------------ ------- ------- ------- ------- ------- ------- ------- ------- -; CS (DAT3) LO -> RTC:2 RTC:2 RTC:2 ~PC:4 ~MCR:3 OPR:2 MK4_SD:2 -; CLK HI -> RTC:1 RTC:1 N/A PC:1 ~MCR:2 OPR:1 N/A -; DI (CMD) HI -> RTC:0 RTC:0 N/A PC:0 ~MCR:0 OPR:0 N/A -; DO (DAT0) HI -> RTC:7 RTC:6 N/A PB:7 ~MSR:5 OPR:0 N/A -;------------------------------------------------------------------------------ +; - TRY TO TEST GOIDLE, FIND CARD THAT REQUIRES 2 REQUESTS +; - DUAL CARDS +; - TEST XC CARD TYPE DETECTION +; - TRY TO GET INIT TO FAIL, REMOVE DELAYS AT START OF GOIDLE? +; +;---------------------------------------------------------------------------------------------- +; SD Signal Active JUHA N8 CSIO PPI UART DSD MK4 SC MT +; ------------ ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- +; CS (DAT3) LO -> RTC:2 RTC:2 RTC:2 ~PC:4 ~MCR:3 OPR:2 SD:2 ~RTC:2/3OPR:4/5 +; CLK HI -> RTC:1 RTC:1 CSIO PC:1 ~MCR:2 OPR:1 CSIO CSIO SPI +; DI (CMD) HI -> RTC:0 RTC:0 CSIO PC:0 ~MCR:0 OPR:0 CSIO CSIO SPI +; DO (DAT0) HI -> RTC:7 RTC:6 CSIO PB:7 ~MSR:5 OPR:0 CSIO CSIO SPI +;---------------------------------------------------------------------------------------------- ; ; CS = CHIP SELECT (AKA DAT3 FOR NON-SPI MODE) ; CLK = CLOCK -; DI = DATA IN (HOST -> CARD, AKA CMD FOR NON-SPI MODE) -; DO = DATA OUT (HOST <- CARD, AKA DAT0 FOR NON-SPI MODE) +; DI = MOSI = DATA IN (HOST -> CARD, AKA CMD FOR NON-SPI MODE) +; DO = MISO = DATA OUT (HOST <- CARD, AKA DAT0 FOR NON-SPI MODE) ; ; NOTES: ; 1) SIGNAL NAMES ARE FROM THE SD CARD SPEC AND ARE NAMED FROM THE @@ -58,13 +58,13 @@ ; +---+---+---+---+---+---+---+---+ ; | 0 | X | X | X | X | X | X | X | ; +---+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -; | | | | | | | -; | | | | | | +--- IDLE -; | | | | | +------- ERASE RESET -; | | | | +----------- ILLEGAL COMMAND -; | | | +--------------- COM CRC ERROR -; | | +------------------- ERASE SEQUENCE ERROR -; | +----------------------- ADDRESS ERROR +; | | | | | | | +; | | | | | | +--- IDLE +; | | | | | +------- ERASE RESET +; | | | | +----------- ILLEGAL COMMAND +; | | | +--------------- COM CRC ERROR +; | | +------------------- ERASE SEQUENCE ERROR +; | +----------------------- ADDRESS ERROR ; +--------------------------- PARAMETER ERROR ; ; === DATA ERROR TOKEN === @@ -79,35 +79,80 @@ ; | +----------- CARD ECC FAILED - CARD INTERNAL ECC FAILED TO CORRECT DATA ; +--------------- OUT OF RANGE - PARAMAETER OUT OF RANGE ALLOWED FOR CARD ; +;------------------------------------------------------------------------------ +; +; *** HACK FOR MISSING PULLUP RESISTORS *** +; +; THERE IS A RECENT TREND FOR SD ADAPTER BOARDS (SUCH AS THOSE USED TO ATTACH AN +; SD CARD TO AN ARDUINO OR RASPBERRY PI) TO OMIT THE PULLUP RESISTORS THAT ARE SUPPOSED +; TO BE ON ALL LINES. DESPITE BEING A CLEAR VIOLATION OF THE SPEC, IT IS SOMETHING THAT +; WE WILL NOW NEED TO LIVE WITH. THE CLK, CS, AND MOSI SIGNALS ARE NOT AN ISSUE SINCE +; WE ARE DRIVING THOSE SIGNALS AS THE HOST. THE PROBLEM IS WITH THE MISO SIGNAL. +; FORTUNATELY, MOST OF THE TIME, THE SD CARD WILL BE DRIVING THE SIGNAL. HOWEVER, +; THERE ARE TWO SCEANRIOS WE NEED TO ACCOMMODATE IN THE CODE: +; +; 1. MISO WILL NOT BE DRIVEN BY THE SD CARD (FLOATING) PRIOR TO RESETING THE +; CARD WITH CMD0. NORMALLY, A COMMAND SEQUENCE INVOLVES WAITING FOR THE +; CARD TO BE "READY" BY READING BYTES FROM THE CARD AND LOOKING FOR $FF. +; WHEN MISO IS FLOATING THIS WILL NOT BE RELIABLE. SINCE THE SPEC INDICATES +; IT IS NOT NECESSARY TO WAIT FOR READY PRIOR TO CMD0, THE CODE HAS BEEN +; MODIFIED TO ISSUE CMD0 WITHOUT WAITING FOR READY. +; +; 2. MISO MAY NOT BE DRIVEN IMMEDIATELY AFTER SENDING A COMMAND (POSSIBLY +; JUST CMD0, BUT NOT SURE). NORMALLY, AFTER SENDING A COMMAND, YOU +; LOOK FOR "FILL" BYTES OF $FF THAT MAY OCCUR PRIOR TO THE RESULT. WHEN MISO +; IS FLOATING IT IS IMPOSSIBLE TO DETERMINE IF THE BYTE RECEIVED IS A FILL +; BYTE OR NOT. BASED ON WHAT I HAVE READ, THERE WILL ALWAYS BE AT LEAST +; ONE FILL BYTE PRIOR TO THE ACTUAL RESULT. ADDITIONALLY, THE SD CARD WILL +; START DRIVING MISO SOMETIME WITHING THAT FIRST FILL BYTE. SO, WE NOW +; JUST DISCARD THE FIRST BYTE RECEIVED AFTER A COMMAND IS SENT WITH THE +; ASSUMPTION THAT IT MUST BE A FILL BYTE AND IS NOT RELIABLE DUE TO FLOATING +; MISO. +; +; THESE CHANGES ARE CONSISTENT WITH THE POPULAR ARDUINO SDFAT LIBRARY, SO THEY ARE +; PROBABLY PRETTY SAFE. HOWEVER, I HAVE BRACKETED THE CHANGES WITH THE EQUATE BELOW. +; IF YOU WANT TO REVERT THESE HACKS, JUST SET THE EQUATE TO FALSE. +; +SD_NOPULLUP .EQU TRUE ; ASSUME NO PULLUP +; #IF (SDMODE == SDMODE_JUHA) ; JUHA MINI-BOARD SD_DEVCNT .EQU 1 ; NUMBER OF PHYSICAL UNITS (SOCKETS) -SD_OPRREG .EQU RTC ; USES RTC LATCHES FOR OPERATION -SD_OPRDEF .EQU %00000001 ; QUIESCENT STATE??? -SD_INPREG .EQU RTC ; INPUT REGISTER IS RTC -SD_CS .EQU %00000100 ; RTC:2 IS SELECT +SD_OPRREG .EQU RTCIO ; USES RTC LATCHES FOR OPERATION +SD_OPRDEF .EQU %00000001 ; QUIESCENT STATE +SD_OPRMSK .EQU %10000111 ; MASK FOR BITS WE OWN IN RTC LATCH PORT +SD_INPREG .EQU RTCIO ; INPUT REGISTER IS RTC +SD_CS0 .EQU %00000100 ; RTC:2 IS SELECT SD_CLK .EQU %00000010 ; RTC:1 IS CLOCK SD_DI .EQU %00000001 ; RTC:0 IS DATA IN (CARD <- CPU) SD_DO .EQU %10000000 ; RTC:7 IS DATA OUT (CARD -> CPU) +; +RTCDEF .SET SD_OPRDEF ; SET DEFAULT IN HBIOS MAINLINE #ENDIF ; #IF (SDMODE == SDMODE_N8) ; UNMODIFIED N8-2511 SD_DEVCNT .EQU 1 ; NUMBER OF PHYSICAL UNITS (SOCKETS) -SD_OPRREG .EQU RTC ; USES RTC LATCHES FOR OPERATION -SD_OPRDEF .EQU %00000001 ; QUIESCENT STATE??? -SD_INPREG .EQU RTC ; INPUT REGISTER IS RTC -SD_CS .EQU %00000100 ; RTC:2 IS SELECT +SD_OPRREG .EQU RTCIO ; USES RTC LATCHES FOR OPERATION +SD_OPRDEF .EQU %00000001 ; QUIESCENT STATE +SD_OPRMSK .EQU %01000111 ; MASK FOR BITS WE OWN IN RTC LATCH PORT +SD_INPREG .EQU RTCIO ; INPUT REGISTER IS RTC +SD_CS0 .EQU %00000100 ; RTC:2 IS SELECT SD_CLK .EQU %00000010 ; RTC:1 IS CLOCK SD_DI .EQU %00000001 ; RTC:0 IS DATA IN (CARD <- CPU) SD_DO .EQU %01000000 ; RTC:6 IS DATA OUT (CARD -> CPU) +; +RTCDEF .SET SD_OPRDEF ; SET DEFAULT IN HBIOS MAINLINE #ENDIF ; #IF (SDMODE == SDMODE_CSIO) ; N8-2312 SD_DEVCNT .EQU 1 ; NUMBER OF PHYSICAL UNITS (SOCKETS) -SD_OPRREG .EQU RTC ; USES RTC LATCHES FOR OPERATION +SD_OPRREG .EQU RTCIO ; USES RTC LATCHES FOR OPERATION SD_OPRDEF .EQU %00000000 ; QUIESCENT STATE -SD_CS .EQU %00000100 ; RTC:2 IS SELECT +SD_OPRMSK .EQU %00000100 ; MASK FOR BITS WE OWN IN RTC LATCH PORT +SD_CS0 .EQU %00000100 ; RTC:2 IS SELECT SD_CNTR .EQU Z180_CNTR SD_TRDR .EQU Z180_TRDR +; +RTCDEF .SET SD_OPRDEF ; SET DEFAULT IN HBIOS MAINLINE #ENDIF ; #IF (SDMODE == SDMODE_PPI) ; PPISD @@ -119,7 +164,7 @@ SD_PPIX .EQU PPIBASE + 3 ; PPI CONTROL PORT SD_OPRREG .EQU SD_PPIC ; PPI PORT C IS OPR REG SD_OPRDEF .EQU %00110001 ; CS HI, DI HI SD_INPREG .EQU SD_PPIB ; INPUT REGISTER IS PPI PORT B -SD_CS .EQU %00010000 ; PPIC:4 IS SELECT +SD_CS0 .EQU %00010000 ; PPIC:4 IS SELECT SD_CLK .EQU %00000010 ; PPIC:1 IS CLOCK SD_DI .EQU %00000001 ; PPIC:0 IS DATA IN (CARD <- CPU) SD_DO .EQU %10000000 ; PPIB:7 IS DATA OUT (CARD -> CPU) @@ -129,21 +174,22 @@ SD_DO .EQU %10000000 ; PPIB:7 IS DATA OUT (CARD -> CPU) SD_DEVCNT .EQU 1 ; NUMBER OF PHYSICAL UNITS (SOCKETS) SD_OPRREG .EQU SIO_MCR ; UART MCR PORT (OUTPUT: CS, CLK, DIN) SD_OPRDEF .EQU %00001100 ; QUIESCENT STATE +SD_OPRMSK .EQU %00101101 ; MASK FOR BITS WE OWN IN RTC LATCH PORT SD_INPREG .EQU SIO_MSR ; INPUT REGISTER IS MSR -SD_CS .EQU %00001000 ; UART MCR:3 IS SELECT +SD_CS0 .EQU %00001000 ; UART MCR:3 IS SELECT SD_CLK .EQU %00000100 ; UART MCR:2 IS CLOCK SD_DI .EQU %00000001 ; UART MCR:0 IS DATA IN (CARD <- CPU) SD_DO .EQU %00100000 ; UART MSR:5 IS DATA OUT (CARD -> CPU) #ENDIF ; #IF (SDMODE == SDMODE_DSD) ; DUAL SD -SD_DEVCNT .EQU 2 ; NUMBER OF PHYSICAL UNITS (SOCKETS) +SD_DEVCNT .EQU SDCNT ; NUMBER OF PHYSICAL UNITS (SOCKETS) SD_OPRREG .EQU $08 ; DEDICATED OPERATIONS REGISTER SD_OPRDEF .EQU %00000001 ; QUIESCENT STATE SD_INPREG .EQU SD_OPRREG ; INPUT REGISTER IS OPRREG SD_SELREG .EQU SD_OPRREG + 1 ; DEDICATED SELECTION REGISTER SD_SELDEF .EQU %00000000 ; SELECTION REGISTER DEFAULT -SD_CS .EQU %00000100 ; RTC:2 IS SELECT +SD_CS0 .EQU %00000100 ; RTC:2 IS SELECT SD_CLK .EQU %00000010 ; RTC:1 IS CLOCK SD_DI .EQU %00000001 ; RTC:6 IS DATA IN (CARD <- CPU) SD_DO .EQU %00000001 ; RTC:0 IS DATA OUT (CARD -> CPU) @@ -151,11 +197,46 @@ SD_DO .EQU %00000001 ; RTC:0 IS DATA OUT (CARD -> CPU) ; #IF (SDMODE == SDMODE_MK4) ; MARK IV (CSIO STYLE INTERFACE) SD_DEVCNT .EQU 1 ; NUMBER OF PHYSICAL UNITS (SOCKETS) -SD_OPRREG .EQU MK4_SD ; DEDICATED MK4 SDCARD REGISTER +SD_OPRREG .EQU $89 ; DEDICATED MK4 SDCARD REGISTER SD_OPRDEF .EQU %00000000 ; QUIESCENT STATE -SD_CS .EQU %00000100 ; SELECT ACTIVE +SD_CS0 .EQU %00000100 ; SELECT ACTIVE +SD_CNTR .EQU Z180_CNTR +SD_TRDR .EQU Z180_TRDR +#ENDIF +; +#IF (SDMODE == SDMODE_SC) ; SC +SD_DEVCNT .EQU SDCNT ; NUMBER OF PHYSICAL UNITS (SOCKETS) +SD_OPRREG .EQU RTCIO ; USES RTC LATCHES FOR OPERATION +SD_OPRDEF .EQU %00001100 ; QUIESCENT STATE (/CS1 & /CS2 DEASSERTED) +SD_OPRMSK .EQU %00001100 ; MASK FOR BITS WE OWN IN RTC LATCH PORT +SD_CS0 .EQU %00000100 ; RTC:2 IS SELECT FOR PRIMARY SPI CARD +SD_CS1 .EQU %00001000 ; RTC:3 IS SELECT FOR SECONDARY SPI CARD SD_CNTR .EQU Z180_CNTR SD_TRDR .EQU Z180_TRDR +; +RTCDEF .SET SD_OPRDEF ; SET DEFAULT IN HBIOS MAINLINE +#ENDIF +; +#IF (SDMODE == SDMODE_MT) ; MT shift register for RC2014 (ref SDMODE_CSIO) +; +; 3 SPI CHANNELS. CHANNEL 0 (CDX & CSX) IS A DEDICATED CONNECTION TO ONBOARD +; WIZNET W5500 AND IS NOT USED HERE. CHANNEL 1 (CD0 & CS0) & 2 (CD1 & CS1) +; ARE ASSUMED TO BE CONNECTED TO SD CARDS. +; +SD_BASE .EQU $5C ; Module base address +SD_DEVCNT .EQU 2 ; NUMBER OF PHYSICAL UNITS (SOCKETS) +SD_WRTR .EQU SD_BASE + 0 ; Write data and transfer +SD_RDTR .EQU SD_BASE + 1 ; Read data and transfer +SD_RDNTR .EQU SD_BASE + 0 ; Read data and NO transfer +SD_OPRREG .EQU SD_BASE + 2 ; SD CHIP SELECTOR +SD_OPRDEF .EQU %00000000 ; QUIESCENT STATE +SD_CDX .EQU %00000001 ; IN/OUT:SD_OPREG:0 = CD0, PMOD pull CD0 low +SD_CD0 .EQU %00000010 ; IN:SD_OPREG:1 = CD1, IN=0 Card detect switch +SD_CD1 .EQU %00000100 ; IN:SD_OPREG:2 = CD2, IN=0 Card detect switch +SD_CSX .EQU %00001000 ; IN/OUT:SD_OPREG:3 = CS0, PMOD SPI CS +SD_CS0 .EQU %00010000 ; IN/OUT:SD_OPREG:4 = CS1, SDCARD1 CS, IN=1 Card present +SD_CS1 .EQU %00100000 ; IN/OUT:SD_OPREG:5 = CS2, SDCARD2 CS, IN=1 Card present + #ENDIF ; ; SD CARD COMMANDS @@ -189,7 +270,7 @@ SD_TYPESDXC .EQU 4 ; SDXC CARD (V3) SD_STOK .EQU 0 ; OK SD_STINVUNIT .EQU -1 ; INVALID UNIT SD_STRDYTO .EQU -2 ; TIMEOUT WAITING FOR CARD TO BE READY -SD_STINITTO .EQU -3 ; INITIALIZATOIN TIMEOUT +SD_STINITTO .EQU -3 ; INITIALIZATION TIMEOUT SD_STCMDTO .EQU -4 ; TIMEOUT WAITING FOR COMMAND RESPONSE SD_STCMDERR .EQU -5 ; COMMAND ERROR OCCURRED (REF SD_RC) SD_STDATAERR .EQU -6 ; DATA ERROR OCCURRED (REF SD_TOK) @@ -248,6 +329,11 @@ SD_INIT: PRTS(" IO=0x$") LD A,SD_OPRREG CALL PRTHEXBYTE +; + LD A,(RTCVAL) ; GET RTC PORT SHADOW VALUE + AND ~SD_OPRMSK ; CLEAR OUR BITS + OR SD_OPRDEF ; SET OUR BIT DEFAULTS + LD (RTCVAL),A ; SAVE IT #ENDIF ; #IF (SDMODE == SDMODE_N8) @@ -255,6 +341,11 @@ SD_INIT: PRTS(" IO=0x$") LD A,SD_OPRREG CALL PRTHEXBYTE +; + LD A,(RTCVAL) ; GET RTC PORT SHADOW VALUE + AND ~SD_OPRMSK ; CLEAR OUR BITS + OR SD_OPRDEF ; SET OUR BIT DEFAULTS + LD (RTCVAL),A ; SAVE IT #ENDIF ; #IF (SDMODE == SDMODE_CSIO) @@ -271,6 +362,11 @@ SD_INIT: PRTS(" TRDR=0x$") LD A,SD_TRDR CALL PRTHEXBYTE +; + LD A,(RTCVAL) ; GET RTC PORT SHADOW VALUE + AND ~SD_OPRMSK ; CLEAR OUR BITS + OR SD_OPRDEF ; SET OUR BIT DEFAULTS + LD (RTCVAL),A ; SAVE IT #ENDIF ; #IF (SDMODE == SDMODE_PPI) @@ -315,6 +411,34 @@ SD_INIT: LD A,SD_TRDR CALL PRTHEXBYTE #ENDIF +; +#IF (SDMODE == SDMODE_SC) + PRTS(" MODE=SC$") + #IF (SDCSIOFAST) + PRTS(" FAST$") + #ENDIF + PRTS(" OPR=0x$") + LD A,SD_OPRREG + CALL PRTHEXBYTE + PRTS(" CNTR=0x$") + LD A,SD_CNTR + CALL PRTHEXBYTE + PRTS(" TRDR=0x$") + LD A,SD_TRDR + CALL PRTHEXBYTE +; + LD A,(RTCVAL) ; GET RTC PORT SHADOW VALUE + AND ~SD_OPRMSK ; CLEAR OUR BITS + OR SD_OPRDEF ; SET OUR BIT DEFAULTS + LD (RTCVAL),A ; SAVE IT +#ENDIF +; +#IF (SDMODE == SDMODE_MT) + PRTS(" MODE=MT$") + PRTS(" IO=0x$") + LD A,SD_BASE + CALL PRTHEXBYTE +#ENDIF ; CALL SD_PROBE ; CHECK FOR HARDWARE JR Z,SD_INIT00 ; CONTINUE IF PRESENT @@ -446,6 +570,7 @@ SD_INITUNIT2: RET Z ; IF NOT, DONE PRTS(" WP$") ; NOTIFY USER ; + XOR A ; SIGNAL SUCCESS RET ; DONE ; ;---------------------------------------------------------------------- @@ -475,8 +600,52 @@ SD_PROBE: CP $01 ; SHOULD READ BACK AS $01 RET ; RETURN W/ ZF SET AS NEEDED #ENDIF +; +;#IF (SDMODE == SDMODE_MT) +; LD A,SD_OPRDEF +; OUT (SD_OPRREG),A ; MAKE SURE CONTROL REGISTER IS CLEARED +;; +; ; TEST WITH PMOD NOT CONNECTED +;; IN A,(SD_OPRREG) +;; AND SD_CD0+SD_CS0 ; ISOLATE CD0 AND CS0 +;; CP SD_CD0+SD_CS0 ; BOTH SHOULD BE HIGH +;; JR NZ,SD_PROBE_FAIL ; FAIL IF NOT +; ; TEST CD0 +;; LD A,SD_CD0 ; D1=DNP CANNOT TEST +;; OUT (SD_OPRREG),A +;; IN A,(SD_OPRREG) +;; AND SD_CD0 +;; JR NZ,SD_PROBE_FAIL ; FAIL IF NOT PULLED LOW +; ; TEST CS0 +;; LD A,SD_CS0 ; D2=DNP CANNOT TEST +;; OUT (SD_OPRREG),A +;; IN A,(SD_OPRREG) +;; AND SD_CS0 +;; JR NZ,SD_PROBE_FAIL ; FAIL IF NOT PULLED LOW +; ; TEST CS1 +;; LD A,SD_CS1 +;; OUT (SD_OPRREG),A +;; IN A,(SD_OPRREG) +;; AND SD_CS1 +;; JR NZ,SD_PROBE_FAIL ; FAIL IF NOT PULLED LOW +;; ; TEST CS2 +;; LD A,SD_CS2 +;; OUT (SD_OPRREG),A +;; IN A,(SD_OPRREG) +;; AND SD_CS2 +;; JR NZ,SD_PROBE_FAIL ; FAIL IF NOT PULLED LOW +; +; LD A,SD_OPRDEF +; OUT (SD_OPRREG),A ; MAKE SURE CONTROL REGISTER IS CLEARED +;#ENDIF ; XOR A ; SIGNAL SUCCESS +; +;#IF (SDMODE == SDMODE_MT) +;SD_PROBE_FAIL: +; LD A,SD_OPRDEF +; OUT (SD_OPRREG),A ; MAKE SURE CONTROL REGISTER IS CLEARED +;#ENDIF RET ; AND RETURN ; ;============================================================================= @@ -508,12 +677,14 @@ SD_DEFMED: ; ; SD_READ: + CALL HB_DSKREAD ; HOOK HBIOS DISK READ SUPERVISOR LD A,SD_CMD_READ_SNGL_BLK ; SETUP FOR SINGLE BLOCK READ CMD JR SD_IO ; CONTINUE TO GENERIC IO ROUTINE ; ; ; SD_WRITE: + CALL HB_DSKWRITE ; HOOK HBIOS DISK WRITE SUPERVISOR LD A,SD_CMD_WRITE_BLOCK ; SETUP FOR BLOCK WRITE CMD JR SD_IO ; CONTINUE TO GENERIC IO ROUTINE ; @@ -582,7 +753,7 @@ SD_RESET: CALL SD_SELUNIT ; SET CUR UNIT ; RE-INITIALIZE THE SD CARD TO ACCOMMODATE HOT SWAPPING CALL SD_INITCARD ; RE-INIT SELECTED UNIT -#IF (SDTRACE == 1) +#IF (SDTRACE >= 3) CALL SD_PRTERR ; PRINT ANY ERRORS #ENDIF OR A ; SET RESULT FLAGS @@ -679,12 +850,6 @@ SD_GEOM: ; (RE)INITIALIZE CARD ; SD_INITCARD: -; - ;; CLEAR OUT UNIT SPECIFIC DATA - ;SD_DPTR(0) ; SET HL TO START OF UNIT DATA - ;LD BC,SD_UNITDATALEN - ;XOR A - ;CALL FILL ; CALL SD_CHKCD ; CHECK CARD DETECT JP Z,SD_NOMEDIA ; Z=NO MEDIA, HANDLE IF SO @@ -697,10 +862,16 @@ SD_INITCARD1: CALL SD_PUT ; SEND 8 CLOCKS POP BC ; RESTORE LOOP CONTROL DJNZ SD_INITCARD1 ; LOOP AS NEEDED +; + ; MAKE SURE WE FINISH SENDING +#IF ((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4) | (SDMODE == SDMODE_SC)) + CALL SD_WAITTX ; WAIT FOR TE TO CLEAR + CALL DLY4 ; WAIT A BIT MORE FOR FINAL BIT +#ENDIF ; ; PUT CARD IN IDLE STATE CALL SD_GOIDLE ; GO TO IDLE - RET NZ ; ABORT IF FAILED + JP NZ,SD_NOMEDIA ; CONVERT ERROR TO NO MEDIA ; SD_INITCARD2: LD (IY+SD_TYPE),SD_TYPESDSC ; ASSUME SDSC CARD TYPE @@ -727,6 +898,9 @@ SD_INITCARD3: CALL VDELAY ; CPU SPEED NORMALIZED DELAY ; SEND APP CMD INTRODUCER CALL SD_EXECACMD ; SEND APP COMMAND INTRODUCER +;#IF (SDMODE == SDMODE_MT) +; CALL NZ,SD_EXECACMD ; retry any fail +;#ENDIF CP SD_STCMDERR ; COMMAND ERROR? JR Z,SD_INITCARD3A ; IF SO, TRY MMC CARD INIT OR A ; SET FLAGS @@ -847,7 +1021,7 @@ SD_INITCARD5: CALL SD_EXECCMDND ; EXEC COMMAND W/ NO DATA RET NZ ; ABORT ON ERROR -#IF ((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4)) +#IF ((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4) | (SDMODE == SDMODE_SC)) ; PER SPEC, THE CARD SHOULD NOW BE ABLE TO HANDLE FULL SPEED OPERATION ; SO, FOR CSIO OPERATION, WE SET CSIO TO MAXIMUM SPEED CALL SD_WAITTX ; MAKE SURE WE ARE DONE SENDING @@ -1081,9 +1255,8 @@ SD_GOIDLE1: RET NZ ; ABORT ON ERROR LD A,(SD_RC) ; GET CARD RESULT DEC A ; MAP EXPECTED $01 -> $00 - RET Z ; ALL IS GOOD, RETURN WITH Z=0 AND Z SET - LD A,SD_STCMDERR ; SET COMMAND ERROR VALUE, NZ ALREADY SET - RET ; AND RETURN + RET Z ; ALL IS GOOD, RETURN WITH A=0 AND Z SET + JP SD_ERRCMD ; SET COMMAND ERROR VALUE ; ; INITIALIZE COMMAND BUFFER ; COMMAND BYTE IN ACCUM @@ -1130,10 +1303,23 @@ SD_EXECCMD: CALL WRITESTR POP AF #ENDIF - +; + CALL SD_SELECT +; +#IF (SD_NOPULLUP) + ; DO NOT WAIT FOR READY PRIOR TO CMD0! THIS HACK IS REQUIRED BY + ; STUPID SD CARD ADAPTERS THAT NOW OMIT THE MISO PULL-UP. SEE + ; COMMENTS AT TOP OF THIS FILE. + LD A,(SD_CMDBUF) + CP SD_CMD_GO_IDLE_STATE + JR Z,SD_EXECCMD0 +#ENDIF +; ; WAIT FOR CARD TO BE READY CALL SD_WAITRDY ; WAIT FOR CARD TO BE READY FOR A COMMAND JP NZ,SD_ERRRDYTO ; HANDLE TIMEOUT ERROR +; +SD_EXECCMD0: ; SEND THE COMMAND LD HL,SD_CMDBUF ; POINT TO COMMAND BUFFER LD E,6 ; COMMANDS ARE 6 BYTES @@ -1143,6 +1329,13 @@ SD_EXECCMD1: INC HL ; POINT TO NEXT BYTE DEC E ; DEC LOOP COUNTER JR NZ,SD_EXECCMD1 ; LOOP TILL DONE W/ ALL 6 BYTES +; +#IF (SD_NOPULLUP) + ; THE FIRST FILL BYTE IS DISCARDED! THIS HACK IS REQUIRED BY + ; STUPID SD CARD ADAPTERS THAT NOW OMIT THE MISO PULL-UP. SEE + ; COMMENTS AT TOP OF THIS FILE. + CALL SD_GET ; GET A BYTE AND DISCARD IT +#ENDIF ; ; GET RESULT LD E,0 ; INIT TIMEOUT LOOP COUNTER @@ -1173,6 +1366,44 @@ SD_EXECCMD3: ; SD_GETDATA ; SD_GETDATA: +#IF (SDMODE == SDMODE_MT) + LD DE,$7FFF ; LOOP MAX (TIMEOUT) +SD_GETDATA1: + IN A,(SD_RDTR) + + CP $FF ; WANT BYTE != $FF + JR NZ,SD_GETDATA2 ; NOT $FF, MOVE ON + DEC DE + BIT 7,D + JR Z,SD_GETDATA1 ; KEEP TRYING UNTIL TIMEOUT +SD_GETDATA2: + LD (SD_TOK),A ; SAVE TOKEN VALUE + #IF (SDTRACE >= 3) + PUSH AF + CALL SD_PRTTOK + POP AF + #ENDIF + + CP $FE ; PACKET START? + JR NZ,SD_GETDATA4 ; NOPE, ABORT, A HAS ERROR CODE + + LD D,B ; SIZE TO DB + LD B,C + LD C,(SD_RDTR) + + INC B + DEC B + JR Z,SD_GETDATA3 + INC D +SD_GETDATA3: + INIR ; GET BLOCK + + DEC D + JR NZ,SD_GETDATA3 ; LOOP FOR ALL BYTES + + IN A,(SD_RDTR) ; DISCARD CRC BYTE 1 + IN A,(SD_RDTR) ; DISCARD CRC BYTE 2 +#ELSE PUSH HL ; SAVE DESTINATION ADDRESS PUSH BC ; SAVE LENGTH TO RECEIVE LD DE,$7FFF ; LOOP MAX (TIMEOUT) @@ -1185,11 +1416,11 @@ SD_GETDATA1: JR Z,SD_GETDATA1 ; KEEP TRYING UNTIL TIMEOUT SD_GETDATA2: LD (SD_TOK),A ; SAVE TOKEN VALUE -#IF (SDTRACE >= 3) + #IF (SDTRACE >= 3) PUSH AF CALL SD_PRTTOK POP AF -#ENDIF + #ENDIF POP DE ; RESTORE LENGTH TO RECEIVE POP HL ; RECOVER DEST ADDRESS CP $FE ; PACKET START? @@ -1204,6 +1435,7 @@ SD_GETDATA3: JR NZ,SD_GETDATA3 ; LOOP FOR ALL BYTES CALL SD_GET ; DISCARD CRC BYTE 1 CALL SD_GET ; DISCARD CRC BYTE 2 +#ENDIF XOR A ; RESULT IS ZERO SD_GETDATA4: RET @@ -1211,6 +1443,32 @@ SD_GETDATA4: ; SD_PUTDATA ; SD_PUTDATA: +#IF (SDMODE == SDMODE_MT) + LD D,B ; length to DB + LD B,C + LD A,$FE ; PACKET START + OUT (SD_WRTR),A ; SEND IT + + LD C,SD_WRTR + + INC B ; IF B!=0 + DEC B + JR Z,SD_PUTDATA1 + INC D ; THEN D=D+1 +SD_PUTDATA1: + OTIR ; SEND B BYTES + + DEC D + JR NZ,SD_PUTDATA1 ; LOOP FOR ALL BYTES + + LD A,$FF ; DUMMY CRC BYTE + OUT (SD_WRTR),A + OUT (SD_WRTR),A ; SEND IT TWICE + + LD DE,$7FFF ; LOOP MAX (TIMEOUT) +SD_PUTDATA2: + IN A,(SD_RDTR) +#ELSE PUSH HL ; SAVE SOURCE ADDRESS PUSH BC ; SAVE LENGTH TO SEND @@ -1234,28 +1492,28 @@ SD_PUTDATA1: LD DE,$7FFF ; LOOP MAX (TIMEOUT) SD_PUTDATA2: CALL SD_GET +#ENDIF CP $FF ; WANT BYTE != $FF JR NZ,SD_PUTDATA3 ; NOT $FF, MOVE ON - DEC DE + DEC DE BIT 7,D JR Z,SD_PUTDATA2 ; KEEP TRYING UNTIL TIMEOUT SD_PUTDATA3: LD (SD_TOK),A -#IF (SDTRACE >= 3) + #IF (SDTRACE >= 3) PUSH AF CALL SD_PRTTOK POP AF -#ENDIF + #ENDIF AND $1F CP $05 RET NZ XOR A RET ; -; SELECT CARD AND WAIT FOR IT TO BE READY ($FF) +; WAIT FOR CARD TO BE READY ($FF). MUST ALREADY BE SELECTED. ; SD_WAITRDY: - CALL SD_SELECT ; SELECT CARD LD DE,$FFFF ; LOOP MAX (TIMEOUT) SD_WAITRDY1: CALL SD_GET @@ -1277,7 +1535,7 @@ SD_WAITRDY1: ; IRRELEVANT. IT CAN BE ASSERTED OR DE-ASSERTED. ; ; NOTE THAT I HAVE FOUND AT LEAST ONE MMC CARD THAT FAILS UNLESS THE CS SIGNAL -; REMAINS ACTIVE DURING THE 8 CLOCKS, SO THE CLOCKS ARE SENT BEFORE DESLECTING THE CARD. +; REMAINS ACTIVE DURING THE 8 CLOCKS, SO THE CLOCKS ARE SENT BEFORE DESELECTING THE CARD. ; SD_DONE: PUSH AF @@ -1295,34 +1553,33 @@ SD_DONE: ; SD_SETUP: ; -#IF ((SDMODE == SDMODE_JUHA) | (SDMODE == SDMODE_N8) | (SDMODE == SDMODE_DSD)) - LD A,SD_OPRDEF - LD (SD_OPRVAL),A - OUT (SD_OPRREG),A +#IF (SDMODE == SDMODE_PPI) + ; PPISD IS DESIGNED TO CORESIDE ON THE SAME PARALLEL PORT + ; AS A DSKY. SEE DSKY.ASM FOR DETAILS. + LD A,82H ; PPI PORT A=OUT, B=IN, C=OUT + OUT (SD_PPIX),A #ENDIF ; -#IF ((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4)) - ; CSIO SETUP -; LD A,2 ; 18MHz/20 <= 400kHz - LD A,6 ; ??? +#IF ((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4) | (SDMODE == SDMODE_SC)) + ; CSIO SETUP FOR Z180 CSIO +; LD A,2 ; DIV 80, 225KHZ @ 18MHZ CLK + LD A,6 ; DIV 1280, 14KHZ @ 18MHZ CLK OUT0 (SD_CNTR),A - LD A,SD_OPRDEF +#ENDIF +; +#IF ((SDMODE == SDMODE_JUHA) | (SDMODE == SDMODE_N8) | (SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_SC)) + LD A,(RTCVAL) LD (SD_OPRVAL),A OUT (SD_OPRREG),A #ENDIF ; -#IF (SDMODE == SDMODE_PPI) - ; PPISD IS DESIGNED TO CORESIDE ON THE SAME PARALLEL PORT - ; AS A DSKY. SEE DSKY.ASM FOR DETAILS. - LD A,82H ; PPI PORT A=OUT, B=IN, C=OUT - OUT (SD_PPIX),A +#IF ((SDMODE == SDMODE_MK4) | (SDMODE == SDMODE_DSD) | (SDMODE == SDMODE_PPI)| (SDMODE == SDMODE_MT)) LD A,SD_OPRDEF LD (SD_OPRVAL),A OUT (SD_OPRREG),A #ENDIF ; #IF (SDMODE == SDMODE_UART) -SD_OPRMSK .EQU (SD_CS | SD_CLK | SD_DI) IN A,(SD_OPRREG) ; OPRREG == SIO_MCR AND ~SD_OPRMSK ; MASK OFF SD CONTROL BITS OR SD_OPRDEF ; SET DEFAULT BITS @@ -1371,11 +1628,35 @@ SD_CHKWP: ; SELECT CARD ; SD_SELECT: - LD A,(SD_OPRVAL) -#IF ((SDMODE == SDMODE_PPI) | (SDMODE == SDMODE_UART)) - AND ~SD_CS ; SET SD_CS (CHIP SELECT) -#ELSE - OR SD_CS ; SET SD_CS (CHIP SELECT) +; ; FINISH SENDING BEFORE ASSERTING CS! +;#IF ((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4) | (SDMODE == SDMODE_SC)) +; CALL SD_WAITTX +;#ENDIF +; + LD A,(IY+SD_DEV) ; GET CURRENT DEVICE + OR A ; SET FLAGS + LD A,(SD_OPRVAL) ; GET CURRENT OPRVAL BACK + JR NZ,SD_SELECT1 ; IF NOT ZERO, DO SECONDARY + ; ASSERT PRIMARY CS, DEASSERT SECONDARY (IF ANY) + OR SD_CS0 +#IF (SD_DEVCNT > 1) + AND ~SD_CS1 +#ENDIF + JR SD_SELECT2 +SD_SELECT1: + ; DEASSERT PRIMARY CS, ASSERT SECONDARY (IF ANY) + AND ~SD_CS0 +#IF (SD_DEVCNT > 1) + OR SD_CS1 +#ENDIF +SD_SELECT2: +; ADJUST BIT(S) FOR INTERFACES USING INVERTED CS BITS +#IF ((SDMODE == SDMODE_PPI) | (SDMODE == SDMODE_UART) | (SDMODE == SDMODE_SC)) + #IF (SD_DEVCNT > 1) + XOR SD_CS0 | SD_CS1 + #ELSE + XOR SD_CS0 + #ENDIF #ENDIF LD (SD_OPRVAL),A OUT (SD_OPRREG),A @@ -1384,17 +1665,41 @@ SD_SELECT: ; DESELECT CARD ; SD_DESELECT: +#IF ((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4) | (SDMODE == SDMODE_SC)) + ; DON'T REMOVE CS UNTIL WE ARE DONE SENDING! + CALL SD_WAITTX ; WAIT FOR TE TO CLEAR +; + ; ACCORDING TO Z180 DOCS, IT MAY TAKE UP TO 1 BIT TIME TO + ; FINISH SENDING AFTER TE IS CLEARED. THE DELAY BELOW WILL + ; DO THIS FOR THE SLOWEST POSSIBLE SEND RATE WHICH IS + ; CLK / 1320, SO DELAY AT LEAST 1320 T-STATES + ;CALL DLY64 ; DELAY FOR FINAL BIT +; + ; IN PRACTICE, IT LOOKS LIKE THIS WORST CASE SCENARIO NEVER + ; OCCURS. FOR NOW, USE A SMALL DELAY WHICH SEEMS TO BE MORE + ; THAN ADEQUATE BASED ON LOGIC ANALYZER TRACES. + CALL DLY4 ; DELAY FOR FINAL BIT +#ENDIF +; LD A,(SD_OPRVAL) -#IF ((SDMODE == SDMODE_PPI) | (SDMODE == SDMODE_UART)) - OR SD_CS ; RESET SD_CS (CHIP SELECT) +#IF (SD_DEVCNT > 1) + AND ~(SD_CS0 | SD_CS1) #ELSE - AND ~SD_CS ; RESET SD_CS (CHIP SELECT) + AND ~SD_CS0 +#ENDIF +; ADJUST BIT(S) FOR INTERFACES USING INVERTED CS BITS +#IF ((SDMODE == SDMODE_PPI) | (SDMODE == SDMODE_UART) | (SDMODE == SDMODE_SC)) + #IF (SD_DEVCNT > 1) + XOR SD_CS0 | SD_CS1 + #ELSE + XOR SD_CS0 + #ENDIF #ENDIF LD (SD_OPRVAL),A OUT (SD_OPRREG),A RET ; -#IF ((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4)) +#IF ((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4) | (SDMODE == SDMODE_SC)) ; ; CSIO WAIT FOR TRANSMIT READY (TX REGSITER EMPTY) ; @@ -1417,17 +1722,22 @@ SD_WAITRX: ; SEND ONE BYTE ; SD_PUT: -#IF ((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4)) +; +#IF (SDMODE == SDMODE_MT) + OUT (SD_WRTR),A +#ELSE +; + #IF ((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4) | (SDMODE == SDMODE_SC)) CALL MIRROR ; MSB<-->LSB MIRROR BITS, RESULT IN C CALL SD_WAITTX ; MAKE SURE WE ARE DONE SENDING OUT0 (SD_TRDR),C ; PUT BYTE IN BUFFER IN0 A,(SD_CNTR) SET 4,A ; SET TRANSMIT ENABLE OUT0 (SD_CNTR),A -#ELSE -#IF (SDMODE == SDMODE_UART) + #ELSE + #IF (SDMODE == SDMODE_UART) XOR $FF ; DI IS INVERTED ON UART -#ENDIF + #ENDIF LD C,A ; C=BYTE TO SEND LD B,8 ; SEND 8 BITS (LOOP 8 TIMES) LD A,(SD_OPRVAL) ; LOAD CURRENT OPR VALUE @@ -1443,13 +1753,19 @@ SD_PUT1: DJNZ SD_PUT1 ; REPEAT FOR ALL 8 BITS LD A,(SD_OPRVAL) ; LOAD CURRENT OPR VALUE OUT (SD_OPRREG),A ; LEAVE WITH CLOCK LOW + #ENDIF #ENDIF RET ; DONE ; ; RECEIVE ONE BYTE ; +; SD_GET: -#IF ((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4)) +; +#IF (SDMODE == SDMODE_MT) + IN A,(SD_RDTR) +#ELSE + #IF ((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4) | (SDMODE == SDMODE_SC)) CALL SD_WAITTX ; MAKE SURE WE ARE DONE SENDING IN0 A,(Z180_CNTR) ; GET CSIO STATUS SET 5,A ; START RECEIVER @@ -1458,35 +1774,36 @@ SD_GET: IN0 A,(Z180_TRDR) ; GET RECEIVED BYTE CALL MIRROR ; MSB<-->LSB MIRROR BITS LD A,C ; KEEP RESULT -#ELSE + #ELSE LD B,8 ; RECEIVE 8 BITS (LOOP 8 TIMES) LD A,(SD_OPRVAL) ; LOAD CURRENT OPR VALUE SD_GET1: XOR SD_CLK ; TOGGLE CLOCK OUT (SD_OPRREG),A ; UPDATE CLOCK IN A,(SD_INPREG) ; READ THE DATA WHILE CLOCK IS ACTIVE - #IF ((SDMODE == SDMODE_JUHA) | (SDMODE == SDMODE_PPI)) + #IF ((SDMODE == SDMODE_JUHA) | (SDMODE == SDMODE_PPI)) RLA ; ROTATE INP:7 INTO CF - #ENDIF - #IF (SDMODE == SDMODE_N8) + #ENDIF + #IF (SDMODE == SDMODE_N8) RLA ; ROTATE INP:6 INTO CF RLA ; " - #ENDIF - #IF (SDMODE == SDMODE_UART) + #ENDIF + #IF (SDMODE == SDMODE_UART) RLA ; ROTATE INP:5 INTO CF RLA ; " RLA ; " - #ENDIF - #IF (SDMODE == SDMODE_DSD) + #ENDIF + #IF (SDMODE == SDMODE_DSD) RRA ; ROTATE INP:0 INTO CF - #ENDIF + #ENDIF RL C ; ROTATE CF INTO C:0 LD A,(SD_OPRVAL) ; BACK TO INITIAL VALUES (TOGGLE CLOCK) OUT (SD_OPRREG),A ; DO IT DJNZ SD_GET1 ; REPEAT FOR ALL 8 BITS LD A,C ; GET BYTE RECEIVED INTO A - #IF (SDMODE == SDMODE_UART) + #IF (SDMODE == SDMODE_UART) XOR $FF ; DO IS INVERTED ON UART + #ENDIF #ENDIF #ENDIF RET @@ -1544,6 +1861,9 @@ SD_ERR2: CALL SD_PRTSTAT CALL SD_REGDUMP #ENDIF + PUSH AF + CALL SD_DESELECT ; De-select if there was an error + POP AF OR A ; SET FLAGS RET ; @@ -1748,7 +2068,8 @@ SD_DSKBUF .DW 0 ; ADR OF ACTIVE DISK BUFFER ; MSB<-->LSB MIRROR BITS IN A, RESULT IN C ; MIRROR: -#IF (((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4)) & SDCSIOFAST) +#IF (((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4) | (SDMODE == SDMODE_SC)) & SDCSIOFAST) + ; FASTEST BUT USES MOST CODE SPACE LD BC,MIRTAB ; 256 BYTE MIRROR TABLE ADD A,C ; ADD OFFSET LD C,A @@ -1758,18 +2079,29 @@ MIRROR2: LD A,(BC) ; GET RESULT LD C,A ; RETURN RESULT IN C RET -#ELSE ; FASTEST BUT USES MOST CODE SPACE - LD B,8 ; BIT COUNTER -MIRROR1: - RLA ; ROTATE BIT 7 INTO CARRY - RR C ; ROTATE CARRY INTO RESULT - DJNZ MIRROR1 ; DO ALL 8 BITS +#ELSE + ; SLOWER BUT LESS CODE SPACE + LD C,A ; A = 76543210 + RLCA + RLCA ; A = 54321076 + XOR C + AND 0AAH + XOR C ; A = 56341270 + LD C,A + RLCA + RLCA + RLCA ; A = 41270563 + RRC C ; C = 05634127 + XOR C + AND 066H + XOR C ; A = 01234567 + LD C,A ; RETURN RESULT IN C RET #ENDIF ; ; LOOKUP TABLE TO MIRROR BITS IN A BYTE ; -#IF (((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4)) & SDCSIOFAST) +#IF (((SDMODE == SDMODE_CSIO) | (SDMODE == SDMODE_MK4) | (SDMODE == SDMODE_SC)) & SDCSIOFAST) MIRTAB .DB 00H, 80H, 40H, 0C0H, 20H, 0A0H, 60H, 0E0H, 10H, 90H, 50H, 0D0H, 30H, 0B0H, 70H, 0F0H .DB 08H, 88H, 48H, 0C8H, 28H, 0A8H, 68H, 0E8H, 18H, 98H, 58H, 0D8H, 38H, 0B8H, 78H, 0F8H diff --git a/Source/HBIOS/simrtc.asm b/Source/HBIOS/simrtc.asm index 2962f098..8be545c3 100644 --- a/Source/HBIOS/simrtc.asm +++ b/Source/HBIOS/simrtc.asm @@ -11,6 +11,10 @@ SIMRTC_BUFSIZ .EQU 6 ; SIX BYTE BUFFER (YYMMDDHHMMSS) ; RTC DEVICE INITIALIZATION ENTRY ; SIMRTC_INIT: + LD A,(RTC_DISPACT) ; RTC DISPATCHER ALREADY SET? + OR A ; SET FLAGS + RET NZ ; IF ALREADY ACTIVE, ABORT +; CALL NEWLINE ; FORMATTING PRTS("SIMRTC: $") ; @@ -20,6 +24,9 @@ SIMRTC_INIT: CALL SIMRTC_GETTIM0 POP HL CALL PRTDT +; + LD BC,SIMRTC_DISPATCH + CALL RTC_SETDISP ; XOR A ; SIGNAL SUCCESS RET @@ -71,13 +78,7 @@ SIMRTC_GETTIM: LD HL,SIMRTC_BUF ; SOURCE ADR POP DE ; DEST ADR LD BC,SIMRTC_BUFSIZ ; LENGTH -#IF (INTMODE == 1) - DI -#ENDIF CALL HB_BNKCPY ; COPY THE CLOCK DATA -#IF (INTMODE == 1) - EI -#ENDIF ; LD DE,60 ; DELAY 60 * 16US = ~1MS CALL VDELAY ; SLOW DOWN SIMH FOR CLOCK TICKING TEST @@ -107,13 +108,7 @@ SIMRTC_SETTIM: LD (HB_DSTBNK),A ; SET IT LD DE,SIMRTC_BUF ; DEST ADR LD BC,SIMRTC_BUFSIZ ; LENGTH -#IF (INTMODE == 1) - DI -#ENDIF CALL HB_BNKCPY ; COPY THE CLOCK DATA -#IF (INTMODE == 1) - EI -#ENDIF ; LD HL,SIMRTC_BUF ; POINT TO TEMP BUF LD A,SIMRTC_CLKWRITE ; WRITE CLOCK COMMAND diff --git a/Source/HBIOS/sio.asm b/Source/HBIOS/sio.asm index 2f0ef922..31000849 100644 --- a/Source/HBIOS/sio.asm +++ b/Source/HBIOS/sio.asm @@ -15,41 +15,89 @@ ; https://www.retrobrewcomputers.org/doku.php?id=boards:ecb:zilog-peripherals:clock-divider ; ; SIO PORT A (COM1:) and SIO PORT B (COM2:) ARE MAPPED TO DEVICE UC1: AND UL1: IN CP/M. -; +; +SIO_BUFSZ .EQU 32 ; RECEIVE RING BUFFER SIZE +; SIO_NONE .EQU 0 SIO_SIO .EQU 1 -; -#IF (SIOMODE == SIOMODE_RC) -SIOA_CMD .EQU SIOBASE + $00 -SIOA_DAT .EQU SIOBASE + $01 -SIOB_CMD .EQU SIOBASE + $02 -SIOB_DAT .EQU SIOBASE + $03 +; +SIO_RTSON .EQU $EA +SIO_RTSOFF .EQU $E8 +; +#IF (INTMODE == 0) +SIO_WR1VAL .EQU $00 ; WR1 VALUE FOR NO INTS +#ELSE +SIO_WR1VAL .EQU $18 ; WR1 VALUE FOR INT ON RECEIVED CHARS +#ENDIF +; +#IF (INTMODE == 2) +; +SIO0_IVT .EQU IVT(INT_SIO0) +SIO1_IVT .EQU IVT(INT_SIO1) +SIO0_VEC .EQU VEC(INT_SIO0) +SIO1_VEC .EQU VEC(INT_SIO1) +; +#ENDIF +; +#IF (SIO0MODE == SIOMODE_RC) +SIO0A_CMD .EQU SIO0BASE + $00 +SIO0A_DAT .EQU SIO0BASE + $01 +SIO0B_CMD .EQU SIO0BASE + $02 +SIO0B_DAT .EQU SIO0BASE + $03 #ENDIF ; -#IF (SIOMODE == SIOMODE_SMB) -SIOA_CMD .EQU SIOBASE + $02 -SIOA_DAT .EQU SIOBASE + $00 -SIOB_CMD .EQU SIOBASE + $03 -SIOB_DAT .EQU SIOBASE + $01 +#IF (SIO0MODE == SIOMODE_SMB) +SIO0A_CMD .EQU SIO0BASE + $02 +SIO0A_DAT .EQU SIO0BASE + $00 +SIO0B_CMD .EQU SIO0BASE + $03 +SIO0B_DAT .EQU SIO0BASE + $01 +#ENDIF +; +#IF (SIO0MODE == SIOMODE_ZP) +SIO0A_CMD .EQU SIO0BASE + $06 +SIO0A_DAT .EQU SIO0BASE + $04 +SIO0B_CMD .EQU SIO0BASE + $07 +SIO0B_DAT .EQU SIO0BASE + $05 #ENDIF ; -#IF (SIOMODE == SIOMODE_ZP) -SIOA_CMD .EQU SIOBASE + $06 -SIOA_DAT .EQU SIOBASE + $04 -SIOB_CMD .EQU SIOBASE + $07 -SIOB_DAT .EQU SIOBASE + $05 +#IF (SIO0MODE == SIOMODE_EZZ80) +SIO0A_CMD .EQU SIO0BASE + $01 +SIO0A_DAT .EQU SIO0BASE + $00 +SIO0B_CMD .EQU SIO0BASE + $03 +SIO0B_DAT .EQU SIO0BASE + $02 #ENDIF ; -#IF (SIOMODE == SIOMODE_EZZ80) -SIOA_CMD .EQU SIOBASE + $01 -SIOA_DAT .EQU SIOBASE + $00 -SIOB_CMD .EQU SIOBASE + $03 -SIOB_DAT .EQU SIOBASE + $02 +#IF (SIOCNT >= 2) +; +#IF (SIO1MODE == SIOMODE_RC) +SIO1A_CMD .EQU SIO1BASE + $00 +SIO1A_DAT .EQU SIO1BASE + $01 +SIO1B_CMD .EQU SIO1BASE + $02 +SIO1B_DAT .EQU SIO1BASE + $03 #ENDIF ; -; CONDITIONALS THAT DETERMINE THE ENCODED VALUE OF THE BAUD RATE +#IF (SIO1MODE == SIOMODE_SMB) +SIO1A_CMD .EQU SIO1BASE + $02 +SIO1A_DAT .EQU SIO1BASE + $00 +SIO1B_CMD .EQU SIO1BASE + $03 +SIO1B_DAT .EQU SIO1BASE + $01 +#ENDIF ; -#INCLUDE "siobaud.inc" +#IF (SIO1MODE == SIOMODE_ZP) +SIO1A_CMD .EQU SIO1BASE + $06 +SIO1A_DAT .EQU SIO1BASE + $04 +SIO1B_CMD .EQU SIO1BASE + $07 +SIO1B_DAT .EQU SIO1BASE + $05 +#ENDIF +; +#IF (SIO1MODE == SIOMODE_EZZ80) +SIO1A_CMD .EQU SIO1BASE + $01 +SIO1A_DAT .EQU SIO1BASE + $00 +SIO1B_CMD .EQU SIO1BASE + $03 +SIO1B_DAT .EQU SIO1BASE + $02 +#ENDIF +; +#ENDIF ; SIO_PREINIT: ; @@ -57,23 +105,15 @@ SIO_PREINIT: ; NOTE: INTS WILL BE DISABLED WHEN PREINIT IS CALLED AND THEY MUST REMIAIN ; DISABLED. ; - LD B,SIO_CNT ; LOOP CONTROL - LD C,0 ; PHYSICAL UNIT INDEX + CALL SIO_PROBE ; PROBE FOR CHIPS +; + LD B,SIO_CFGCNT ; LOOP CONTROL XOR A ; ZERO TO ACCUM LD (SIO_DEV),A ; CURRENT DEVICE NUMBER + LD IY,SIO_CFG ; POINT TO START OF CFG TABLE SIO_PREINIT0: PUSH BC ; SAVE LOOP CONTROL - LD A,C ; PHYSICAL UNIT TO A - RLCA ; MULTIPLY BY CFG TABLE ENTRY SIZE (8 BYTES) - RLCA ; ... - RLCA ; ... TO GET OFFSET INTO CFG TABLE - LD HL,SIO_CFG ; POINT TO START OF CFG TABLE - CALL ADDHLA ; HL := ENTRY ADDRESS - PUSH HL ; SAVE IT - PUSH HL ; COPY CFG DATA PTR - POP IY ; ... TO IY CALL SIO_INITUNIT ; HAND OFF TO GENERIC INIT CODE - POP DE ; GET ENTRY ADDRESS BACK, BUT PUT IN DE POP BC ; RESTORE LOOP CONTROL ; LD A,(IY+1) ; GET THE SIO TYPE DETECTED @@ -81,27 +121,79 @@ SIO_PREINIT0: JR Z,SIO_PREINIT2 ; SKIP IT IF NOTHING FOUND ; PUSH BC ; SAVE LOOP CONTROL + PUSH IY ; CFG ENTRY ADDRESS + POP DE ; ... TO DE LD BC,SIO_FNTBL ; BC := FUNCTION TABLE ADDRESS CALL NZ,CIO_ADDENT ; ADD ENTRY IF SIO FOUND, BC:DE POP BC ; RESTORE LOOP CONTROL ; SIO_PREINIT2: - INC C ; NEXT PHYSICAL UNIT + LD DE,SIO_CFGSIZ ; SIZE OF CFG ENTRY + ADD IY,DE ; BUMP IY TO NEXT ENTRY DJNZ SIO_PREINIT0 ; LOOP UNTIL DONE ; -#IF (INTMODE == 1) - ; ADD IM1 INT CALL LIST ENTRY IF APPROPRIATE - LD A,(SIO_DEV) ; GET NEXT DEVICE NUM +#IF (INTMODE >= 1) + ; SETUP INT VECTORS AS APPROPRIATE + LD A,(SIO_DEV) ; GET DEVICE COUNT OR A ; SET FLAGS - JR Z,SIO_PREINIT3 ; IF ZERO, NO SIO DEVICES + JR Z,SIO_PREINIT3 ; IF ZERO, NO SIO DEVICES, ABORT +; +#IF (INTMODE == 1) + ; ADD IM1 INT CALL LIST ENTRY LD HL,SIO_INT ; GET INT VECTOR CALL HB_ADDIM1 ; ADD TO IM1 CALL LIST #ENDIF ; #IF (INTMODE == 2) - ; SETUP SIO INTERRUPT VECTOR IN IVT - LD HL,INT_SIO - LD (HBX_IVT + IVT_SER0),HL + ; SETUP IM2 VECTORS + LD HL,SIO_INT0 + LD (SIO0_IVT),HL ; IVT INDEX +; +#IF (SIOCNT >= 2) + LD HL,SIO_INT1 + LD (SIO1_IVT),HL ; IVT INDEX +#ENDIF +; +#ENDIF +; +#ENDIF +; +; FOR NOW, THIS IS SPECIFICALLY FOR A CTC TO DRIVE AN SIO +; AT 1:1 USING CTC CHANNELS A & B. IN OTHER WORDS, IT JUST +; PASSES THE INCOMING TRIGGER OUT AT 1:1. +; +#IF (SIOCNT >= 1) + #IF (SIO0CTCC >= 0) + LD A,%01010111 ; CTCC CONTROL WORD VALUE + ; |||||||+-- 1=CONTROL WORD FLAG + ; ||||||+--- 1=SOFTWARE RESET + ; |||||+---- 1=TIME CONSTANT FOLLOWS + ; ||||+----- 0=AUTO TRIGGER WHEN TIME CONST LOADED + ; |||+------ 1=RISING EDGE TRIGGER + ; ||+------- 0=PRESCALER OF 16 (NOT USED) + ; |+-------- 1=COUNTER MODE + ; +--------- 0=NO INTERRUPTS + OUT (CTCA + SIO0CTCC),A ; SETUP CTCC + LD A,1 ; CTC TIMER CONSTANT = 1 + OUT (CTCA + SIO0CTCC),A ; SETUP CTC TIMER CONSTANT + #ENDIF +#ENDIF +; +#IF (SIOCNT >= 2) + #IF (SIO1CTCC >= 0) + LD A,%01010111 ; CTCC CONTROL WORD VALUE + ; |||||||+-- 1=CONTROL WORD FLAG + ; ||||||+--- 1=SOFTWARE RESET + ; |||||+---- 1=TIME CONSTANT FOLLOWS + ; ||||+----- 0=AUTO TRIGGER WHEN TIME CONST LOADED + ; |||+------ 1=RISING EDGE TRIGGER + ; ||+------- 0=PRESCALER OF 16 (NOT USED) + ; |+-------- 1=COUNTER MODE + ; +--------- 0=NO INTERRUPTS + OUT (CTCA + SIO1CTCC),A ; SETUP CTCC + LD A,1 ; CTC TIMER CONSTANT = 1 + OUT (CTCA + SIO1CTCC),A ; SETUP CTC TIMER CONSTANT + #ENDIF #ENDIF ; SIO_PREINIT3: @@ -122,6 +214,13 @@ SIO_INITUNIT: INC (HL) ; INCREMENT IT (FOR NEXT LOOP) LD (IY),A ; UPDATE UNIT NUM + ; IT IS EASY TO SPECIFY A SERIAL CONFIG THAT CANNOT BE IMPLEMENTED + ; DUE TO THE CONSTRAINTS OF THE SIO. HERE WE FORCE A GENERIC + ; FAILSAFE CONFIG ONTO THE CHANNEL. IF THE SUBSEQUENT "REAL" + ; CONFIG FAILS, AT LEAST THE CHIP WILL BE ABLE TO SPIT DATA OUT + ; AT A RATIONAL BAUD/DATA/PARITY/STOP CONFIG. + CALL SIO_INITSAFE +; ; SET DEFAULT CONFIG LD DE,-1 ; LEAVE CONFIG ALONE ; CALL INITDEVX TO IMPLEMENT CONFIG, BUT NOTE THAT WE CALL @@ -131,26 +230,16 @@ SIO_INITUNIT: ; ; SIO_INIT: - LD B,SIO_CNT ; COUNT OF POSSIBLE SIO UNITS - LD C,0 ; INDEX INTO SIO CONFIG TABLE + LD B,SIO_CFGCNT ; COUNT OF POSSIBLE SIO UNITS + LD IY,SIO_CFG ; POINT TO START OF CFG TABLE SIO_INIT1: PUSH BC ; SAVE LOOP CONTROL - - LD A,C ; PHYSICAL UNIT TO A - RLCA ; MULTIPLY BY CFG TABLE ENTRY SIZE (8 BYTES) - RLCA ; ... - RLCA ; ... TO GET OFFSET INTO CFG TABLE - LD HL,SIO_CFG ; POINT TO START OF CFG TABLE - CALL ADDHLA ; HL := ENTRY ADDRESS - PUSH HL ; COPY CFG DATA PTR - POP IY ; ... TO IY - LD A,(IY+1) ; GET SIO TYPE OR A ; SET FLAGS CALL NZ,SIO_PRTCFG ; PRINT IF NOT ZERO - POP BC ; RESTORE LOOP CONTROL - INC C ; NEXT UNIT + LD DE,SIO_CFGSIZ ; SIZE OF CFG ENTRY + ADD IY,DE ; BUMP IY TO NEXT ENTRY DJNZ SIO_INIT1 ; LOOP TILL DONE ; XOR A ; SIGNAL SUCCESS @@ -160,96 +249,110 @@ SIO_INIT1: ; #IF (INTMODE > 0) ; +; IM1 ENTRY POINT +; SIO_INT: -SIOA_INT: - ; CHECK FOR RECEIVE PENDING ON CHANNEL A - XOR A ; A := 0 - OUT (SIOA_CMD),A ; ADDRESS RD0 - IN A,(SIOA_CMD) ; GET RD0 - AND $01 ; ISOLATE RECEIVE READY BIT - JR Z,SIOB_INT ; CHECK CHANNEL B -; -SIOA_INT00: - ; HANDLE CHANNEL A - IN A,(SIOA_DAT) ; READ PORT - LD E,A ; SAVE BYTE READ - LD A,(SIOA_CNT) ; GET CURRENT BUFFER USED COUNT - CP SIOA_BUFSZ ; COMPARE TO BUFFER SIZE - ;RET Z ; BAIL OUT IF BUFFER FULL, RCV BYTE DISCARDED - JR Z,SIOA_INT2 ; BAIL OUT IF BUFFER FULL, RCV BYTE DISCARDED - INC A ; INCREMENT THE COUNT - LD (SIOA_CNT),A ; AND SAVE IT - CP SIOA_BUFSZ - 5 ; BUFFER GETTING FULL? - JR NZ,SIOA_INT0 ; IF NOT, BYPASS CLEARING RTS - LD A,5 ; RTS IS IN WR5 - OUT (SIOA_CMD),A ; ADDRESS WR5 - LD A,$E8 ; VALUE TO CLEAR RTS - OUT (SIOA_CMD),A ; DO IT -SIOA_INT0: - LD HL,(SIOA_HD) ; GET HEAD POINTER - LD A,L ; GET LOW BYTE - CP SIOA_BUFEND & $FF ; PAST END? - JR NZ,SIOA_INT1 ; IF NOT, BYPASS POINTER RESET - LD HL,SIOA_BUF ; ... OTHERWISE, RESET TO START OF BUFFER -SIOA_INT1: - LD A,E ; RECOVER BYTE READ - LD (HL),A ; SAVE RECEIVED BYTE TO HEAD POSITION - INC HL ; INCREMENT HEAD POINTER - LD (SIOA_HD),HL ; SAVE IT -; -SIOA_INT2: - ; CHECK FOR MORE PENDING... - XOR A ; A := 0 - OUT (SIOA_CMD),A ; ADDRESS RD0 - IN A,(SIOA_CMD) ; GET RD0 - RRA ; READY BIT TO CF - JR C,SIOA_INT00 ; IF SET, DO SOME MORE - OR $FF ; NZ SET TO INDICATE INT HANDLED - RET ; AND RETURN + ; CHECK/HANDLE FIRST CARD (SIO0) IF IT EXISTS + LD A,(SIO0A_CFG + 1) ; GET SIO TYPE FOR FIRST CHANNEL OF FIRST SIO + OR A ; SET FLAGS + CALL NZ,SIO_INT0 ; CALL IF CARD EXISTS + RET NZ ; DONE IF INT HANDLED +; +#IF (SIOCNT >= 2) + ; CHECK/HANDLE SECOND CARD (SIO1) IF IT EXISTS + LD A,(SIO1A_CFG + 1) ; GET SIO TYPE FOR FIRST CHANNEL OF SECOND SIO + OR A ; SET FLAGS + CALL NZ,SIO_INT1 ; CALL IF CARD EXISTS +#ENDIF +; + RET ; DONE +; +; IM2 ENTRY POINTS +; +SIO_INT0: + ; INTERRUPT HANDLER FOR FIRST SIO (SIO0) + LD IY,SIO0A_CFG ; POINT TO SIO0A CFG + CALL SIO_INTRCV ; TRY TO RECEIVE FROM IT + RET NZ ; DONE IF INT HANDLED + LD IY,SIO0B_CFG ; POINT TO SIO0B CFG + JR SIO_INTRCV ; TRY TO RECEIVE FROM IT AND RETURN ; -SIOB_INT: - ; CHECK FOR RECEIVE PENDING ON CHANNEL B +#IF (SIOCNT >= 2) +; +SIO_INT1: + ; INTERRUPT HANDLER FOR SECOND SIO (SIO1) + LD IY,SIO1A_CFG ; POINT TO SIO1A CFG + CALL SIO_INTRCV ; TRY TO RECEIVE FROM IT + RET NZ ; DONE IF INT HANDLED + LD IY,SIO1B_CFG ; POINT TO SIO1B CFG + JR SIO_INTRCV ; TRY TO RECEIVE FROM IT AND RETURN +; +#ENDIF +; +; HANDLE INT FOR A SPECIFIC CHANNEL +; BASED ON UNIT CFG POINTED TO BY IY +; +SIO_INTRCV: + ; CHECK TO SEE IF SOMETHING IS ACTUALLY THERE + LD C,(IY+3) ; CMD/STAT PORT TO C XOR A ; A := 0 - OUT (SIOB_CMD),A ; ADDRESS RD0 - IN A,(SIOB_CMD) ; GET RD0 + OUT (C),A ; ADDRESS RD0 + IN A,(C) ; GET RD0 AND $01 ; ISOLATE RECEIVE READY BIT - RET Z ; IF NOT, RETURN WITH Z SET -; -SIOB_INT00: - ; HANDLE CHANNEL B - IN A,(SIOB_DAT) ; READ PORT - LD E,A ; SAVE BYTE READ - LD A,(SIOB_CNT) ; GET CURRENT BUFFER USED COUNT - CP SIOB_BUFSZ ; COMPARE TO BUFFER SIZE - ;RET Z ; BAIL OUT IF BUFFER FULL, RCV BYTE DISCARDED - JR Z,SIOB_INT2 ; BAIL OUT IF BUFFER FULL, RCV BYTE DISCARDED + RET Z ; NOTHING AVAILABLE ON CURRENT CHANNEL +; +SIO_INTRCV1: + ; RECEIVE CHARACTER INTO BUFFER + LD C,(IY+4) ; DATA PORT TO C + IN A,(C) ; READ PORT + LD B,A ; SAVE BYTE READ + LD L,(IY+7) ; SET HL TO + LD H,(IY+8) ; ... START OF BUFFER STRUCT + LD A,(HL) ; GET COUNT + CP SIO_BUFSZ ; COMPARE TO BUFFER SIZE + JR Z,SIO_INTRCV4 ; BAIL OUT IF BUFFER FULL, RCV BYTE DISCARDED INC A ; INCREMENT THE COUNT - LD (SIOB_CNT),A ; AND SAVE IT - CP SIOB_BUFSZ - 5 ; BUFFER GETTING FULL? - JR NZ,SIOB_INT0 ; IF NOT, BYPASS CLEARING RTS + LD (HL),A ; AND SAVE IT + CP SIO_BUFSZ / 2 ; BUFFER GETTING FULL? + JR NZ,SIO_INTRCV2 ; IF NOT, BYPASS CLEARING RTS + LD C,(IY+3) ; CMD/STAT PORT TO C LD A,5 ; RTS IS IN WR5 - OUT (SIOB_CMD),A ; ADDRESS WR5 - LD A,$E8 ; VALUE TO CLEAR RTS - OUT (SIOB_CMD),A ; DO IT -SIOB_INT0: - LD HL,(SIOB_HD) ; GET HEAD POINTER - LD A,L ; GET LOW BYTE - CP SIOB_BUFEND & $FF ; PAST END? - JR NZ,SIOB_INT1 ; IF NOT, BYPASS POINTER RESET - LD HL,SIOB_BUF ; ... OTHERWISE, RESET TO START OF BUFFER -SIOB_INT1: - LD A,E ; RECOVER BYTE READ - LD (HL),A ; SAVE RECEIVED BYTE TO HEAD POSITION - INC HL ; INCREMENT HEAD POINTER - LD (SIOB_HD),HL ; SAVE IT -; -SIOB_INT2: + OUT (C),A ; ADDRESS WR5 + LD A,SIO_RTSOFF ; VALUE TO CLEAR RTS + OUT (C),A ; DO IT +SIO_INTRCV2: + INC HL ; HL NOW HAS ADR OF HEAD PTR + PUSH HL ; SAVE ADR OF HEAD PTR + LD A,(HL) ; DEREFERENCE HL + INC HL + LD H,(HL) + LD L,A ; HL IS NOW ACTUAL HEAD PTR + LD (HL),B ; SAVE CHARACTER RECEIVED IN BUFFER AT HEAD + INC HL ; BUMP HEAD POINTER + POP DE ; RECOVER ADR OF HEAD PTR + LD A,L ; GET LOW BYTE OF HEAD PTR + SUB SIO_BUFSZ+4 ; SUBTRACT SIZE OF BUFFER AND POINTER + CP E ; IF EQUAL TO START, HEAD PTR IS PAST BUF END + JR NZ,SIO_INTRCV3 ; IF NOT, BYPASS + LD H,D ; SET HL TO + LD L,E ; ... HEAD PTR ADR + INC HL ; BUMP PAST HEAD PTR + INC HL + INC HL + INC HL ; ... SO HL NOW HAS ADR OF ACTUAL BUFFER START +SIO_INTRCV3: + EX DE,HL ; DE := HEAD PTR VAL, HL := ADR OF HEAD PTR + LD (HL),E ; SAVE UPDATED HEAD PTR + INC HL + LD (HL),D ; CHECK FOR MORE PENDING... + LD C,(IY+3) ; CMD/STAT PORT TO C XOR A ; A := 0 - OUT (SIOB_CMD),A ; ADDRESS RD0 - IN A,(SIOB_CMD) ; GET RD0 + OUT (C),A ; ADDRESS RD0 + IN A,(C) ; GET RD0 RRA ; READY BIT TO CF - JR C,SIOB_INT00 ; IF SET, DO SOME MORE + JR C,SIO_INTRCV1 ; IF SET, DO SOME MORE +SIO_INTRCV4: OR $FF ; NZ SET TO INDICATE INT HANDLED RET ; AND RETURN ; @@ -276,17 +379,7 @@ SIO_FNTBL: SIO_IN: CALL SIO_IST ; CHAR WAITING? JR Z,SIO_IN ; LOOP IF NOT - LD C,(IY+3) ; C := SIO CMD PORT -#IF (SIOMODE == SIOMODE_RC) - INC C ; BUMP TO DATA PORT -#ENDIF -#IF ((SIOMODE == SIOMODE_SMB) | (SIOMODE == SIOMODE_ZP)) - DEC C ; DECREMENT CMD PORT TWICE TO GET DATA PORT - DEC C -#ENDIF -#IF (SIOMODE == SIOMODE_EZZ80) - DEC C ; DECREMENT CMD PORT TO GET DATA PORT -#ENDIF + LD C,(IY+4) ; DATA PORT IN E,(C) ; GET CHAR XOR A ; SIGNAL SUCCESS RET @@ -294,68 +387,50 @@ SIO_IN: #ELSE ; SIO_IN: - LD A,(IY+2) ; GET CHANNEL - OR A ; SET FLAGS - JR Z,SIOA_IN ; HANDLE CHANNEL A - DEC A ; TEST FOR NEXT DEVICE - JR Z,SIOB_IN ; HANDLE CHANNEL B - CALL PANIC ; ELSE FATAL ERROR - RET ; ... AND RETURN -; -SIOA_IN: - CALL SIOA_IST ; RECEIVED CHAR READY? - JR Z,SIOA_IN ; LOOP TILL WE HAVE SOMETHING IN BUFFER + CALL SIO_IST ; SEE IF CHAR AVAILABLE + JR Z,SIO_IN ; LOOP UNTIL SO HB_DI ; AVOID COLLISION WITH INT HANDLER - LD A,(SIOA_CNT) ; GET COUNT + LD L,(IY+7) ; SET HL TO + LD H,(IY+8) ; ... START OF BUFFER STRUCT + LD A,(HL) ; GET COUNT DEC A ; DECREMENT COUNT - LD (SIOA_CNT),A ; SAVE SAVE IT - CP 5 ; BUFFER LOW THRESHOLD - JR NZ,SIOA_IN0 ; IF NOT, BYPASS SETTING RTS + LD (HL),A ; SAVE UPDATED COUNT + CP SIO_BUFSZ / 4 ; BUFFER LOW THRESHOLD + JR NZ,SIO_IN1 ; IF NOT, BYPASS SETTING RTS + LD C,(IY+3) ; C IS CMD/STATUS PORT ADR LD A,5 ; RTS IS IN WR5 - OUT (SIOA_CMD),A ; ADDRESS WR5 - LD A,$EA ; VALUE TO SET RTS - OUT (SIOA_CMD),A ; DO IT -SIOA_IN0: - LD HL,(SIOA_TL) ; GET BUFFER TAIL POINTER - LD E,(HL) ; GET BYTE - INC HL ; BUMP TAIL POINTER - LD A,L ; GET LOW BYTE - CP SIOA_BUFEND & $FF ; PAST END? - JR NZ,SIOA_IN1 ; IF NOT, BYPASS POINTER RESET - LD HL,SIOA_BUF ; ... OTHERWISE, RESET TO START OF BUFFER -SIOA_IN1: - LD (SIOA_TL),HL ; SAVE UPDATED TAIL POINTER - HB_EI ; INTERRUPTS OK AGAIN - XOR A ; SIGNAL SUCCESS - RET ; AND DONE -; -SIOB_IN: - CALL SIOB_IST ; RECEIVED CHAR READY? - JR Z,SIOB_IN ; LOOP TILL WE HAVE SOMETHING IN BUFFER - HB_DI ; AVOID COLLISION WITH INT HANDLER - LD A,(SIOB_CNT) ; GET COUNT - DEC A ; DECREMENT COUNT - LD (SIOB_CNT),A ; SAVE SAVE IT - CP 5 ; BUFFER LOW THRESHOLD - JR NZ,SIOB_IN0 ; IF NOT, BYPASS SETTING RTS - LD A,5 ; RTS IS IN WR5 - OUT (SIOB_CMD),A ; ADDRESS WR5 - LD A,$EA ; VALUE TO SET RTS - OUT (SIOB_CMD),A ; DO IT -SIOB_IN0: - LD HL,(SIOB_TL) ; GET BUFFER TAIL POINTER - LD E,(HL) ; GET BYTE - INC HL ; BUMP TAIL POINTER - LD A,L ; GET LOW BYTE - CP SIOB_BUFEND & $FF ; PAST END? - JR NZ,SIOB_IN1 ; IF NOT, BYPASS POINTER RESET - LD HL,SIOB_BUF ; ... OTHERWISE, RESET TO START OF BUFFER -SIOB_IN1: - LD (SIOB_TL),HL ; SAVE UPDATED TAIL POINTER + OUT (C),A ; ADDRESS WR5 + LD A,SIO_RTSON ; VALUE TO SET RTS + OUT (C),A ; DO IT +SIO_IN1: + INC HL + INC HL + INC HL ; HL NOW HAS ADR OF TAIL PTR + PUSH HL ; SAVE ADR OF TAIL PTR + LD A,(HL) ; DEREFERENCE HL + INC HL + LD H,(HL) + LD L,A ; HL IS NOW ACTUAL TAIL PTR + LD C,(HL) ; C := CHAR TO BE RETURNED + INC HL ; BUMP TAIL PTR + POP DE ; RECOVER ADR OF TAIL PTR + LD A,L ; GET LOW BYTE OF TAIL PTR + SUB SIO_BUFSZ+2 ; SUBTRACT SIZE OF BUFFER AND POINTER + CP E ; IF EQUAL TO START, TAIL PTR IS PAST BUF END + JR NZ,SIO_IN2 ; IF NOT, BYPASS + LD H,D ; SET HL TO + LD L,E ; ... TAIL PTR ADR + INC HL ; BUMP PAST TAIL PTR + INC HL ; ... SO HL NOW HAS ADR OF ACTUAL BUFFER START +SIO_IN2: + EX DE,HL ; DE := TAIL PTR VAL, HL := ADR OF TAIL PTR + LD (HL),E ; SAVE UPDATED TAIL PTR + INC HL + LD (HL),D + LD E,C ; MOVE CHAR TO RETURN TO E HB_EI ; INTERRUPTS OK AGAIN XOR A ; SIGNAL SUCCESS RET ; AND DONE -; #ENDIF ; ; @@ -363,17 +438,7 @@ SIOB_IN1: SIO_OUT: CALL SIO_OST ; READY FOR CHAR? JR Z,SIO_OUT ; LOOP IF NOT - LD C,(IY+3) ; C := SIO CMD PORT -#IF (SIOMODE == SIOMODE_RC) - INC C ; BUMP TO DATA PORT -#ENDIF -#IF ((SIOMODE == SIOMODE_SMB) | (SIOMODE == SIOMODE_ZP)) - DEC C ; DECREMENT CMD PORT TWICE TO GET DATA PORT - DEC C -#ENDIF -#IF (SIOMODE == SIOMODE_EZZ80) - DEC C ; DECREMENT CMD PORT TO GET DATA PORT -#ENDIF + LD C,(IY+4) ; DATA PORT OUT (C),E ; SEND CHAR FROM E XOR A ; SIGNAL SUCCESS RET @@ -396,25 +461,12 @@ SIO_IST: #ELSE ; SIO_IST: - LD A,(IY+2) ; GET CHANNEL - OR A ; SET FLAGS - JR Z,SIOA_IST ; HANDLE CHANNEL A - DEC A ; TEST FOR NEXT DEVICE - JR Z,SIOB_IST ; HANDLE CHANNEL B - CALL PANIC ; ELSE FATAL ERROR - RET ; ... AND RETURN -; -SIOA_IST: - LD A,(SIOA_CNT) ; GET BUFFER UTILIZATION COUNT - OR A ; SET FLAGS - JP Z,CIO_IDLE ; NOT READY, RETURN VIA IDLE PROCESSING - RET ; AND DONE -; -SIOB_IST: - LD A,(SIOB_CNT) ; GET BUFFER UTILIZATION COUNT + LD L,(IY+7) ; GET ADDRESS + LD H,(IY+8) ; ... OF RECEIVE BUFFER + LD A,(HL) ; BUFFER UTILIZATION COUNT OR A ; SET FLAGS JP Z,CIO_IDLE ; NOT READY, RETURN VIA IDLE PROCESSING - RET ; DONE + RET ; #ENDIF ; @@ -453,134 +505,207 @@ SIO_INITDEVX: ; ; THIS ENTRY POINT BYPASSES DISABLING/ENABLING INTS WHICH IS REQUIRED BY ; PREINIT ABOVE. PREINIT IS NOT ALLOWED TO ENABLE INTS! +; +#IF (SIODEBUG) + CALL NEWLINE + PRTS("SIO$") + LD A,(IY+2) + SRL A + CALL PRTDECB + LD A,(IY+2) + AND $01 + ADD A,'A' + CALL COUT + CALL PC_COLON +#ENDIF ; ; TEST FOR -1 WHICH MEANS USE CURRENT CONFIG (JUST REINIT) LD A,D ; TEST DE FOR AND E ; ... VALUE OF -1 INC A ; ... SO Z SET IF -1 - JR NZ,SIO_INITDEV1 ; IF DE == -1, REINIT CURRENT CONFIG + JR NZ,SIO_INITDEV1 ; IF DE == -1, REINIT CURRENT CONFIG ; ; LOAD EXISTING CONFIG TO REINIT - LD E,(IY+4) ; LOW BYTE - LD D,(IY+5) ; HIGH BYTE + LD E,(IY+5) ; LOW BYTE + LD D,(IY+6) ; HIGH BYTE ; SIO_INITDEV1: - PUSH DE ; SAVE CONFIG - +; +#IF (SIODEBUG) + PUSH DE + POP BC + PRTS(" CFG=$") + CALL PRTHEXWORD +#ENDIF +; + LD A,E ; GET CONFIG LSB + AND $E0 ; CHECK FOR DTR, XON, PARITY=MARK/SPACE + JR NZ,SIO_INITFAIL ; IF ANY BIT SET, FAIL, NOT SUPPORTED +; LD A,D ; GET CONFIG MSB AND $1F ; ISOLATE ENCODED BAUD RATE - +; #IF (SIODEBUG) - PUSH AF - PRTS(" ENCODE[$") + PRTS(" ENC=$") CALL PRTHEXBYTE - PRTC(']') - POP AF -#ENDIF -; -; ONLY FOUR BAUD RATES ARE POSSIBLE WITH A FIXED CLOCK. -; THESE ARE PREDETERMINED BY HARDWARE SETTINGS AND MATCHING -; CONFIGURATION SETTINGS. WE PRECALCULATED THE FOUR -; POSSIBLE ENCODED VALUES. -; - CP SIOBAUD1 ; We set the divider and the lower bit (d2) of the stop bits - LD D,$04 ; /1 N,8,1 - JR Z,BROK - CP SIOBAUD2 - LD D,$44 ; /16 N,8,1 - JR Z,BROK - CP SIOBAUD3 - LD D,$84 ; /32 N,8,1 - JR Z,BROK - CP SIOBAUD4 - LD D,$C4 ; /64 N,8,1 - JR Z,BROK - +#ENDIF +; + PUSH DE ; SAVE REQUESTED CONFIG + LD L,(IY+9) ; LOAD CLK FREQ + LD H,(IY+10) ; ... INTO DE:HL + LD E,(IY+11) ; ... " + LD D,(IY+12) ; ... " + LD C,75 ; BAUD RATE ENCODING CONSTANT + CALL ENCODE ; C = TEST BAUD RATE (ENCODED) = BAUDTST + POP DE ; GET REQ CONFIG BACK, D = BAUDREQ +; + ; BIT 4 (DIV 3) OF BAUDREQ AND BAUDTST MUST MATCH! + LD A,C ; A = BAUDTST + XOR D ; XOR WITH BAUDREQ + BIT 4,A ; DO BIT 4 VALS MATCH? + JR NZ,SIO_INITFAIL ; IF NOT, BAIL OUT +; + LD A,C ; BAUDTST TO A + AND $0F ; ISOLATE DIV 2 BAUD BITS + LD C,A ; C = BAUDTST +; + LD A,D ; MSB W/ BAUD RATE TO A + AND $0F ; ISOLATE DIV 2 BAUD BITS + LD L,A ; L = BAUDREQ +; +; PUSH AF ; *DEBUG* +; CALL NEWLINE ; *DEBUG* +; LD A,L ; *DEBUG* +; CALL PRTHEXBYTE ; *DEBUG* +; LD A,C ; *DEBUG* +; CALL PRTHEXBYTE ; *DEBUG* +; CALL NEWLINE ; *DEBUG* +; POP AF ; *DEBUG* +; + LD A,C ; A = BAUDTST + LD B,$04 ; SIO R4 VAL FOR DIV 1 + CP L ; BAUDTST = BAUDREQ? + JR Z,SIO_INITBROK ; IF MATCH, WE ARE DONE +; + SUB 4 ; DIVIDE BY 16 (NOW DIV 16 TOT) + JR C,SIO_INITFAIL ; FAIL IF UNDERFLOW + LD B,$44 ; SIO R4 VAL FOR DIV 16 + CP L ; BAUDTST = BAUDREQ? + JR Z,SIO_INITBROK ; IF MATCH, WE ARE DONE +; + SUB 1 ; DIVIDE BY 2 (NOW DIV 32 TOT) + JR C,SIO_INITFAIL ; FAIL IF UNDERFLOW + LD B,$84 ; SIO R4 VAL FOR DIV 32 + CP L ; BAUDTST = BAUDREQ? + JR Z,SIO_INITBROK ; IF MATCH, WE ARE DONE +; + SUB 1 ; DIVIDE BY 2 (NOW DIV 64 TOT) + JR C,SIO_INITFAIL ; FAIL IF UNDERFLOW + LD B,$C4 ; SIO R4 VAL FOR DIV 64 + CP L ; BAUDTST = BAUDREQ? + JR Z,SIO_INITBROK ; IF MATCH, WE ARE DONE +; +SIO_INITFAIL: +; #IF (SIODEBUG) - PUSH AF - PRTS(" BR FAIL[$") - CALL PRTHEXBYTE - PRTC(']') - POP AF + PRTS(" BAD CFG$") #ENDIF ; -EXITINIT: - POP DE + OR $FF RET ; NZ status here indicating fail / invalid baud rate. - -BROK: - LD A,E - AND $E0 - JR NZ,EXITINIT ; NZ status here indicates dtr, xon, parity mark or space so return - - LD A,E ; set stop bit (d3) and add divider +; +SIO_INITBROK: + LD A,E ; set stop bit (d3) and add divider AND $04 RLA - OR D ; carry gets reset here - LD D,A + OR B ; carry gets reset here + LD L,A ; save in L LD A,E ; get the parity bits SRL A ; move them to bottom two bits SRL A ; we know top bits are zero from previous test SRL A ; add stop bits - OR D ; carry = 0 + OR L ; carry = 0 ; ; SET DIVIDER, STOP AND PARITY WR4 ; - LD BC,SIO_INITVALS+3 - LD (BC),A - -#IF (SIODEBUG) - PUSH AF - PRTS(" WR4[$") - CALL PRTHEXBYTE - PRTC(']') - POP AF -#ENDIF - + LD (SIO_WR4),A +; LD A,E ; 112233445566d1d0 CC RRA ; CC112233445566d1 d0 RRA ; d0CC112233445566 d1 RRA ; d1d0CC1122334455 66 - LD D,A + LD L,A RRA ; 66d1d0CC11223344 55 AND $60 ; 0011110000000000 00 - OR $8a + OR $8A ; ; SET TRANSMIT DATA BITS WR5 ; - LD BC,SIO_INITVALS+11 - LD (BC),A - -#IF (SIODEBUG) - PUSH AF - PRTS(" WR5[$") - CALL PRTHEXBYTE - PRTC(']') - POP AF -#ENDIF + LD (SIO_WR5),A ; ; SET RECEIVE DATA BITS WR3 ; - LD A,D - AND $C0 - OR $01 - - LD BC,SIO_INITVALS+9 - LD (BC),A - + LD A,L ; DATA BITS + AND $C0 ; CLEAR OTHER BITS + OR $21 ; CTS/DCD AUTO, RX ENABLE +; + LD (SIO_WR3),A +; +; SAVE CONFIG PERMANENTLY NOW +; + LD (IY+5),E ; SAVE LOW WORD + LD (IY+6),D ; SAVE HI WORD +; + JR SIO_INITGO ; GO TO SEND INIT +; +; ENTER HERE TO PERFORM A "SAFE" INITIALIZTION. I.E., INIT THE +; CHANNEL USING THE DEFAULT, GENERIC REGISTER VALUES. THIS CAN BE +; USED TO ENSURE INITIALIZATION IF THE FULL CONFIGURATION ABOVE +; FAILS. +; +SIO_INITSAFE: + LD HL,SIO_INITDEFS + LD DE,SIO_INITVALS + LD BC,SIO_INITLEN + LDIR +; +SIO_INITGO: +; +; SET INTERRUPT VECTOR OFFSET WR2 +; +#IF (INTMODE == 2) + LD A,(IY+2) ; CHIP / CHANNEL + SRL A ; SHIFT AWAY CHANNEL BIT + LD L,SIO0_VEC ; ASSUME CHIP 0 + JR Z,SIO_INITIVT ; IF SO, DO IT + LD L,SIO1_VEC ; ASSUME CHIP 1 + DEC A ; CHIP 1? + JR Z,SIO_INITIVT ; IF SO, DO IT + CALL PANIC ; IMPOSSIBLE SITUATION +SIO_INITIVT: + LD A,L ; VALUE TO A + LD (SIO_WR2),A ; SAVE IT +; +#ENDIF +; #IF (SIODEBUG) - PUSH AF - PRTS(" WR3[$") + LD HL,SIO_INITVALS + LD B,SIO_INITLEN/2 +SIO_INITPRT: + PRTS(" WR$") + LD A,(HL) CALL PRTHEXBYTE - PRTC(']') - POP AF -#ENDIF - - POP DE ; RESTORE CONFIG - - LD (IY+4),E ; SAVE LOW WORD - LD (IY+5),D ; SAVE HI WORD + INC HL + LD A,'=' + CALL COUT + LD A,(HL) + CALL PRTHEXBYTE + INC HL + DJNZ SIO_INITPRT + LD DE,65 + CALL VDELAY ; WAIT FOR FINAL CHAR TO SEND +#ENDIF ; ; PROGRAM THE SIO CHIP CHANNEL LD C,(IY+3) ; COMMAND PORT @@ -591,8 +716,8 @@ BROK: #IF (INTMODE > 0) ; ; RESET THE RECEIVE BUFFER - LD E,(IY+6) - LD D,(IY+7) ; DE := _CNT + LD E,(IY+7) + LD D,(IY+8) ; DE := _CNT XOR A ; A := 0 LD (DE),A ; _CNT = 0 INC DE ; DE := ADR OF _HD @@ -615,25 +740,46 @@ BROK: XOR A ; SIGNAL SUCCESS RET ; RETURN ; +; THE SIO IS A LITTLE PRICKLY ABOUT THE ORDER IN WHICH REGSITERS ARE +; WRITTEN DURING CONFIGURATION. THE TABLE BELOW IS USED TO SETUP +; THE REGISTER VALUES AND THEN THE ENTIRE TABLE CAN BE SPIT OUT. ; SIO_INITVALS: - .DB $00, $18 ; WR0: CHANNEL RESET - .DB $04, $00 ; WR4: CLK BAUD PARITY STOP BIT -#IF (INTMODE == 0) - .DB $01, $00 ; WR1: NO INTERRUPTS -#ELSE - .DB $01, $18 ; WR1: INTERRUPT ON ALL RECEIVE CHARACTERS -#ENDIF - .DB $02, IVT_SER0 ; WR2: INTERRUPT VECTOR OFFSET - .DB $03, $C1 ; WR3: 8 BIT RCV, RX ENABLE - .DB $05, $EA ; WR5: DTR, 8 BITS SEND, TX ENABLE, RTS 1 11 0 1 0 1 0 (1=DTR,11=8bits,0=sendbreak,1=TxEnable,0=sdlc,1=RTS,0=txcrc) + .DB $00, $18 ; WR0: CHANNEL RESET CMD +SIO_WR4 .EQU $+1 + .DB $04, $C4 ; WR4: CLK BAUD PARITY STOP BIT +SIO_WR1 .EQU $+1 + .DB $01, SIO_WR1VAL ; WR1: INTERRUPT STYLE +SIO_WR2 .EQU $+1 + .DB $02, $00 ; WR2: IM2 VEC OFFSET, SET DYNAMICALLY +SIO_WR3 .EQU $+1 + .DB $03, $E1 ; WR3: 8 BIT RCV, CTS/DCD AUTO, RX ENABLE +SIO_WR5 .EQU $+1 + .DB $05, SIO_RTSON ; WR5: DTR, 8 BITS SEND, TX ENABLE, RTS 1 11 0 1 0 1 0 (1=DTR,11=8bits,0=sendbreak,1=TxEnable,0=sdlc,1=RTS,0=txcrc) +; SIO_INITLEN .EQU $ - SIO_INITVALS ; +; THE FOLLOWING TABLE IS A GENERIC, STATIC SET OF CONFIG VALUES THAT CAN +; BE USED TO INITIALIZE THE WORKING TABLE ABOVE. +; +SIO_INITDEFS: + .DB $00, $18 ; WR0: CHANNEL RESET CMD + .DB $04, $C4 ; WR4: CLK BAUD PARITY STOP BIT + .DB $01, SIO_WR1VAL ; WR1: INTERRUPT STYLE + .DB $02, $00 ; WR2: IM2 VEC OFFSET + .DB $03, $E1 ; WR3: 8 BIT RCV, CTS/DCD AUTO, RX ENABLE + .DB $05, SIO_RTSON ; WR5: DTR, 8 BITS SEND, TX ENABLE, RTS 1 11 0 1 0 1 0 (1=DTR,11=8bits,0=sendbreak,1=TxEnable,0=sdlc,1=RTS,0=txcrc) +; +#IF (($ - SIO_INITDEFS) != SIO_INITLEN) + .ECHO "*** ERROR: SIO_INITDEFS TABLE IS NOT THE SAME SIZE AS SIO_INITVALS TABLE!!!\n" + !!! ; FORCE AN ASSEMBLY ERROR +#ENDIF +; ; ; SIO_QUERY: - LD E,(IY+4) ; FIRST CONFIG BYTE TO E - LD D,(IY+5) ; SECOND CONFIG BYTE TO D + LD E,(IY+5) ; FIRST CONFIG BYTE TO E + LD D,(IY+6) ; SECOND CONFIG BYTE TO D XOR A ; SIGNAL SUCCESS RET ; DONE ; @@ -642,24 +788,92 @@ SIO_QUERY: SIO_DEVICE: LD D,CIODEV_SIO ; D := DEVICE TYPE LD E,(IY) ; E := PHYSICAL UNIT + LD C,$00 ; C := DEVICE TYPE, 0x00 IS RS-232 XOR A ; SIGNAL SUCCESS RET ; +; SIO CHIP PROBE +; CHECK FOR PRESENCE OF SIO CHIPS AND POPULATE THE +; SIO_MAP BITMAP (ONE BIT PER CHIP). THIS DETECTS +; CHIPS, NOT CHANNELS. EACH CHIP HAS 2 CHANNELS. +; MAX OF TWO CHIPS CURRENTLY. INT VEC VALUE IS TRASHED! +; +SIO_PROBE: + ; CLEAR THE PRESENCE BITMAP + LD HL,SIO_MAP ; HL POINTS TO BITMAP + XOR A ; ZERO + LD (SIO_MAP),A ; CLEAR CHIP PRESENT BITMAP + ; INIT THE INT VEC REGISTER OF ALL POSSIBLE CHIPS + ; TO ZERO. A IS STILL ZERO. + LD B,2 ; WR2 REGISTER (INT VEC) + LD C,SIO0B_CMD ; FIRST CHIP + CALL SIO_WR ; WRITE ZERO TO CHIP REG +#IF (SIOCNT >= 2) + LD C,SIO1B_CMD ; SECOND CHIP + CALL SIO_WR ; WRITE ZERO TO CHIP REG +#ENDIF + ; FIRST POSSIBLE CHIP + LD C,SIO0B_CMD ; FIRST CHIP CMD/STAT PORT + CALL SIO_PROBECHIP ; PROBE IT + JR NZ,SIO_PROBE1 ; IF NOT ZERO, NOT FOUND + SET 0,(HL) ; SET BIT FOR FIRST CARD +SIO_PROBE1: +; +#IF (SIOCNT >= 2) + LD C,SIO1B_CMD ; SECOND CHIP CMD/STAT PORT + CALL SIO_PROBECHIP ; PROBE IT + JR NZ,SIO_PROBE2 ; IF NOT ZERO, NOT FOUND + SET 1,(HL) ; SET BIT FOR SECOND CARD +SIO_PROBE2: +#ENDIF +; + RET +; +SIO_PROBECHIP: + ; READ WR2 TO ENSURE IT IS ZERO (AVOID PHANTOM PORTS) + CALL SIO_RD ; GET VALUE + AND $F0 ; ONLY TOP NIBBLE + RET NZ ; ABORT IF NOT ZERO + ; WRITE INT VEC VALUE TO WR2 + LD A,$FF ; TEST VALUE + CALL SIO_WR ; WRITE IT + ; READ WR2 TO CONFIRM VALUE WRITTEN + CALL SIO_RD ; REREAD VALUE + AND $F0 ; ONLY TOP NIBBLE + CP $F0 ; COMPARE + RET ; DONE, Z IF FOUND, NZ IF MISCOMPARE +; +; READ/WRITE CHIP REGISTER. ENTER CHIP CMD/STAT PORT ADR IN C +; AND CHIP REGISTER NUMBER IN B. VALUE TO WRITE IN A OR VALUE +; RETURNED IN A. +; +SIO_WR: + OUT (C),B ; SELECT CHIP REGISTER + OUT (C),A ; WRITE VALUE + RET +; +SIO_RD: + OUT (C),B ; SELECT CHIP REGISTER + IN A,(C) ; GET VALUE + RET +; ; SIO DETECTION ROUTINE +; THERE IS ONLY ONE VARIATION OF SIO CHIP, SO HERE WE JUST CHECK THE +; CHIP PRESENCE BITMAP TO SET THE CHIP TYPE OF EITHER NONE OR SIO. ; SIO_DETECT: - LD C,(IY+3) ; COMMAND PORT - XOR A - OUT (C),A ; ACCESS RD0 - IN A,(C) ; GET RD0 VALUE - LD B,A ; SAVE IT - LD A,1 - OUT (C),A ; ACCESS RD1 - IN A,(C) ; GET RD1 VALUE - CP B ; COMPARE - LD A,SIO_NONE ; ASSUME NOTHING THERE - RET Z ; RD0=RD1 MEANS NOTHING THERE - LD A,SIO_SIO ; GUESS WE HAVE A VALID SIO HERE + LD B,(IY+2) ; GET CHIP/CHANNEL + SRL B ; SHIFT AWAY THE CHANNEL BIT + INC B ; NUMBER OF TIMES TO ROTATE BITS + LD A,(SIO_MAP) ; BIT MAP IN A +SIO_DETECT1: + ; ROTATE DESIRED CHIP BIT INTO CF + RRA ; ROTATE NEXT BIT INTO CF + DJNZ SIO_DETECT1 ; DO THIS UNTIL WE HAVE DESIRED BIT + ; RETURN CHIP TYPE + LD A,SIO_NONE ; ASSUME NOTHING HERE + RET NC ; IF CF NOT SET, RETURN + LD A,SIO_SIO ; CHIP TYPE IS SIO RET ; DONE ; ; @@ -691,8 +905,8 @@ SIO_PRTCFG: RET Z ; IF ZERO, NOT PRESENT ; PRTS(" MODE=$") ; FORMATTING - LD E,(IY+4) ; LOAD CONFIG - LD D,(IY+5) ; ... WORD TO DE + LD E,(IY+5) ; LOAD CONFIG + LD D,(IY+6) ; ... WORD TO DE CALL PS_PRTSC0 ; PRINT CONFIG ; XOR A @@ -701,8 +915,8 @@ SIO_PRTCFG: ; ; SIO_TYPE_MAP: - .DW SIO_STR_NONE - .DW SIO_STR_SIO + .DW SIO_STR_NONE + .DW SIO_STR_SIO SIO_STR_NONE .DB "$" SIO_STR_SIO .DB "SIO$" @@ -710,51 +924,109 @@ SIO_STR_SIO .DB "SIO$" ; WORKING VARIABLES ; SIO_DEV .DB 0 ; DEVICE NUM USED DURING INIT +SIO_MAP .DB 0 ; CHIP PRESENCE BITMAP ; #IF (INTMODE == 0) ; -SIOA_RCVBUF .EQU 0 -SIOB_RCVBUF .EQU 0 +SIO0A_RCVBUF .EQU 0 +SIO0B_RCVBUF .EQU 0 +; +#IF (SIOCNT >= 2) +SIO1A_RCVBUF .EQU 0 +SIO1B_RCVBUF .EQU 0 +#ENDIF ; #ELSE ; -; CHANNEL A RECEIVE BUFFER -SIOA_RCVBUF: -SIOA_CNT .DB 0 ; CHARACTERS IN RING BUFFER -SIOA_HD .DW SIOA_BUF ; BUFFER HEAD POINTER -SIOA_TL .DW SIOA_BUF ; BUFFER TAIL POINTER -SIOA_BUF .FILL 32,0 ; RECEIVE RING BUFFER -SIOA_BUFEND .EQU $ ; END OF BUFFER -SIOA_BUFSZ .EQU $ - SIOA_BUF ; SIZE OF RING BUFFER +; SIO0 CHANNEL A RECEIVE BUFFER +SIO0A_RCVBUF: +SIO0A_CNT .DB 0 ; CHARACTERS IN RING BUFFER +SIO0A_HD .DW SIO0A_BUF ; BUFFER HEAD POINTER +SIO0A_TL .DW SIO0A_BUF ; BUFFER TAIL POINTER +SIO0A_BUF .FILL SIO_BUFSZ,0 ; RECEIVE RING BUFFER +; +; SIO0 CHANNEL B RECEIVE BUFFER +SIO0B_RCVBUF: +SIO0B_CNT .DB 0 ; CHARACTERS IN RING BUFFER +SIO0B_HD .DW SIO0B_BUF ; BUFFER HEAD POINTER +SIO0B_TL .DW SIO0B_BUF ; BUFFER TAIL POINTER +SIO0B_BUF .FILL SIO_BUFSZ,0 ; RECEIVE RING BUFFER +; +#IF (SIOCNT >= 2) +; +; SIO1 CHANNEL A RECEIVE BUFFER +SIO1A_RCVBUF: +SIO1A_CNT .DB 0 ; CHARACTERS IN RING BUFFER +SIO1A_HD .DW SIO1A_BUF ; BUFFER HEAD POINTER +SIO1A_TL .DW SIO1A_BUF ; BUFFER TAIL POINTER +SIO1A_BUF .FILL SIO_BUFSZ,0 ; RECEIVE RING BUFFER +; +; SIO1 CHANNEL B RECEIVE BUFFER +SIO1B_RCVBUF: +SIO1B_CNT .DB 0 ; CHARACTERS IN RING BUFFER +SIO1B_HD .DW SIO1B_BUF ; BUFFER HEAD POINTER +SIO1B_TL .DW SIO1B_BUF ; BUFFER TAIL POINTER +SIO1B_BUF .FILL SIO_BUFSZ,0 ; RECEIVE RING BUFFER ; -; CHANNEL B RECEIVE BUFFER -SIOB_RCVBUF: -SIOB_CNT .DB 0 ; CHARACTERS IN RING BUFFER -SIOB_HD .DW SIOB_BUF ; BUFFER HEAD POINTER -SIOB_TL .DW SIOB_BUF ; BUFFER TAIL POINTER -SIOB_BUF .FILL 32,0 ; RECEIVE RING BUFFER -SIOB_BUFEND .EQU $ ; END OF BUFFER -SIOB_BUFSZ .EQU $ - SIOB_BUF ; SIZE OF RING BUFFER +#ENDIF ; #ENDIF ; ; SIO PORT TABLE ; SIO_CFG: - ; SIO CHANNEL A + ; SIO0 CHANNEL A +SIO0A_CFG: .DB 0 ; DEVICE NUMBER (SET DURING INIT) .DB 0 ; SIO TYPE (SET DURING INIT) - .DB 0 ; SIO CHANNEL (A) - .DB SIOA_CMD ; BASE PORT (CMD PORT) - .DW DEFSIOACFG ; LINE CONFIGURATION - .DW SIOA_RCVBUF ; POINTER TO RCV BUFFER STRUCT -; - ; SIO CHANNEL B + .DB $00 ; CHIP 0 / CHANNEL A (LOW BIT IS CHANNEL) + .DB SIO0A_CMD ; CMD/STATUS PORT + .DB SIO0A_DAT ; DATA PORT + .DW SIO0ACFG ; LINE CONFIGURATION + .DW SIO0A_RCVBUF ; POINTER TO RCV BUFFER STRUCT + .DW (SIO0ACLK / SIO0ADIV) & $FFFF ; CLOCK FREQ AS + .DW (SIO0ACLK / SIO0ADIV) >> 16 ; ... DWORD VALUE +; +SIO_CFGSIZ .EQU $ - SIO_CFG ; SIZE OF ONE CFG TABLE ENTRY +; + ; SIO0 CHANNEL B +SIO0B_CFG: .DB 0 ; DEVICE NUMBER (SET DURING INIT) .DB 0 ; SIO TYPE (SET DURING INIT) - .DB 1 ; SIO CHANNEL (B) - .DB SIOB_CMD ; BASE PORT (CMD PORT) - .DW DEFSIOBCFG ; LINE CONFIGURATION - .DW SIOB_RCVBUF ; POINTER TO RCV BUFFER STRUCT + .DB $01 ; CHIP 0 / CHANNEL B (LOW BIT IS CHANNEL) + .DB SIO0B_CMD ; CMD/STATUS PORT + .DB SIO0B_DAT ; DATA PORT + .DW SIO0BCFG ; LINE CONFIGURATION + .DW SIO0B_RCVBUF ; POINTER TO RCV BUFFER STRUCT + .DW (SIO0BCLK / SIO0BDIV) & $FFFF ; CLOCK FREQ AS + .DW (SIO0BCLK / SIO0BDIV) >> 16 ; ... DWORD VALUE +; +#IF (SIOCNT >= 2) +; + ; SIO1 CHANNEL A +SIO1A_CFG: + .DB 0 ; DEVICE NUMBER (SET DURING INIT) + .DB 0 ; SIO TYPE (SET DURING INIT) + .DB $02 ; CHIP 1 / CHANNEL A (LOW BIT IS CHANNEL) + .DB SIO1A_CMD ; CMD/STATUS PORT + .DB SIO1A_DAT ; DATA PORT + .DW SIO1ACFG ; LINE CONFIGURATION + .DW SIO1A_RCVBUF ; POINTER TO RCV BUFFER STRUCT + .DW (SIO1ACLK / SIO1ADIV) & $FFFF ; CLOCK FREQ AS + .DW (SIO1ACLK / SIO1ADIV) >> 16 ; ... DWORD VALUE +; + ; SIO1 CHANNEL B +SIO1B_CFG: + .DB 0 ; DEVICE NUMBER (SET DURING INIT) + .DB 0 ; SIO TYPE (SET DURING INIT) + .DB $03 ; CHIP 1 / CHANNEL B (LOW BIT IS CHANNEL) + .DB SIO1B_CMD ; CMD/STATUS PORT + .DB SIO1B_DAT ; DATA PORT + .DW SIO1BCFG ; LINE CONFIGURATION + .DW SIO1B_RCVBUF ; POINTER TO RCV BUFFER STRUCT + .DW (SIO1BCLK / SIO1BDIV) & $FFFF ; CLOCK FREQ AS + .DW (SIO1BCLK / SIO1BDIV) >> 16 ; ... DWORD VALUE +; +#ENDIF ; -SIO_CNT .EQU ($ - SIO_CFG) / 8 +SIO_CFGCNT .EQU ($ - SIO_CFG) / SIO_CFGSIZ diff --git a/Source/HBIOS/siobaud.inc b/Source/HBIOS/siobaud.inc deleted file mode 100644 index f9c77746..00000000 --- a/Source/HBIOS/siobaud.inc +++ /dev/null @@ -1,393 +0,0 @@ -; -; SIOBAUD1, SIOBAUD2, SIOBAUD3, SIOBAUD4 ARE SET TO THE -; ENCODED VALUE OF EACH POSSIBLE BAUD RATE WITH A FIXED CLOCK. -; - -#IF (DEFSIOCLK/DEFSIODIV/1 == 75) -SIOBAUD1 .EQU 0 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 150) -SIOBAUD1 .EQU 1 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 300) -SIOBAUD1 .EQU 2 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 600) -SIOBAUD1 .EQU 3 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 1200) -SIOBAUD1 .EQU 4 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 2400) -SIOBAUD1 .EQU 5 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 4800) -SIOBAUD1 .EQU 6 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 9600) -SIOBAUD1 .EQU 7 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 19200) -SIOBAUD1 .EQU 8 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 38400) -SIOBAUD1 .EQU 9 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 76800) -SIOBAUD1 .EQU 10 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 153600) -SIOBAUD1 .EQU 11 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 307200) -SIOBAUD1 .EQU 12 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 614400) -SIOBAUD1 .EQU 13 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 ==1228800) -SIOBAUD1 .EQU 14 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 ==2457600) -SIOBAUD1 .EQU 15 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 225) -SIOBAUD1 .EQU 16 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 450) -SIOBAUD1 .EQU 17 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 900) -SIOBAUD1 .EQU 18 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 1800) -SIOBAUD1 .EQU 19 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 3600) -SIOBAUD1 .EQU 20 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 7200) -SIOBAUD1 .EQU 21 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 14400) -SIOBAUD1 .EQU 22 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 28800) -SIOBAUD1 .EQU 23 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 57600) -SIOBAUD1 .EQU 24 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 115200) -SIOBAUD1 .EQU 25 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 230400) -SIOBAUD1 .EQU 26 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 460800) -SIOBAUD1 .EQU 27 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 == 921600) -SIOBAUD1 .EQU 28 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 ==1843200) -SIOBAUD1 .EQU 29 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 ==3686400) -SIOBAUD1 .EQU 30 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/1 ==7372800) -SIOBAUD1 .EQU 31 -#ENDIF - - -#IF (DEFSIOCLK/DEFSIODIV/16 == 75) -SIOBAUD2 .EQU 0 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 150) -SIOBAUD2 .EQU 1 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 300) -SIOBAUD2 .EQU 2 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 600) -SIOBAUD2 .EQU 3 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 1200) -SIOBAUD2 .EQU 4 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 2400) -SIOBAUD2 .EQU 5 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 4800) -SIOBAUD2 .EQU 6 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 9600) -SIOBAUD2 .EQU 7 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 19200) -SIOBAUD2 .EQU 8 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 38400) -SIOBAUD2 .EQU 9 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 76800) -SIOBAUD2 .EQU 10 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 153600) -SIOBAUD2 .EQU 11 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 307200) -SIOBAUD2 .EQU 12 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 614400) -SIOBAUD2 .EQU 13 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 ==1228800) -SIOBAUD2 .EQU 14 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 ==2457600) -SIOBAUD2 .EQU 15 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 225) -SIOBAUD2 .EQU 16 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 450) -SIOBAUD2 .EQU 17 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 900) -SIOBAUD2 .EQU 18 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 1800) -SIOBAUD2 .EQU 19 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 3600) -SIOBAUD2 .EQU 20 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 7200) -SIOBAUD2 .EQU 21 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 14400) -SIOBAUD2 .EQU 22 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 28800) -SIOBAUD2 .EQU 23 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 57600) -SIOBAUD2 .EQU 24 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 115200) -SIOBAUD2 .EQU 25 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 230400) -SIOBAUD2 .EQU 26 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 460800) -SIOBAUD2 .EQU 27 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 == 921600) -SIOBAUD2 .EQU 28 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 ==1843200) -SIOBAUD2 .EQU 29 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 ==3686400) -SIOBAUD2 .EQU 30 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/16 ==7372800) -SIOBAUD2 .EQU 31 -#ENDIF - -#IF (DEFSIOCLK/DEFSIODIV/32== 75) -SIOBAUD3 .EQU 0 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 150) -SIOBAUD3 .EQU 1 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 300) -SIOBAUD3 .EQU 2 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 600) -SIOBAUD3 .EQU 3 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 1200) -SIOBAUD3 .EQU 4 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 2400) -SIOBAUD3 .EQU 5 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 4800) -SIOBAUD3 .EQU 6 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 9600) -SIOBAUD3 .EQU 7 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 19200) -SIOBAUD3 .EQU 8 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 38400) -SIOBAUD3 .EQU 9 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 76800) -SIOBAUD3 .EQU 10 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 153600) -SIOBAUD3 .EQU 11 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 307200) -SIOBAUD3 .EQU 12 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 614400) -SIOBAUD3 .EQU 13 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32==1228800) -SIOBAUD3 .EQU 14 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32==2457600) -SIOBAUD3 .EQU 15 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 225) -SIOBAUD3 .EQU 16 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 450) -SIOBAUD3 .EQU 17 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 900) -SIOBAUD3 .EQU 18 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 1800) -SIOBAUD3 .EQU 19 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 3600) -SIOBAUD3 .EQU 20 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 7200) -SIOBAUD3 .EQU 21 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 14400) -SIOBAUD3 .EQU 22 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 28800) -SIOBAUD3 .EQU 23 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 57600) -SIOBAUD3 .EQU 24 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 115200) -SIOBAUD3 .EQU 25 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 230400) -SIOBAUD3 .EQU 26 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 460800) -SIOBAUD3 .EQU 27 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32== 921600) -SIOBAUD3 .EQU 28 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32==1843200) -SIOBAUD3 .EQU 29 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32==3686400) -SIOBAUD3 .EQU 30 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/32==7372800) -SIOBAUD3 .EQU 31 -#ENDIF - -#IF (DEFSIOCLK/DEFSIODIV/64== 75) -SIOBAUD4 .EQU 0 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 150) -SIOBAUD4 .EQU 1 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 300) -SIOBAUD4 .EQU 2 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 600) -SIOBAUD4 .EQU 3 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 1200) -SIOBAUD4 .EQU 4 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 2400) -SIOBAUD4 .EQU 5 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 4800) -SIOBAUD4 .EQU 6 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 9600) -SIOBAUD4 .EQU 7 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 19200) -SIOBAUD4 .EQU 8 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 38400) -SIOBAUD4 .EQU 9 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 76800) -SIOBAUD4 .EQU 10 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 153600) -SIOBAUD4 .EQU 11 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 307200) -SIOBAUD4 .EQU 12 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 614400) -SIOBAUD4 .EQU 13 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64==1228800) -SIOBAUD4 .EQU 14 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64==2457600) -SIOBAUD4 .EQU 15 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 225) -SIOBAUD4 .EQU 16 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 450) -SIOBAUD4 .EQU 17 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 900) -SIOBAUD4 .EQU 18 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 1800) -SIOBAUD4 .EQU 19 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 3600) -SIOBAUD4 .EQU 20 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 7200) -SIOBAUD4 .EQU 21 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 14400) -SIOBAUD4 .EQU 22 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 28800) -SIOBAUD4 .EQU 23 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 57600) -SIOBAUD4 .EQU 24 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 115200) -SIOBAUD4 .EQU 25 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 230400) -SIOBAUD4 .EQU 26 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 460800) -SIOBAUD4 .EQU 27 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64== 921600) -SIOBAUD4 .EQU 28 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64==1843200) -SIOBAUD4 .EQU 29 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64==3686400) -SIOBAUD4 .EQU 30 -#ENDIF -#IF (DEFSIOCLK/DEFSIODIV/64==7372800) -SIOBAUD4 .EQU 31 -#ENDIF diff --git a/Source/HBIOS/spk.asm b/Source/HBIOS/spk.asm index fea556c7..e1575660 100644 --- a/Source/HBIOS/spk.asm +++ b/Source/HBIOS/spk.asm @@ -6,31 +6,258 @@ SPK_INIT: CALL NEWLINE ; FORMATTING PRTS("SPK: IO=0x$") - LD A,DSRTC_BASE + LD A,RTCIO CALL PRTHEXBYTE - CALL SPK_BEEP - XOR A + CALL SPK_SETTBL + CALL SPK_BEEP ; PLAY A NOTE + XOR A RET ; -SPK_BEEP: - PUSH DE +; SETUP THE SPEAKER NOTE TABLE ACCORDING TO THE CPU SPEED. +; FREQUENCY ACCURACY DECREASES AS CLOCK SPEED MULITPLIER INCREASES. +; 1MHZ ERROR MAY OCCUR IF CPU CLOCK IS UNDER. I.E 3.999 = 3MHZ + +SPK_SETTBL: + LD A,(CB_CPUMHZ) ; GET CPU SPEED. + LD C,A + + LD B,SPK_NOTCNT ; SET NUMBER OF NOTES TO + LD HL,SPK_TUNTBL+2 ; ADJUST AND START POINT + +SPK_SETTBL2: PUSH HL - LD HL,400 ; CYCLES OF TONE - ;LD B,%00000100 ; D2 MAPPED TO Q0 - LD A,DSRTC_RESET - OR %00000100 ; D2 MAPPED TO Q0 - LD B,A -SPK_BEEP1: - LD A,B - OUT (DSRTC_BASE),A - XOR %00000100 + LD A,(HL) ; READ + LD E,A ; IN + INC HL ; THE + LD A,(HL) ; 1MHZ + LD D,A ; NOTE + + PUSH BC + LD B,C + LD HL,0 ; MULTIPLY +SPK_SETTBL1: ; 1MHZ NOTE + ADD HL,DE ; VALUE BY + DJNZ SPK_SETTBL1 ; SYSTEM MHZ + POP BC +; + LD DE,30 ; ADD OVEREAD + ADD HL,DE ; COMPENSATION +; + POP DE ; RECALL NOTE + EX DE,HL ; ADDRESS +; + LD (HL),E ; SAVE + INC HL ; THE + LD (HL),D ; NEW + INC HL ; NOTE + INC HL ; AND MOVE + INC HL ; TO NEXT + + DJNZ SPK_SETTBL2 ; NEXT NOTE + RET + +SPK_BEEP: + LD HL,SPK_NOTE_C8 ; SELECT NOTE +; + LD A,(HL) ; LOAD 1ST ARG + INC HL ; IN DE + LD E,A + LD A,(HL) + INC HL + LD D,A +; + LD A,(HL) ; LOAD 2ND ARG + INC HL ; IN BC + LD C,A + LD A,(HL) + INC HL LD B,A - LD DE,17 - CALL VDELAY - DEC HL - LD A,H - OR L - JR NZ,SPK_BEEP1 + PUSH BC ; SETUP ARG IN HL POP HL - POP DE +; + CALL SPK_BEEPER ; PLAY +; RET +; +; The following SPK_BEEPER routine is a modification of code from +; "The Complete SPECTRUM ROM DISSASSEMBLY" by Dr Ian Logan & Dr Frank O’Hara +; +; https://www.esocop.org/docs/CompleteSpectrumROMDisassemblyThe.pdf +; +; DE Number of passes to make through the sound generation loop +; HL Loop delay parameter +; +SPK_BEEPER: + PUSH IX + DI ; Disable the interrupt for the duration of a 'beep'. + LD A,L ; Save L temporarily. + SRL L ; Each '1' in the L register is to count 4 T states, but take INT (L/4) and count 16 T states instead. + SRL L + CPL ; Go back to the original value in L and find how many were lost by taking 3-(A mod 4). + AND $03 + LD C,A + LD B,$00 + LD IX,SPK_DLYADJ ; The base address of the timing loop. + ADD IX,BC ; Alter the length of the timing loop. Use an earlier starting point for each '1' lost by taking INT (L/4). + LD A,(RTCVAL) ; Fetch the present border colour from BORDCR and move it to bits 2, 1 and 0 of the A register. +; +; The HL register holds the 'length of the timing loop' with 16 T states being used for each '1' in the L register and 1024 T states for each '1' in the H register. +; +SPK_DLYADJ: + NOP ; Add 4 T states for each earlier entry point that is used. + NOP + NOP + INC B ; The values in the B and C registers will come from the H and L registers - see below. + INC C +BE_H_L_LP: + DEC C ; The 'timing loop', i.e. BC*4 T states. (But note that at the half-cycle point, C will be equal to L+1.) + JR NZ,BE_H_L_LP + LD C,$3F + DEC B + JP NZ,BE_H_L_LP +; +; The loudspeaker is now alternately activated and deactivated. +; + XOR %00000100 ; Flip bit 2. + OUT (RTCIO),A ; Perform the 'OUT' operation, leaving other bits unchanged. + LD B,H ; Reset the B register. + LD C,A ; Save the A register. + BIT 4,A ; Jump if at the half-cycle point. + JR NZ,BE_AGAIN +; +; After a full cycle the DE register pair is tested. +; + LD A,D ; Jump forward if the last complete pass has been made already. + OR E + JR Z,BE_END + LD A,C ; Fetch the saved value. + LD C,L ; Reset the C register. + DEC DE ; Decrease the pass counter. + JP (IX) ; Jump back to the required starting location of the loop. +; +; The parameters for the second half-cycle are set up. +; +BE_AGAIN: + LD C,L ; Reset the C register. + INC C ; Add 16 T states as this path is shorter. + JP (IX) ; Jump back. +BE_END: + EI + POP IX + RET +; +; STANDARD ONE SECOND TONE TABLES AT 1MHZ (UNCOMPENSATED). FOR SPK_BEEPER, FIRST WORD LOADED INTO DE, SECOND INTO HL +; +; EXCEL SPREADSHEET FOR CALCULATION CAN BE FOUND HERE: +; +; https://www.retrobrewcomputers.org/lib/exe/fetch.php?media=boards:sbc:sbc_v2:sbc_v2-004:spk_beep_tuntbl.xlsx +; +SPK_TUNTBL: + .DW $13, $191A ; D + .DW $14, $17B3 ; E0 + .DW $15, $165E ; F0 + .DW $17, $151E ; F + .DW $18, $13EE ; G0 + .DW $19, $12CF ; G + .DW $1B, $11C1 ; A0 + .DW $1D, $10C1 ; A + .DW $1E, $FD1 ; B0 + .DW $20, $EEE ; C1 + .DW $22, $E17 ; C + .DW $24, $D4D ; D1 + .DW $26, $C8E ; D + .DW $29, $BD9 ; E1 + .DW $2B, $B2F ; F1 + .DW $2E, $A8E ; F + .DW $31, $9F7 ; G1 + .DW $33, $968 ; G + .DW $37, $8E0 ; A1 + .DW $3A, $861 ; A + .DW $3D, $7E8 ; B1 + .DW $41, $777 ; C2 + .DW $45, $70B ; C + .DW $49, $6A6 ; D2 + .DW $4D, $647 ; D + .DW $52, $5EC ; E2 + .DW $57, $597 ; F2 + .DW $5C, $547 ; F + .DW $62, $4FB ; G2 + .DW $67, $4B3 ; G + .DW $6E, $470 ; A2 + .DW $74, $430 ; A + .DW $7B, $3F4 ; B2 + .DW $82, $3BB ; C3 + .DW $8A, $385 ; C + .DW $92, $353 ; D3 + .DW $9B, $323 ; D + .DW $A4, $2F6 ; E3 + .DW $AE, $2CB ; F3 + .DW $B9, $2A3 ; F + .DW $C4, $27D ; G3 + .DW $CF, $259 ; G + .DW $DC, $238 ; A3 + .DW $E9, $218 ; A + .DW $F6, $1FA ; B3 + .DW $105, $1DD ; C4 + .DW $115, $1C2 ; C + .DW $125, $1A9 ; D4 + .DW $137, $191 ; D + .DW $149, $17B ; E4 + .DW $15D, $165 ; F4 + .DW $171, $151 ; F + .DW $188, $13E ; G4 + .DW $19F, $12C ; G + .DW $1B8, $11C ; A4 + .DW $1D2, $10C ; A + .DW $1ED, $FD ; B4 + .DW $20B, $EE ; C5 + .DW $22A, $E1 ; C + .DW $24B, $D4 ; D5 + .DW $26E, $C8 ; D + .DW $293, $BD ; E5 + .DW $2BA, $B2 ; F5 + .DW $2E3, $A8 ; F + .DW $30F, $9F ; G5 + .DW $33E, $96 ; G + .DW $370, $8E ; A5 + .DW $3A4, $86 ; A + .DW $3DB, $7E ; B5 + .DW $416, $77 ; C6 + .DW $454, $70 ; C + .DW $496, $6A ; D6 + .DW $4DC, $64 ; D + .DW $526, $5E ; E6 + .DW $574, $59 ; F6 + .DW $5C7, $54 ; F + .DW $61F, $4F ; G6 + .DW $67D, $4B ; G + .DW $6E0, $47 ; A6 + .DW $748, $43 ; A + .DW $7B7, $3F ; B6 + .DW $82D, $3B ; C7 + .DW $8A9, $38 ; C + .DW $92D, $35 ; D7 + .DW $9B9, $32 ; D + .DW $A4D, $2F ; E7 + .DW $AE9, $2C ; F7 + .DW $B8F, $2A ; F + .DW $C3F, $27 ; G7 + .DW $CFA, $25 ; G + .DW $DC0, $23 ; A7 + .DW $E91, $21 ; A + .DW $F6F, $1F ; B7 +SPK_NOTE_C8: + .DW $105A, $1D ; C8 + .DW $1152, $1C ; C + .DW $125A, $1A ; D8 + .DW $1372, $19 ; D + .DW $149A, $17 ; E8 + .DW $15D3, $16 ; F8 + .DW $171F, $15 ; F + .DW $187F, $13 ; G8 + .DW $19F4, $12 ; G + .DW $1B80, $11 ; A8 + .DW $1D22, $10 ; A + .DW $1EDE, $F ; B8 + +SPK_NOTCNT .EQU ($-SPK_TUNTBL) / 4 diff --git a/Source/HBIOS/std.asm b/Source/HBIOS/std.asm index b670d283..f0aa880d 100644 --- a/Source/HBIOS/std.asm +++ b/Source/HBIOS/std.asm @@ -1,30 +1,35 @@ ; The purpose of this file is to define generic symbols and to include -; the appropriate std-*.inc file to bring in platform specifics. +; the requested build configuraton file to bring in platform specifics. -; There are several classes of systems supported by SBC. -; 1. SBC Z80 SBC (v1 or v2) w/ ECB interface -; 2. ZETA Standalone Z80 SBC w/ SBC compatibility -; 3. ZETA2 Second version of ZETA with enhanced memory bank switching -; 4. N8 MSX-compatible Z180 SBC w/ onboard video and sound -; 5. MK4 Mark IV Z180 based SBC w/ ECB interface -; 6. UNA Any Z80/Z180 computer with UNA BIOS -; 7. RC RC2014 based system with SMB 512K RAM/ROM card - -; All the classes require certain generic definitions, and these are -; defined here prior to the inclusion of platform specific .inc files. - -; It is unfortunate, but all the possible config items must be defined -; here because the config gets read before the specific std-*.inc's +; There are several hardware platforms supported by SBC. +; 1. SBC Z80 SBC (v1 or v2) w/ ECB interface +; 2. ZETA Standalone Z80 SBC w/ SBC compatibility +; 3. ZETA2 Second version of ZETA with enhanced memory bank switching +; 4. N8 MSX-ish Z180 SBC w/ onboard video and sound +; 5. MK4 Mark IV Z180 based SBC w/ ECB interface +; 6. UNA Any Z80/Z180 computer with UNA BIOS +; 7. RCZ80 RC2014 based system with 512K banked RAM/ROM card +; 8. RCZ180 RC2014 based system with Z180 CPU +; 9. EZZ80 Easy Z80, Z80 SBC w/ RC2014 bus and CTC +; 10. SCZ180 Steve Cousins Z180 based system +; 11. DYNO Steve Garcia's Dyno Micro-ATX Motherboard ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; INCLUDE VERSION ; -#INCLUDE "ver.inc" ; ADD BIOSVER +#INCLUDE "../ver.inc" ; ADD BIOSVER ; FALSE .EQU 0 TRUE .EQU ~FALSE ; +; DEBUGGING OPTIONS +; +USENONE .EQU 0 ; NO DEBUG +USEXIO .EQU 1 ; BASIC SERIAL DRIVER +USEMIO .EQU 2 ; MEMORY BUFFER DRIVER +WBWDEBUG .EQU USENONE +; ; PRIMARY HARDWARE PLATFORMS ; PLT_SBC .EQU 1 ; SBC ECB Z80 SBC @@ -33,18 +38,49 @@ PLT_ZETA2 .EQU 3 ; ZETA Z80 V2 SBC PLT_N8 .EQU 4 ; N8 (HOME COMPUTER) Z180 SBC PLT_MK4 .EQU 5 ; MARK IV PLT_UNA .EQU 6 ; UNA BIOS -PLT_RC .EQU 7 ; RC2014 -PLT_RC180 .EQU 8 ; RC2014 W/ Z180 +PLT_RCZ80 .EQU 7 ; RC2014 W Z80 +PLT_RCZ180 .EQU 8 ; RC2014 W/ Z180 +PLT_EZZ80 .EQU 9 ; EASY Z80 +PLT_SCZ180 .EQU 10 ; SCZ180 +PLT_DYNO .EQU 11 ; DYNO MICRO-ATX MOTHERBOARD ; -#IF (PLATFORM != PLT_UNA) -#INCLUDE "hbios.inc" -#ENDIF +; +; CPU TYPES +; +CPU_NONE .EQU 0 ; NO CPU TYPE DEFINED +CPU_Z80 .EQU 1 ; Z80 FAMILY +CPU_Z180 .EQU 2 ; Z180 FAMILY +CPU_Z280 .EQU 3 ; Z280 FAMILY +; +; BIOS MODE +; +BIOS_NONE .EQU 0 ; NO BIOS TYPE DEFINED +BIOS_WBW .EQU 1 ; ROMWBW HBIOS +BIOS_UNA .EQU 2 ; UNA UBIOS +; +; MEMORY MANAGERS +; +MM_NONE .EQU 0 +MM_SBC .EQU 1 ; ORIGINAL N8VEM/RBC Z80 SBC BANKED MEMORY +MM_Z2 .EQU 2 ; 16K X 4 BANKED MEMORY INTRODUCED ON ZETA2 +MM_N8 .EQU 3 ; Z180 CUSTOMIZED FOR N8 MEMORY EXTENSIONS +MM_Z180 .EQU 4 ; Z180 NATIVE MEMORY MANAGER ; ; BOOT STYLE ; BT_MENU .EQU 1 ; WAIT FOR MENU SELECTION AT LOADER PROMPT BT_AUTO .EQU 2 ; AUTO SELECT BOOT_DEFAULT AFTER BOOT_TIMEOUT ; +; BOOT RECOVERY METHODS +; +BT_REC_NONE .EQU 0 ; NO RECOVERY MODE +BT_REC_FORCE .EQU 1 ; FORCE BOOT RECOVERY MODE +BT_REC_SBC01 .EQU 2 ; ECB-SBCV2 - BIT 1 RTC HIGH +BT_REC_SBC1B .EQU 3 ; ECB-SBCV2 - 1-BIT IO PORT +BT_REC_SBCRI .EQU 4 ; ECB-SBCV2 - 16550 UART RING INDICATOR LINE +; +BT_REC_TYPE .EQU BT_REC_NONE ; BOOT RECOVERY METHOD TO USE +; ; FLOPPY DISK MEDIA SELECTIONS (ID'S MUST BE INDEX OF ENTRY IN FCD_TBL) ; FDM720 .EQU 0 ; 3.5" FLOPPY, 720KB, 2 SIDES, 80 TRKS, 9 SECTORS @@ -66,6 +102,14 @@ MID_FD360 .EQU 7 MID_FD120 .EQU 8 MID_FD111 .EQU 9 ; +; ZILOG CTC MODE SELECTIONS +; +CTCMODE_NONE .EQU 0 ; NO CTC +CTCMODE_ZP .EQU 1 ; ZILOG PERIPHERALS ECB CTC +CTCMODE_Z2 .EQU 2 ; ZETA2 ONBOARD CTC +CTCMODE_EZ .EQU 3 ; EASY Z80 ONBOARD CTC +CTCMODE_RC .EQU 4 ; RC2014 CTC MODULE (ALSO KIO) +; ; DS RTC MODE SELECTIONS ; DSRTCMODE_NONE .EQU 0 ; NO DSRTC @@ -97,16 +141,15 @@ FDMODE_N8 .EQU 5 ; N8 FDMODE_DIO3 .EQU 6 ; DISKIO V3 FDMODE_RCSMC .EQU 7 ; RC2014 SMC 9266 @ $40 (SCOTT BAKER) FDMODE_RCWDC .EQU 8 ; RC2014 WDC 37C65 @ $40 (SCOTT BAKER) - +FDMODE_DYNO .EQU 9 ; DYNO WDC 37C65 @ $84 ; ; IDE MODE SELECTIONS ; IDEMODE_NONE .EQU 0 IDEMODE_DIO .EQU 1 ; DISKIO V1 IDEMODE_DIDE .EQU 2 ; DUAL IDE -IDEMODE_MK4 .EQU 3 ; MARK IV ONBOARD IDE (8 BIT) -IDEMODE_RC .EQU 4 ; RC2014 CF MODULE (8 BIT) @ $10 (SPENCER OWEN) -IDEMODE_SMB .EQU 5 ; RC2014 IDE MODULE (8 BIT) @ $E0 (SCOTT BAKER) +IDEMODE_MK4 .EQU 3 ; MARK IV ONBOARD IDE (8 BIT ONLY) +IDEMODE_RC .EQU 4 ; RC2014 CF MODULE (8 BIT ONLY) ; ; PPIDE MODE SELECTIONS ; @@ -116,6 +159,7 @@ PPIDEMODE_DIO3 .EQU 2 ; DISKIO V3 PARALLEL PORT PPIDEMODE_MFP .EQU 3 ; MULTIFUNCTION / PIC PPIDEMODE_N8 .EQU 4 ; MULTIFUNCTION / PIC PPIDEMODE_RC .EQU 5 ; RC2014 PPIDE MODULE @ $20 (ED BRINDLEY) +PPIDEMODE_DYNO .EQU 6 ; DYNO PPIDE @ $4C ; ; SD MODE SELECTIONS ; @@ -127,6 +171,22 @@ SDMODE_PPI .EQU 4 ; PPISD MINI BOARD SDMODE_UART .EQU 5 ; SD INTERFACE VIA UART SDMODE_DSD .EQU 6 ; DUAL SD SDMODE_MK4 .EQU 7 ; MARK IV +SDMODE_SC .EQU 8 ; SC (Steve Cousins) +SDMODE_MT .EQU 9 ; MT (Shift register SPI WIZNET for RC2014) +; +; SOUND CHIP MODE SELECTIONS +; +AYMODE_NONE .EQU 0 +AYMODE_N8 .EQU 1 ; N8 BUILT-IN SOUND +AYMODE_SCG .EQU 2 ; SCG ECB BOARD +AYMODE_RCZ80 .EQU 3 ; RC2014 SOUND MODULE BY ED BRINDLEY ON Z80 +AYMODE_RCZ180 .EQU 4 ; RC2014 SOUND MODULE BY ED BRINDLEY ON Z180 +; +; TMS VIDEO MODE SELECTIONS +; +TMSMODE_NONE .EQU 0 +TMSMODE_SCG .EQU 1 ; SCG ECB BOARD +TMSMODE_N8 .EQU 2 ; N8 BUILT-IN VIDEO ; ; SERIAL DEVICE CONFIGURATION CONSTANTS ; @@ -172,7 +232,6 @@ SER_BAUD7200 .EQU $15 << 8 SER_BAUD14400 .EQU $16 << 8 SER_BAUD28800 .EQU $17 << 8 SER_BAUD57600 .EQU $18 << 8 - SER_BAUD115200 .EQU $19 << 8 SER_BAUD230400 .EQU $1A << 8 SER_BAUD460800 .EQU $1B << 8 @@ -218,112 +277,85 @@ SER_1843200_8N1 .EQU SER_BAUD1843200 | SER_DATA8 | SER_PARNONE | SER_STOP1 SER_3686400_8N1 .EQU SER_BAUD3686400 | SER_DATA8 | SER_PARNONE | SER_STOP1 SER_7372800_8N1 .EQU SER_BAUD7372800 | SER_DATA8 | SER_PARNONE | SER_STOP1 ; -; ECB-VDU MODES -; -V80X24 .EQU 0 -V80X25 .EQU 1 -V80X30 .EQU 2 -V80X25B .EQU 3 -V80X24B .EQU 4 -; -; INTERRUPT VECTOR TABLE ENTRY OFFSETS (Z180 COMPATIBLE) -; -IVT_INT1 .EQU 0 -IVT_INT2 .EQU 2 -IVT_TIM0 .EQU 4 -IVT_TIM1 .EQU 6 -IVT_DMA0 .EQU 8 -IVT_DMA1 .EQU 10 -IVT_CSIO .EQU 12 -IVT_SER0 .EQU 14 -IVT_SER1 .EQU 16 -IVT_PIO0 .EQU 18 -IVT_PIO1 .EQU 20 -IVT_PIO2 .EQU 22 -IVT_PIO3 .EQU 24 -; -#INCLUDE "build.inc" ; INCLUDE USER CONFIG, ADD VARIANT, TIMESTAMP, & ROMSIZE -; -#IF ((PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4) | (PLATFORM == PLT_RC180)) -#DEFINE CPU_Z180 -#ELSE -#DEFINE CPU_Z80 -#ENDIF +; TERMENABLE CONTROLS INCLUSION OF TERMINAL PSEUDO-DEVICE DRIVER +; IT IS SET TO TRUE BY THE INCLUSION OF ANY VDA DRIVER. ; -; SET PLATFORM NAME STRING +TERMENABLE .EQU FALSE ; TERM PSEUDO DEVICE, WILL AUTO-ENABLE IF A VDA IS ENABLED ; -#IF (PLATFORM == PLT_SBC) - #DEFINE PLATFORM_NAME "SBC Z80" -#ENDIF -#IF (PLATFORM == PLT_ZETA) - #DEFINE PLATFORM_NAME "ZETA Z80" -#ENDIF -#IF (PLATFORM == PLT_ZETA2) - #DEFINE PLATFORM_NAME "ZETA Z80 V2" -#ENDIF -#IF (PLATFORM == PLT_N8) - #DEFINE PLATFORM_NAME "N8 Z180" -#ENDIF -#IF (PLATFORM == PLT_MK4) - #DEFINE PLATFORM_NAME "MARK IV Z180" -#ENDIF -#IF (PLATFORM == PLT_UNA) - #DEFINE PLATFORM_NAME "UNA" -#ENDIF -#IF (PLATFORM == PLT_RC) - #DEFINE PLATFORM_NAME "RC2014 Z80" -#ENDIF -#IF (PLATFORM == PLT_RC180) - #DEFINE PLATFORM_NAME "RC2014 Z180" -#ENDIF +; VIDEO MODES ; -; INCLUDE PLATFORM SPECIFIC HARDWARE DEFINITIONS +V80X24 .EQU 0 ; ECB-VDU +V80X25 .EQU 1 ; ECB-VDU, ECB-VGA3 +V80X30 .EQU 2 ; ECB-VDU, ECB-VGA3 +V80X25B .EQU 3 ; ECB-VDU +V80X24B .EQU 4 ; ECB-VDU +V80X43 .EQU 5 ; ECB-VGA3 +V80X60 .EQU 6 ; ECB-VGA3 ; -#IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA) | (PLATFORM == PLT_ZETA2)) -#INCLUDE "plt_sbc.inc" -#ENDIF +; KEYBOARD LAYOUTS ; -#IF (PLATFORM == PLT_N8) -#INCLUDE "plt_n8.inc" -#ENDIF +KBD_US .EQU 0 ; US ENGLISH +KBD_DE .EQU 1 ; GERMAN ; -#IF (PLATFORM == PLT_MK4) -#INCLUDE "plt_mk4.inc" -#ENDIF +; EMULATION TYPES ; -#IF (PLATFORM == PLT_UNA) -#INCLUDE "plt_una.inc" -#ENDIF +EMUTYP_NONE .EQU 0 ; NONE +EMUTYP_TTY .EQU 1 ; TTY +EMUTYP_ANSI .EQU 2 ; ANSI +; +; DEVICE DRIVER TO BE INITIALIZED FIRST. FIRST CIO DRIVER, UNIT 0 INITIALIZED BECOMES PRIMARY CONSOLE. +; IS AN INDEX INTO THE ENABLED INITIALIZATION DRIVER LIST i.e. ASCI, UART, SIO, ACIA, PIO, UF ETC. +; EXAMPLE: IF ONLY UART, SIO AND PIO ARE ENABLE AND THE SIO IS DESIRED AS THE PRIMARY CONSOLE, +; SET FORCECON TO 2 IN YOUR CUSTOM CONFIGURATION FILE i.e. "FORCECON: .SET 2" +; +FORCECON .EQU 0 ; DEFAULT IS TO FOLLOW NORMAL SEQUENCE ; -#IF (PLATFORM == PLT_RC) -#INCLUDE "plt_rc.inc" +#INCLUDE "build.inc" ; INCLUDE USER CONFIG, ADD VARIANT, TIMESTAMP, & ROMSIZE +; +#IF (BIOS == BIOS_WBW) +#INCLUDE "hbios.inc" #ENDIF ; -#IF (PLATFORM == PLT_RC180) -#INCLUDE "plt_rc180.inc" +#IF (BIOS == BIOS_UNA) +#INCLUDE "../UBIOS/ubios.inc" #ENDIF ; -; SETUP DEFAULT CPU SPEED VALUES ; -CPUKHZ .EQU CPUOSC / 1000 ; CPU FREQ IN KHZ +; INCLUDE Z180 REGISTER DEFINITIONS ; -#IFDEF CPU_Z180 -#IF (Z180_CLKDIV == 0) -CPUKHZ .SET CPUKHZ / 2 ; ADJUST FOR HALF SPEED OPERATION -#ENDIF -#IF (Z180_CLKDIV == 2) -CPUKHZ .SET CPUKHZ * 2 ; ADJUST FOR DOUBLE SPEED OPERATION +#IF (BIOS == BIOS_WBW) + #IF (CPUFAM == CPU_Z180) + #INCLUDE "z180.inc" + #ENDIF #ENDIF +; +; SETUP DEFAULT CPU SPEED VALUES +; +CPUKHZ .EQU CPUOSC / 1000 ; CPU FREQ IN KHZ +; +#IF (BIOS == BIOS_WBW) + #IF (CPUFAM == CPU_Z180) + #IF (Z180_CLKDIV == 0) +CPUKHZ .SET CPUKHZ / 2 ; ADJUST FOR HALF SPEED OPERATION + #ENDIF + #IF (Z180_CLKDIV == 2) +CPUKHZ .SET CPUKHZ * 2 ; ADJUST FOR DOUBLE SPEED OPERATION + #ENDIF + #ENDIF #ENDIF ; -CPUMHZ .EQU CPUKHZ / 1000 ; CPU FREQ IN MHZ +CPUMHZ .EQU CPUKHZ / 1000 ; CPU FREQ IN MHZ +; +TICKSPERSEC .EQU 50 ; ; MEMORY BANK CONFIGURATION ; -#IF (PLATFORM == PLT_UNA) +#IF (BIOS == BIOS_UNA) BID_ROM0 .EQU $0000 BID_RAM0 .EQU $8000 -#ELSE +#ENDIF +; +#IF (BIOS == BIOS_WBW) BID_ROM0 .EQU $00 BID_RAM0 .EQU $80 #ENDIF @@ -375,8 +407,8 @@ CPM_ENT .EQU CBIOS_LOC ; CPM ENTRY POINT (IN CBIOS) LDR_SIZ .EQU $0E00 -MON_LOC .EQU $C000 ; LOCATION OF MONITOR FOR RUNNING SYSTEM -MON_SIZ .EQU $1000 ; SIZE OF MONITOR BINARY IMAGE +MON_LOC .EQU $F000 ; LOCATION OF MONITOR FOR RUNNING SYSTEM +MON_SIZ .EQU $0E00 ; SIZE OF MONITOR BINARY IMAGE MON_END .EQU MON_LOC + MON_SIZ ; END OF MONITOR BAS_LOC .EQU $0200 ; NASCOM BASIC @@ -387,7 +419,7 @@ TBC_LOC .EQU $0A00 ; TASTYBASIC TBC_SIZ .EQU $0900 TBC_END .EQU TBC_LOC + TBC_SIZ -EGG_LOC .EQU $0A00 ; EASTER EGG +EGG_LOC .EQU $F000 ; EASTER EGG EGG_SIZ .EQU $0200 EGG_END .EQU EGG_LOC + EGG_SIZ @@ -395,9 +427,63 @@ FTH_LOC .EQU $0200 ; CAMEL FORTH FTH_SIZ .EQU $1700 FTH_END .EQU FTH_LOC + FTH_SIZ +GAM_LOC .EQU $0200 ; GAME 2048 +GAM_SIZ .EQU $0900 +GAM_END .EQU GAM_LOC + GAM_SIZ + +USR_LOC .EQU $0200 ; USER +USR_SIZ .EQU $1000 +USR_END .EQU USR_LOC + USR_SIZ + MON_DSKY .EQU MON_LOC + (0 * 3) ; MONITOR ENTRY (DSKY) MON_SERIAL .EQU MON_LOC + (1 * 3) ; MONITOR ENTRY (SERIAL PORT) ; +; INTERRUPT MODE 2 SLOT ASSIGNMENTS +; +#IF (INTMODE == 2) + + #IF (CPUFAM == CPU_Z180) + +; Z180-BASED SYSTEMS + +INT_INT1 .EQU 0 ; Z180 INT 1 +INT_INT2 .EQU 1 ; Z180 INT 2 +INT_TIM0 .EQU 2 ; Z180 TIMER 0 +INT_TIM1 .EQU 3 ; Z180 TIMER 1 +INT_DMA0 .EQU 4 ; Z180 DMA 0 +INT_DMA1 .EQU 5 ; Z180 DMA 1 +INT_CSIO .EQU 6 ; Z180 CSIO +INT_SER0 .EQU 7 ; Z180 SERIAL 0 +INT_SER1 .EQU 8 ; Z180 SERIAL 0 +INT_PIO0A .EQU 9 ; ZILOG PIO 0, CHANNEL A +INT_PIO0B .EQU 10 ; ZILOG PIO 0, CHANNEL B +INT_PIO1A .EQU 11 ; ZILOG PIO 1, CHANNEL A +INT_PIO1B .EQU 12 ; ZILOG PIO 1, CHANNEL B +INT_SIO0 .EQU 13 ; ZILOG SIO 0, CHANNEL A & B +INT_SIO1 .EQU 14 ; ZILOG SIO 1, CHANNEL A & B + + #ELSE + +; Z80-BASED SYSTEMS + +INT_CTC0A .EQU 0 ; ZILOG CTC 0, CHANNEL A +INT_CTC0B .EQU 1 ; ZILOG CTC 0, CHANNEL B +INT_CTC0C .EQU 2 ; ZILOG CTC 0, CHANNEL C +INT_CTC0D .EQU 3 ; ZILOG CTC 0, CHANNEL D +INT_SIO0 .EQU 7 ; ZILOG SIO 0, CHANNEL A & B +INT_SIO1 .EQU 8 ; ZILOG SIO 1, CHANNEL A & B +INT_PIO0A .EQU 9 ; ZILOG PIO 0, CHANNEL A +INT_PIO0B .EQU 10 ; ZILOG PIO 0, CHANNEL B +INT_PIO1A .EQU 11 ; ZILOG PIO 1, CHANNEL A +INT_PIO1B .EQU 12 ; ZILOG PIO 1, CHANNEL B + + #ENDIF + +#DEFINE IVT(INTX) HB_IVT+(INTX * 4)+1 +#DEFINE VEC(INTX) INTX*2 + +#ENDIF +; ; HELPER MACROS ; #DEFINE PRTC(C) CALL PRTCH \ .DB C ; PRINT CHARACTER C TO CONSOLE - PRTC('X') diff --git a/Source/HBIOS/tastybasic.asm b/Source/HBIOS/tastybasic.asm index 3366737b..243cce39 100644 --- a/Source/HBIOS/tastybasic.asm +++ b/Source/HBIOS/tastybasic.asm @@ -11,7 +11,7 @@ ; ; Tasty Basic is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of -; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License @@ -24,69 +24,70 @@ #define dwa(addr) .db (addr >> 8) + 080h\ .db addr & 0ffh -ctrlc .equ 03h -bs .equ 08h -lf .equ 0ah -cr .equ 0dh -ctrlo .equ 0fh -ctrlu .equ 15h +ctrlc .equ 03h +bs .equ 08h +lf .equ 0ah +cr .equ 0dh +ctrlo .equ 0fh +ctrlu .equ 15h -#ifdef ZEMU ; Z80 Emulator -tty_data .equ 7ch -tty_status .equ 7dh -rx_full .equ 1 -tx_empty .equ 0 -TBC_LOC .equ 0 -#else ; RomWBW -#include "std.asm" +#ifdef ZEMU ; Z80 Emulator +tty_data .equ 7ch +tty_status .equ 7dh +rx_full .equ 1 +tx_empty .equ 0 +TBC_LOC .equ 0 +#else ; RomWBW +#include "std.asm" #endif - .org TBC_LOC + + .org TBC_LOC start: - ld sp,stack ; ** Cold Start ** - ld a,0ffh - jp init + ld sp,stack ; ** Cold Start ** + ld a,0ffh + jp init testc: - ex (sp),hl ; ** TestC ** - call skipspace ; ignore spaces - cp (hl) ; test character - inc hl ; compare the byte that follows the - jr z,tc1 ; call instruction with the text pointer - push bc - ld c,(hl) ; if not equal, ad the seond byte - ld b, 0h ; that follows the call to the old pc - add hl,bc - pop bc - dec de + ex (sp),hl ; ** TestC ** + call skipspace ; ignore spaces + cp (hl) ; test character + inc hl ; compare the byte that follows the + jr z,tc1 ; call instruction with the text pointer + push bc + ld c,(hl) ; if not equal, ad the seond byte + ld b, 0h ; that follows the call to the old pc + add hl,bc + pop bc + dec de tc1: - inc de ; if equal, skip those bytes - inc hl ; and continue - ex (sp),hl - ret + inc de ; if equal, skip those bytes + inc hl ; and continue + ex (sp),hl + ret skipspace: - ld a,(de) ; ** SkipSpace ** - cp ' ' ; ignore spaces - ret nz ; in text (where de points) - inc de ; and return the first non-blank - jp skipspace ; character in A + ld a,(de) ; ** SkipSpace ** + cp ' ' ; ignore spaces + ret nz ; in text (where de points) + inc de ; and return the first non-blank + jp skipspace ; character in A expr: - call expr2 ; ** Expr ** - push hl ; evaluate expression - jp expr1 + call expr2 ; ** Expr ** + push hl ; evaluate expression + jp expr1 comp: - ld a,h ; ** Compare ** - cp d ; compare hl with de - ret nz ; return c and z flags - ld a,l ; old a is lost - cp e - ret + ld a,h ; ** Compare ** + cp d ; compare hl with de + ret nz ; return c and z flags + ld a,l ; old a is lost + cp e + ret finish: - pop af ; ** Finish ** - call fin ; check end of command - jp qwhat + pop af ; ** Finish ** + call fin ; check end of command + jp qwhat ;************************************************************* ; @@ -98,7 +99,7 @@ finish: ; 'IF' IS FOLLOWED BY AN EXPR. AS A CONDITION AND ONE OR MORE ; COMMANDS (INCLUDING OTHER 'IF'S) SEPERATED BY SEMI-COLONS. ; NOTE THAT THE WORD 'THEN' IS NOT USED. TBI EVALUATES THE -; EXPR. IF IT IS NON-ZERO, EXECUTION CONTINUES. IF THE +; EXPR. IF IT IS NON-ZERO, EXECUTION CONTINUES. IF THE ; EXPR. IS ZERO, THE COMMANDS THAT FOLLOWS ARE IGNORED AND ; EXECUTION CONTINUES AT THE NEXT LINE. ; @@ -107,10 +108,10 @@ finish: ; DOUBLE QUOTES, OR IS A BACK-ARROW, IT HAS THE SAME EFFECT AS ; IN 'PRINT'. IF AN ITEM IS A VARIABLE, THIS VARIABLE NAME IS ; PRINTED OUT FOLLOWED BY A COLON. THEN TBI WAITS FOR AN -; EXPR. TO BE TYPED IN. THE VARIABLE IS THEN SET TO THE +; EXPR. TO BE TYPED IN. THE VARIABLE IS THEN SET TO THE ; VALUE OF THIS EXPR. IF THE VARIABLE IS PROCEDED BY A STRING ; (AGAIN IN SINGLE OR DOUBLE QUOTES), THE STRING WILL BE -; PRINTED FOLLOWED BY A COLON. TBI THEN WAITS FOR INPUT EXPR. +; PRINTED FOLLOWED BY A COLON. TBI THEN WAITS FOR INPUT EXPR. ; AND SET THE VARIABLE TO THE VALUE OF THE EXPR. ; ; IF THE INPUT EXPR. IS INVALID, TBI WILL PRINT "WHAT?", @@ -125,90 +126,90 @@ finish: ; THIS IS DONE BY 'DEFLT'. ;************************************************************* rem: - ld hl,0000h ; ** Rem ** - jr if1 ; this is like 'IF 0' + ld hl,0000h ; ** Rem ** + jr if1 ; this is like 'IF 0' iff: - call expr ; ** If ** + call expr ; ** If ** if1: - ld a,h ; is the expr = 0? - or l - jp nz,runsml ; no, continue - call findskip ; yes, skip rest of line - jp nc,runtsl ; and run the next line - jp rstart ; if no, restart + ld a,h ; is the expr = 0? + or l + jp nz,runsml ; no, continue + call findskip ; yes, skip rest of line + jp nc,runtsl ; and run the next line + jp rstart ; if no, restart inputerror: - ld hl,(stkinp) ; ** InputError ** - ld sp,hl ; restore old sp and old current - pop hl - ld (current),hl - pop de ; and old text pointer - pop de ; redo current + ld hl,(stkinp) ; ** InputError ** + ld sp,hl ; restore old sp and old current + pop hl + ld (current),hl + pop de ; and old text pointer + pop de ; redo current input: - push de ; ** Input ** - call qtstg ; is next item a string? - jp ip2 ; no - call testvar ; yes and followed by a variable? - jp c,ip4 ; no - jp ip3 ; yes, input variable + push de ; ** Input ** + call qtstg ; is next item a string? + jp ip2 ; no + call testvar ; yes and followed by a variable? + jp c,ip4 ; no + jp ip3 ; yes, input variable ip2: - push de ; save for printstr - call testvar ; must be variable - jp c,qwhat ; no, what? - ld a,(de) ; prepare for printstr - ld c,a - sub a - ld (de),a - pop de - call printstr ; print string as prompt - ld a,c ; restore text - dec de - ld (de),a + push de ; save for printstr + call testvar ; must be variable + jp c,qwhat ; no, what? + ld a,(de) ; prepare for printstr + ld c,a + sub a + ld (de),a + pop de + call printstr ; print string as prompt + ld a,c ; restore text + dec de + ld (de),a ip3: - push de ; save text pointer - ex de,hl - ld hl,(current) ; also save current - push hl - ld hl,input - ld (current),hl - ld hl,0000h - add hl,sp - ld (stkinp),hl - push de - ld a,':' - call getline - ld de,buffer - call expr - nop - nop - nop - pop de - ex de,hl - ld (hl),e - inc hl - ld (hl),d - pop hl - ld (current),hl - pop de + push de ; save text pointer + ex de,hl + ld hl,(current) ; also save current + push hl + ld hl,input + ld (current),hl + ld hl,0000h + add hl,sp + ld (stkinp),hl + push de + ld a,':' + call getline + ld de,buffer + call expr + nop + nop + nop + pop de + ex de,hl + ld (hl),e + inc hl + ld (hl),d + pop hl + ld (current),hl + pop de ip4: - pop af ; purge stack - call testc ; is next character ','? - .db ',' - .db ip5-$-1 - jr input ; yes, more items + pop af ; purge stack + call testc ; is next character ','? + .db ',' + .db ip5-$-1 + jr input ; yes, more items ip5: - call finish + call finish deflt: - ld a,(de) ; ** DEFLT ** - cp cr ; empty line is fine - jr z,lt1 ; else it's 'LET' + ld a,(de) ; ** DEFLT ** + cp cr ; empty line is fine + jr z,lt1 ; else it's 'LET' let: - call setval ; ** Let ** - call testc ; set value to var - .db ',' - .db lt1-$-1 - jr let ; item by item + call setval ; ** Let ** + call testc ; set value to var + .db ',' + .db lt1-$-1 + jr let ; item by item lt1: - call finish + call finish ;************************************************************* ; @@ -221,310 +222,310 @@ lt1: ; ;************************************************************* peek: - call parn ; ** Peek(expr) ** - ld a,h ; expression must be positive - or a - jp m,qhow - ld a,(hl) - ld h,0 - ld l,a - ret + call parn ; ** Peek(expr) ** + ld a,h ; expression must be positive + or a + jp m,qhow + ld a,(hl) + ld h,0 + ld l,a + ret poke: - call expr ; ** Poke ** - ld a,h ; address must be positive - or a - jp m,qhow - push hl - call testc ; is next char a comma? - .db ',' - .db pk1-$-1 ; what, no? - call expr ; get value to store - ld a,0 ; is it > 255? - cp h - jp z,pk2 ; no, all good - pop hl - jp m,qhow + call expr ; ** Poke ** + ld a,h ; address must be positive + or a + jp m,qhow + push hl + call testc ; is next char a comma? + .db ',' + .db pk1-$-1 ; what, no? + call expr ; get value to store + ld a,0 ; is it > 255? + cp h + jp z,pk2 ; no, all good + pop hl + jp m,qhow pk2: - ld a,l ; save value - pop hl - ld (hl),a - call finish + ld a,l ; save value + pop hl + ld (hl),a + call finish pk1: - pop hl - jp qwhat + pop hl + jp qwhat usrexec: - call parn ; ** Usr(expr) ** - push de - ex de,hl - ld hl,ue1 - push hl - ld ix,(usrptr) - jp (ix) + call parn ; ** Usr(expr) ** + push de + ex de,hl + ld hl,ue1 + push hl + ld ix,(usrptr) + jp (ix) ue1: - ex de,hl - pop de - ret + ex de,hl + pop de + ret ;************************************************************* ; ; *** EXPR *** ; ; 'EXPR' EVALUATES ARITHMETICAL OR LOGICAL EXPRESSIONS. ; :: -; +; ; WHERE IS ONE OF THE OPERATORS IN TAB8 AND THE ; RESULT OF THESE OPERATIONS IS 1 IF TRUE AND 0 IF FALSE. ; ::=(+ OR -)(+ OR -)(....) ; WHERE () ARE OPTIONAL AND (....) ARE OPTIONAL REPEATS. ; ::=(* OR />)(....) ; ::= -; -; () +; +; () ; IS RECURSIVE SO THAT VARIABLE '@' CAN HAVE AN ; AS INDEX, FUNCTIONS CAN HAVE AN AS ARGUMENTS, AND ; CAN BE AN IN PARANTHESE. ;************************************************************* expr1: - ld hl,tab8-1 ; look up rel.op - jp exec ; go do it + ld hl,tab8-1 ; look up rel.op + jp exec ; go do it xp11: - call xp18 ; rel.op.'>=' - ret c ; no, return hl=0 - ld l,a ; yes, return hl=1 - ret + call xp18 ; rel.op.'>=' + ret c ; no, return hl=0 + ld l,a ; yes, return hl=1 + ret xp12: - call xp18 ; rel.op.'#' - ret z ; no, return hl=0 - ld l,a ; yes, return hl=1 - ret + call xp18 ; rel.op.'#' + ret z ; no, return hl=0 + ld l,a ; yes, return hl=1 + ret xp13: - call xp18 ; rel.op.'>' - ret z ; no - ret c ; also, no - ld l,a ; yes, return hl=1 - ret + call xp18 ; rel.op.'>' + ret z ; no + ret c ; also, no + ld l,a ; yes, return hl=1 + ret xp14: - call xp18 ; rel.op.'<=' - ld l,a ; set hl=1 - ret z ; yes, return hl=1 - ret c - ld l,h ; else set hl=0 - ret + call xp18 ; rel.op.'<=' + ld l,a ; set hl=1 + ret z ; yes, return hl=1 + ret c + ld l,h ; else set hl=0 + ret xp15: - call xp18 ; rel.op.'=' - ret nz ; no, return hl=0 - ld l,a ; else hl=1 - ret + call xp18 ; rel.op.'=' + ret nz ; no, return hl=0 + ld l,a ; else hl=1 + ret xp16: - call xp18 ; rel.op.'<' - ret nc ; no, return hl=0 - ld l,a ; else hl=1 - ret + call xp18 ; rel.op.'<' + ret nc ; no, return hl=0 + ld l,a ; else hl=1 + ret xp17: - pop hl ; not rel.op - ret ; return hl= + pop hl ; not rel.op + ret ; return hl= xp18: - ld a,c ; routine for all rel.ops - pop hl - pop bc - push hl - push bc ; reverse top of stack - ld c,a - call expr2 ; get second - ex de,hl ; value now in de - ex (sp),hl ; first in hl - call ckhlde ; compare them - pop de ; restore text pointer - ld hl,0000h ; set hl=0, a=1 - ld a,1 - ret + ld a,c ; routine for all rel.ops + pop hl + pop bc + push hl + push bc ; reverse top of stack + ld c,a + call expr2 ; get second + ex de,hl ; value now in de + ex (sp),hl ; first in hl + call ckhlde ; compare them + pop de ; restore text pointer + ld hl,0000h ; set hl=0, a=1 + ld a,1 + ret expr2: - call testc ; is it minus sign? - .db '-' - .db xp21-$-1 - ld hl,0000h ; yes, fake 0 - - jr xp26 ; treat like subtract + call testc ; is it minus sign? + .db '-' + .db xp21-$-1 + ld hl,0000h ; yes, fake 0 - + jr xp26 ; treat like subtract xp21: - call testc ; is it plus sign? - .db '+' - .db xp22-$-1 + call testc ; is it plus sign? + .db '+' + .db xp22-$-1 xp22: - call expr3 ; first + call expr3 ; first xp23: - call testc ; addition? - .db '+' - .db xp25-$-1 - push hl ; yes, save value - call expr3 ; get second + call testc ; addition? + .db '+' + .db xp25-$-1 + push hl ; yes, save value + call expr3 ; get second xp24: - ex de,hl ; 2nd in de - ex (sp),hl ; 1st in hl - ld a,h ; compare sign - xor d - ld a,d - add hl,de - pop de ; restore text pointer - jp m,xp23 ; first and second sign differ - xor h ; first and second sign are equal - jp p,xp23 ; so is the result - jp qhow ; else we have overflow + ex de,hl ; 2nd in de + ex (sp),hl ; 1st in hl + ld a,h ; compare sign + xor d + ld a,d + add hl,de + pop de ; restore text pointer + jp m,xp23 ; first and second sign differ + xor h ; first and second sign are equal + jp p,xp23 ; so is the result + jp qhow ; else we have overflow xp25: - call testc ; subtract? - .db '-' - .db xp42-$-1 + call testc ; subtract? + .db '-' + .db xp42-$-1 xp26: - push hl ; yes, save first - call expr3 ; get second - call changesign ; negate - jr xp24 ; and add them + push hl ; yes, save first + call expr3 ; get second + call changesign ; negate + jr xp24 ; and add them expr3: - call expr4 ; get first expr4 + call expr4 ; get first expr4 xp31: - call testc ; multiply? - .db '*' - .db xp34-$-1 - push hl ; yes, save first and get second - call expr4 ; - ld b,0 ; clear b for sign - call checksign - ex (sp),hl ; first in hl - call checksign ; check sign of first - ex de,hl - ex (sp),hl - ld a,h ; is hl > 255? - or a - jr z,xp32 ; no - ld a,d ; yes, what about de - or d - ex de,hl - jp nz,ahow + call testc ; multiply? + .db '*' + .db xp34-$-1 + push hl ; yes, save first and get second + call expr4 ; + ld b,0 ; clear b for sign + call checksign + ex (sp),hl ; first in hl + call checksign ; check sign of first + ex de,hl + ex (sp),hl + ld a,h ; is hl > 255? + or a + jr z,xp32 ; no + ld a,d ; yes, what about de + or d + ex de,hl + jp nz,ahow xp32: - ld a,l - ld hl,0000h - or a - jr z,xp35 + ld a,l + ld hl,0000h + or a + jr z,xp35 xp33: - add hl,de - jp c,ahow - dec a - jr nz,xp33 - jr xp35 + add hl,de + jp c,ahow + dec a + jr nz,xp33 + jr xp35 xp34: - call testc ; divide - .db '/' - .db xp42-$-1 - push hl ; yes, save first - call expr4 ; and get the second one - ld b,0h ; clear b for sign - call checksign ; check sign of the second - ex (sp),hl ; get the first in hl - call checksign ; check sign of first - ex de,hl - ex (sp),hl - ex de,hl - ld a,d ; divide by 0? - or e - jp z,ahow ; err...how? - push bc ; else save sign - call divide - ld h,b - ld l,c - pop bc ; retrieve sign + call testc ; divide + .db '/' + .db xp42-$-1 + push hl ; yes, save first + call expr4 ; and get the second one + ld b,0h ; clear b for sign + call checksign ; check sign of the second + ex (sp),hl ; get the first in hl + call checksign ; check sign of first + ex de,hl + ex (sp),hl + ex de,hl + ld a,d ; divide by 0? + or e + jp z,ahow ; err...how? + push bc ; else save sign + call divide + ld h,b + ld l,c + pop bc ; retrieve sign xp35: - pop de ; and text pointer - ld a,h ; hl must be positive - or a - jp m,qhow ; else it's overflow - ld a,b - or a - call m,changesign ; change sign if needed - jp xp31 ; look for more terms + pop de ; and text pointer + ld a,h ; hl must be positive + or a + jp m,qhow ; else it's overflow + ld a,b + or a + call m,changesign ; change sign if needed + jp xp31 ; look for more terms expr4: - ld hl,tab4-1 ; find function in tab4 - jp exec ; and execute it + ld hl,tab4-1 ; find function in tab4 + jp exec ; and execute it xp40: - call testvar - jr c,xp41 ; nor a variable - ld a,(hl) - inc hl - ld h,(hl) ; value in hl - ld l,a - ret + call testvar + jr c,xp41 ; nor a variable + ld a,(hl) + inc hl + ld h,(hl) ; value in hl + ld l,a + ret xp41: - call testnum ; or is it a number - ld a,b ; number of digits - or a - ret nz ; ok + call testnum ; or is it a number + ld a,b ; number of digits + or a + ret nz ; ok parn: - call testc - .db '(' - .db xp43-$-1 - call expr ; "(expr)" - call testc - .db ')' - .db xp43-$-1 + call testc + .db '(' + .db xp43-$-1 + call expr ; "(expr)" + call testc + .db ')' + .db xp43-$-1 xp42: - ret + ret xp43: - jp qwhat ; what? + jp qwhat ; what? rnd: - call parn ; ** Rnd(expr) ** - ld a,h ; expression must be positive - or a - jp m,qhow - or l ; and non-zero - jp z,qhow - push de ; save de and hl - push hl - ld hl,(rndptr) ; get memory as random number - ld de,LST_ROM - call comp - jr c,ra1 ; wrap around if last - ld hl,start + call parn ; ** Rnd(expr) ** + ld a,h ; expression must be positive + or a + jp m,qhow + or l ; and non-zero + jp z,qhow + push de ; save de and hl + push hl + ld hl,(rndptr) ; get memory as random number + ld de,LST_ROM + call comp + jr c,ra1 ; wrap around if last + ld hl,start ra1: - ld e,(hl) - inc hl - ld d,(hl) - ld (rndptr),hl - pop hl - ex de,hl - push bc - call divide ; rnd(n)=mod(m,n)+1 - pop bc - pop de - inc hl - ret + ld e,(hl) + inc hl + ld d,(hl) + ld (rndptr),hl + pop hl + ex de,hl + push bc + call divide ; rnd(n)=mod(m,n)+1 + pop bc + pop de + inc hl + ret abs: - call parn ; ** Abs (expr) ** - dec de - call checksign - inc de - ret + call parn ; ** Abs (expr) ** + dec de + call checksign + inc de + ret size: - ld hl,(textunfilled) ; ** Size ** - push de ; get the number of free bytes between - ex de,hl ; and varbegin - ld hl,varbegin - call subde - pop de - ret + ld hl,(textunfilled) ; ** Size ** + push de ; get the number of free bytes between + ex de,hl ; and varbegin + ld hl,varbegin + call subde + pop de + ret clrvars: - ld hl,(textunfilled) ; ** ClearVars** - push de ; get the number of bytes available - ex de,hl ; for variable storge - ld hl,varend - call subde - ld b,h ; and save in bc - ld c,l - ld hl,(textunfilled) ; clear the first byte - ld d,h - ld e,l - inc de - ld (hl),0h - ldir ; and repeat for all the others - pop de - ret + ld hl,(textunfilled) ; ** ClearVars** + push de ; get the number of bytes available + ex de,hl ; for variable storge + ld hl,varend + call subde + ld b,h ; and save in bc + ld c,l + ld hl,(textunfilled) ; clear the first byte + ld d,h + ld e,l + inc de + ld (hl),0h + ldir ; and repeat for all the others + pop de + ret ;************************************************************* ; @@ -534,7 +535,7 @@ clrvars: ; ; 'SUBDE' SUBSTRACTS DE FROM HL ; -; 'CHKSGN' CHECKS SIGN OF HL. IF +, NO CHANGE. IF -, CHANGE +; 'CHKSGN' CHECKS SIGN OF HL. IF +, NO CHANGE. IF -, CHANGE ; SIGN AND FLIP SIGN OF B. ; ; 'CHGSGN' CHECKS SIGN N OF HL AND B UNCONDITIONALLY. @@ -544,85 +545,85 @@ clrvars: ; CASE, HL DE ARE THEN COMPARED TO SET THE FLAGS. ;************************************************************* divide: - push hl ; ** Divide ** - ld l,h ; divide h by de - ld h,0h - call dv1 - ld b,c ; save result in b - ld a,l ; (remainder + l) / de - pop hl - ld h,a + push hl ; ** Divide ** + ld l,h ; divide h by de + ld h,0h + call dv1 + ld b,c ; save result in b + ld a,l ; (remainder + l) / de + pop hl + ld h,a dv1: - ld c,0ffh ; result in c + ld c,0ffh ; result in c dv2: - inc c ; dumb routine - call subde ; divide using subtract and count - jr nc,dv2 - add hl,de - ret + inc c ; dumb routine + call subde ; divide using subtract and count + jr nc,dv2 + add hl,de + ret subde: - ld a,l ; ** subde ** - sub e ; subtract de from hl - ld l,a - ld a,h - sbc a,d - ld h,a - ret + ld a,l ; ** subde ** + sub e ; subtract de from hl + ld l,a + ld a,h + sbc a,d + ld h,a + ret checksign: - ld a,h ; ** CheckSign ** - or a ; check sign of hl - ret p + ld a,h ; ** CheckSign ** + or a ; check sign of hl + ret p changesign: - ld a,h ; ** ChangeSign ** - or l ; check if hl is zero - jp nz,cs1 ; no, try to change sign - ret ; yes, return + ld a,h ; ** ChangeSign ** + or l ; check if hl is zero + jp nz,cs1 ; no, try to change sign + ret ; yes, return cs1: - ld a,h ; change sign of hl - push af - cpl - ld h,a - ld a,l - cpl - ld l,a - inc hl - pop af - xor h - jp p,qhow - ld a,b ; and also flip b - xor 80h - ld b,a - ret + ld a,h ; change sign of hl + push af + cpl + ld h,a + ld a,l + cpl + ld l,a + inc hl + pop af + xor h + jp p,qhow + ld a,b ; and also flip b + xor 80h + ld b,a + ret ckhlde: - ld a,h ; same sign? - xor d ; yes, compare - jp p,ck1 ; no, exchange and compare - ex de,hl + ld a,h ; same sign? + xor d ; yes, compare + jp p,ck1 ; no, exchange and compare + ex de,hl ck1: - call comp - ret + call comp + ret ;************************************************************* ; ; *** SETVAL *** FIN *** ENDCHK *** & ERROR (& FRIENDS) *** ; ; "SETVAL" EXPECTS A VARIABLE, FOLLOWED BY AN EQUAL SIGN AND -; THEN AN EXPR. IT EVALUATES THE EXPR. AND SET THE VARIABLE +; THEN AN EXPR. IT EVALUATES THE EXPR. AND SET THE VARIABLE ; TO THAT VALUE. ; ; "FIN" CHECKS THE END OF A COMMAND. IF IT ENDED WITH ":", -; EXECUTION CONTINUES. IF IT ENDED WITH A CR, IT FINDS THE +; EXECUTION CONTINUES. IF IT ENDED WITH A CR, IT FINDS THE ; NEXT LINE AND CONTINUE FROM THERE. ; ; "ENDCHK" CHECKS IF A COMMAND IS ENDED WITH CR. THIS IS -; REQUIRED IN CERTAIN COMMANDS. (GOTO, RETURN, AND STOP ETC.) +; REQUIRED IN CERTAIN COMMANDS. (GOTO, RETURN, AND STOP ETC.) ; ; "ERROR" PRINTS THE STRING POINTED BY DE (AND ENDS WITH CR). ; IT THEN PRINTS THE LINE POINTED BY 'CURRNT' WITH A "?" ; INSERTED AT WHERE THE OLD TEXT POINTER (SHOULD BE ON TOP ; OF THE STACK) POINTS TO. EXECUTION OF TB IS STOPPED -; AND TBI IS RESTARTED. HOWEVER, IF 'CURRNT' -> ZERO +; AND TBI IS RESTARTED. HOWEVER, IF 'CURRNT' -> ZERO ; (INDICATING A DIRECT COMMAND), THE DIRECT COMMAND IS NOT ; PRINTED. AND IF 'CURRNT' -> NEGATIVE # (INDICATING 'INPUT' ; COMMAND), THE INPUT LINE IS NOT PRINTED AND EXECUTION IS @@ -635,76 +636,76 @@ ck1: ; 'AHOW' AND 'AHOW' IN THE ZERO PAGE SECTION ALSO DO THIS. ;************************************************************* setval: - call testvar ; ** SetVal ** - jp c,qwhat ; no variable - push hl ; save address of var - call testc ; do we have =? - .db '=' - .db sv1-$-1 - call expr ; evaluate expression - ld b,h ; value is in bc now - ld c,l - pop hl ; get address - ld (hl),c ; save value - inc hl - ld (hl),b - ret + call testvar ; ** SetVal ** + jp c,qwhat ; no variable + push hl ; save address of var + call testc ; do we have =? + .db '=' + .db sv1-$-1 + call expr ; evaluate expression + ld b,h ; value is in bc now + ld c,l + pop hl ; get address + ld (hl),c ; save value + inc hl + ld (hl),b + ret sv1: - jp qwhat + jp qwhat fin: - call testc ; test for ':' - .db ':' - .db fi1 - $ - 1 - pop af ; yes, purge return address - jp runsml ; continue on same line + call testc ; test for ':' + .db ':' + .db fi1 - $ - 1 + pop af ; yes, purge return address + jp runsml ; continue on same line fi1: - call testc ; not ':', is it cr - .db cr - .db fi2 - $ - 1 - pop af ; yes, purge return address - jp runnxl ; run next line + call testc ; not ':', is it cr + .db cr + .db fi2 - $ - 1 + pop af ; yes, purge return address + jp runnxl ; run next line fi2: - ret ; else return to caller + ret ; else return to caller endchk: - call skipspace ; ** EndChk ** - cp cr ; ends with cr? - ret z ; ok, otherwise say 'what?' + call skipspace ; ** EndChk ** + cp cr ; ends with cr? + ret z ; ok, otherwise say 'what?' qwhat: - push de ; ** QWhat ** + push de ; ** QWhat ** awhat: - ld de,what ; ** AWhat ** + ld de,what ; ** AWhat ** handleerror: - sub a ; ** Error ** - call printstr ; print error message - pop de - ld a,(de) ; save the character - push af ; at where old de points - sub a ; and put a 0 (zero) there - ld (de),a - ld hl,(current) ; get the current line number - push hl - ld a,(hl) ; check the value - inc hl - or (hl) - pop de - jp z,rstart ; if zero, just rerstart - ld a,(hl) ; if negative - or a - jp m,inputerror ; then redo input - call printline ; else print the line - dec de ; up to where the 0 is - pop af ; restore the character - ld (de),a - ld a,'?' ; print a ? - call outc - sub a ; and the rest of the line - call printstr - jp rstart + sub a ; ** Error ** + call printstr ; print error message + pop de + ld a,(de) ; save the character + push af ; at where old de points + sub a ; and put a 0 (zero) there + ld (de),a + ld hl,(current) ; get the current line number + push hl + ld a,(hl) ; check the value + inc hl + or (hl) + pop de + jp z,rstart ; if zero, just rerstart + ld a,(hl) ; if negative + or a + jp m,inputerror ; then redo input + call printline ; else print the line + dec de ; up to where the 0 is + pop af ; restore the character + ld (de),a + ld a,'?' ; print a ? + call outc + sub a ; and the rest of the line + call printstr + jp rstart qsorry: - push de ; ** Sorry ** + push de ; ** Sorry ** asorry: - ld de,sorry - jr handleerror + ld de,sorry + jr handleerror ;************************************************************* ; @@ -712,7 +713,7 @@ asorry: ; ; 'GETLN' READS A INPUT LINE INTO 'BUFFER'. IT FIRST PROMPT ; THE CHARACTER IN A (GIVEN BY THE CALLER), THEN IT FILLS -; THE BUFFER AND ECHOS. IT IGNORES LF'S AND NULLS, BUT STILL +; THE BUFFER AND ECHOS. IT IGNORES LF'S AND NULLS, BUT STILL ; ECHOS THEM BACK. RUB-OUT IS USED TO CAUSE IT TO DELETE ; THE LAST CHARACTER (IF THERE IS ONE), AND ALT-MOD IS USED TO ; CAUSE IT TO DELETE THE WHOLE LINE AND START IT ALL OVER. @@ -734,72 +735,72 @@ asorry: ; 'FNDSKP' USE DE TO FIND A CR, AND THEN START SEARCH. ;************************************************************* getline: - call outc ; ** GetLine ** - ld de,buffer ; prompt and initalise pointer + call outc ; ** GetLine ** + ld de,buffer ; prompt and initalise pointer gl1: - call chkio ; check keyboard - jr z,gl1 ; no input, so wait - cp bs ; erase last character? - jr z,gl3 ; yes - call outc ; echo character - cp lf ; ignore lf - jr z,gl1 - or a ; ignore null - jr z,gl1 - cp ctrlu ; erase the whole line? - jr z,gl4 ; yes - ld (de),a ; save the input - inc de ; and increment pointer - cp cr ; was it cr? - ret z ; yes, end of line - ld a,e ; any free space left? - cp bufend & 0ffh - jr nz,gl1 ; yes, get next char + call chkio ; check keyboard + jr z,gl1 ; no input, so wait + cp bs ; erase last character? + jr z,gl3 ; yes + call outc ; echo character + cp lf ; ignore lf + jr z,gl1 + or a ; ignore null + jr z,gl1 + cp ctrlu ; erase the whole line? + jr z,gl4 ; yes + ld (de),a ; save the input + inc de ; and increment pointer + cp cr ; was it cr? + ret z ; yes, end of line + ld a,e ; any free space left? + cp bufend & 0ffh + jr nz,gl1 ; yes, get next char gl3: - ld a,e ; delete last character - cp buffer & 0ffh ; if there are any? - jr z,gl4 ; no, redo whole line - dec de ; yes, back pointer - ld a,08h ; and echo a backspace - call outc - jr gl1 ; and get next character + ld a,e ; delete last character + cp buffer & 0ffh ; if there are any? + jr z,gl4 ; no, redo whole line + dec de ; yes, back pointer + ld a,08h ; and echo a backspace + call outc + jr gl1 ; and get next character gl4: - call crlf ; redo entire line - ld a,'>' - jr getline + call crlf ; redo entire line + ld a,'>' + jr getline findline: - ld a,h ; ** FindLine ** - or a ; check the sign of hl - jp m,qhow ; it cannot be negative - ld de,textbegin ; initialise the text pointer + ld a,h ; ** FindLine ** + or a ; check the sign of hl + jp m,qhow ; it cannot be negative + ld de,textbegin ; initialise the text pointer findlineptr: fl1: - push hl ; save line number - ld hl,(textunfilled) ; check if we passed end - dec hl - call comp - pop hl ; retrieve line number - ret c ; c,nz passed end - ld a,(de) ; we didn't; get first byte - sub l ; is this the line? - ld b,a ; compare low order - inc de - ld a,(de) ; get second byte - sbc a,h ; compare high order - jr c,fl2 ; no, not there yet - dec de ; else we either found it - or b ; or it's not there - ret ; nc,z:found; nc,nz:no + push hl ; save line number + ld hl,(textunfilled) ; check if we passed end + dec hl + call comp + pop hl ; retrieve line number + ret c ; c,nz passed end + ld a,(de) ; we didn't; get first byte + sub l ; is this the line? + ld b,a ; compare low order + inc de + ld a,(de) ; get second byte + sbc a,h ; compare high order + jr c,fl2 ; no, not there yet + dec de ; else we either found it + or b ; or it's not there + ret ; nc,z:found; nc,nz:no findnext: - inc de ; find next line + inc de ; find next line fl2: - inc de ; just passed first and second byte + inc de ; just passed first and second byte findskip: - ld a,(de) ; ** FindSkip ** - cp cr ; try to find cr - jr nz,fl2 ; keep looking - inc de ; found cr, skip over - jr fl1 ; check if end of text + ld a,(de) ; ** FindSkip ** + cp cr ; try to find cr + jr nz,fl2 ; keep looking + inc de ; found cr, skip over + jr fl1 ; check if end of text ;************************************************************* ; @@ -826,130 +827,130 @@ findskip: ; 'PRTLN' PRINTS A SAVED TEXT LINE WITH LINE # AND ALL. ;************************************************************* printstr: - ld b,a + ld b,a ps1: - ld a,(de) ; get a character - inc de ; bump pointer - cp b ; same as old A? - ret z ; yes, return - call outc ; no, show character - cp cr ; was it a cr? - jr nz,ps1 ; no, next character - ret ; yes, returns + ld a,(de) ; get a character + inc de ; bump pointer + cp b ; same as old A? + ret z ; yes, return + call outc ; no, show character + cp cr ; was it a cr? + jr nz,ps1 ; no, next character + ret ; yes, returns qtstg: - call testc ; ** Qtstg ** - .db 22h ; is it a double quote - .db qt3-$-1 - ld a,22h + call testc ; ** Qtstg ** + .db 22h ; is it a double quote + .db qt3-$-1 + ld a,22h qt1: - call printstr ; print until another - cp cr - pop hl - jp z,runnxl + call printstr ; print until another + cp cr + pop hl + jp z,runnxl qt2: - inc hl ; skip 3 bytes on return - inc hl - inc hl - jp (hl) ; return + inc hl ; skip 3 bytes on return + inc hl + inc hl + jp (hl) ; return qt3: - call testc ; is it a single quote - .db 27h - .db qt4-$-1 - ld a,27h - jr qt1 + call testc ; is it a single quote + .db 27h + .db qt4-$-1 + ld a,27h + jr qt1 qt4: - call testc ; is it back-arrow - .db '_' - .db qt5-$-1 - ld a,8dh ; yes, cr without lf - call outc - call outc - pop hl ; return address - jr qt2 + call testc ; is it back-arrow + .db '_' + .db qt5-$-1 + ld a,8dh ; yes, cr without lf + call outc + call outc + pop hl ; return address + jr qt2 qt5: - ret ; none of the above + ret ; none of the above printnum: - ld b,0h ; ** PrintNum ** - call checksign ; check sign - jp p,pn1 ; no sign - ld b,'-' - dec c + ld b,0h ; ** PrintNum ** + call checksign ; check sign + jp p,pn1 ; no sign + ld b,'-' + dec c pn1: - push de ; save - ld de, 000ah ; decimal - push de ; save as flag - dec c ; c=spaces - push bc ; save sign & space + push de ; save + ld de, 000ah ; decimal + push de ; save as flag + dec c ; c=spaces + push bc ; save sign & space pn2: - call divide ; divide hl by 10 - ld a,b ; result 0? - or c - jr z,pn3 ; yes, we got all - ex (sp),hl ; no, save remainder - dec l ; and count space - push hl ; hl is old bc - ld h,b ; moved result to bc - ld l,c - jr pn2 ; and divide by 10 + call divide ; divide hl by 10 + ld a,b ; result 0? + or c + jr z,pn3 ; yes, we got all + ex (sp),hl ; no, save remainder + dec l ; and count space + push hl ; hl is old bc + ld h,b ; moved result to bc + ld l,c + jr pn2 ; and divide by 10 pn3: - pop bc ; we got all digits + pop bc ; we got all digits pn4: - dec c - ld a,c ; look at space count - or a - jp m,pn5 ; no leading spaces - ld a,' ' ; print a leading space - call outc - jr pn4 ; any more? + dec c + ld a,c ; look at space count + or a + jp m,pn5 ; no leading spaces + ld a,' ' ; print a leading space + call outc + jr pn4 ; any more? pn5: - ld a,b ; print sign - or a - call nz,outc - ld e,l ; last remainder in e + ld a,b ; print sign + or a + call nz,outc + ld e,l ; last remainder in e pn6: - ld a,e ; check digit in e - cp lf ; lf is flag for no more - pop de - ret z ; if yes, return - add a,30h ; else convert to ascii - call outc ; and print the digit - jr pn6 ; next digit + ld a,e ; check digit in e + cp lf ; lf is flag for no more + pop de + ret z ; if yes, return + add a,30h ; else convert to ascii + call outc ; and print the digit + jr pn6 ; next digit printhex: - ld c,h ; ** PrintHex ** - call ph1 ; first hex byte + ld c,h ; ** PrintHex ** + call ph1 ; first hex byte printhex8: - ld c,l ; then second + ld c,l ; then second ph1: - ld a,c ; get left nibble into position - rra - rra - rra - rra - call ph2 ; and turn into hex digit - ld a,c ; then convert right nibble + ld a,c ; get left nibble into position + rra + rra + rra + rra + call ph2 ; and turn into hex digit + ld a,c ; then convert right nibble ph2: - and 0fh ; mask right nibble - add a,90h ; and convert to ascii character - daa - adc a,40h - daa - call outc ; print character - ret + and 0fh ; mask right nibble + add a,90h ; and convert to ascii character + daa + adc a,40h + daa + call outc ; print character + ret printline: - ld a,(de) ; ** PrintLine ** - ld l,a ; low order line number - inc de - ld a,(de) ; high order - ld h,a - inc de - ld c,04h ; print 4 digit line number - call printnum - ld a,' ' ; followed by a space - call outc - sub a ; and the the rest - call printstr - ret + ld a,(de) ; ** PrintLine ** + ld l,a ; low order line number + inc de + ld a,(de) ; high order + ld h,a + inc de + ld c,04h ; print 4 digit line number + call printnum + ld a,' ' ; followed by a space + call outc + sub a ; and the the rest + call printstr + ret ;************************************************************* ; @@ -968,143 +969,143 @@ printline: ; STACK ;************************************************************* mvup: - call comp ; ** mvup ** - ret z ; de = hl, return - ld a,(de) ; get one byte - ld (bc),a ; then copy it - inc de ; increase both pointers - inc bc - jr mvup ; until done + call comp ; ** mvup ** + ret z ; de = hl, return + ld a,(de) ; get one byte + ld (bc),a ; then copy it + inc de ; increase both pointers + inc bc + jr mvup ; until done mvdown: - ld a,b ; ** mvdown ** - sub d ; check if de = bc - jp nz,md1 ; no, go move - ld a,c ; maybe, other byte - sub e - ret z ; yes, return + ld a,b ; ** mvdown ** + sub d ; check if de = bc + jp nz,md1 ; no, go move + ld a,c ; maybe, other byte + sub e + ret z ; yes, return md1: - dec de ; else move a byte - dec hl ; but first decrease both pointers - ld a,(de) ; and then do it - ld (hl),a - jr mvdown ; loop back + dec de ; else move a byte + dec hl ; but first decrease both pointers + ld a,(de) ; and then do it + ld (hl),a + jr mvdown ; loop back popa: - pop bc ; bc = return address - pop hl ; restore loopvar - ld (loopvar),hl - ld a,h - or l - jr z,pp1 ; all done, so return - pop hl - ld (loopinc),hl - pop hl - ld (looplmt),hl - pop hl - ld (loopln),hl - pop hl - ld (loopptr),hl + pop bc ; bc = return address + pop hl ; restore loopvar + ld (loopvar),hl + ld a,h + or l + jr z,pp1 ; all done, so return + pop hl + ld (loopinc),hl + pop hl + ld (looplmt),hl + pop hl + ld (loopln),hl + pop hl + ld (loopptr),hl pp1: - push bc ; bc = return address - ret + push bc ; bc = return address + ret pusha: - ld hl,stacklimit ; ** PushA ** - call changesign - pop bc ; bc = return address - add hl,sp ; is stack near the top? - jp nc,qsorry ; yes, sorry - ld hl,(loopvar) ; else save loop variables - ld a,h - or l - jr z,pu1 ; only when loopvar not 0 - ld hl,(loopptr) - push hl - ld hl,(loopln) - push hl - ld hl,(looplmt) - push hl - ld hl,(loopinc) - push hl - ld hl,(loopvar) + ld hl,stacklimit ; ** PushA ** + call changesign + pop bc ; bc = return address + add hl,sp ; is stack near the top? + jp nc,qsorry ; yes, sorry + ld hl,(loopvar) ; else save loop variables + ld a,h + or l + jr z,pu1 ; only when loopvar not 0 + ld hl,(loopptr) + push hl + ld hl,(loopln) + push hl + ld hl,(looplmt) + push hl + ld hl,(loopinc) + push hl + ld hl,(loopvar) pu1: - push hl - push bc ; bc = return address - ret + push hl + push bc ; bc = return address + ret testvar: - call skipspace ; ** testvar ** - sub '@' ; test variables - ret c ; not a variable - jr nz,tv1 ; not @ array - inc de ; is is the @ array - call parn ; @ should be followed by (expr) - add hl,hl ; as its index - jr c,qhow ; is index too big? - push de ; will it override text? - ex de,hl - call size ; find the size of free - call comp - jp c,asorry ; yes, sorry - ld hl,varbegin ; no, get address of @(expr) and - call subde ; put it in hl - pop de - ret + call skipspace ; ** testvar ** + sub '@' ; test variables + ret c ; not a variable + jr nz,tv1 ; not @ array + inc de ; is is the @ array + call parn ; @ should be followed by (expr) + add hl,hl ; as its index + jr c,qhow ; is index too big? + push de ; will it override text? + ex de,hl + call size ; find the size of free + call comp + jp c,asorry ; yes, sorry + ld hl,varbegin ; no, get address of @(expr) and + call subde ; put it in hl + pop de + ret tv1: - cp 1bh ; not @, is it A to Z - ccf - ret c - inc de ; if A trhough Z - ld hl,varbegin ; calculate address of that variable - rlca ; and return it in hl - add a,l ; with the c flag cleared - ld l,a - ld a,0 - adc a,h - ld h,a - ret + cp 1bh ; not @, is it A to Z + ccf + ret c + inc de ; if A trhough Z + ld hl,varbegin ; calculate address of that variable + rlca ; and return it in hl + add a,l ; with the c flag cleared + ld l,a + ld a,0 + adc a,h + ld h,a + ret testnum: - ld hl,0000h ; ** TestNum ** - ld b,h ; test if the text is a number - call skipspace + ld hl,0000h ; ** TestNum ** + ld b,h ; test if the text is a number + call skipspace tn1: - cp '0' ; if not,return 0 in b and hl - ret c - cp ':' ; if a digit, convert to binary in - ret nc ; b and hl - ld a,0f0h ; set b to number of digits - and h ; if h>255, there is no room for - jr nz,qhow ; next digit - inc b ; b counts number of digits - push bc - ld b,h ; hl=10*hl+(new digit) - ld c,l - add hl,hl ; where 10* is done by shift and add - add hl,hl - add hl,bc - add hl,hl - ld a,(de) ; and (digit) is by stripping the - inc de ; ascii code - and 0fh - add a,l - ld l,a - ld a,0 - adc a,h - ld h,a - pop bc - ld a,(de) - jp p,tn1 + cp '0' ; if not,return 0 in b and hl + ret c + cp ':' ; if a digit, convert to binary in + ret nc ; b and hl + ld a,0f0h ; set b to number of digits + and h ; if h>255, there is no room for + jr nz,qhow ; next digit + inc b ; b counts number of digits + push bc + ld b,h ; hl=10*hl+(new digit) + ld c,l + add hl,hl ; where 10* is done by shift and add + add hl,hl + add hl,bc + add hl,hl + ld a,(de) ; and (digit) is by stripping the + inc de ; ascii code + and 0fh + add a,l + ld l,a + ld a,0 + adc a,h + ld h,a + pop bc + ld a,(de) + jp p,tn1 qhow: - push de ; ** Error How? ** + push de ; ** Error How? ** ahow: - ld de,how - jp handleerror + ld de,how + jp handleerror -msg1 .db "TASTY BASIC",cr -msg2 .db " BYTES FREE",cr -how .db "HOW?",cr -ok .db "OK",cr -what .db "WHAT?",cr -sorry .db "SORRY",cr +msg1 .db "TASTY BASIC",cr +msg2 .db " BYTES FREE",cr +how .db "HOW?",cr +ok .db "OK",cr +what .db "WHAT?",cr +sorry .db "SORRY",cr ;************************************************************* ; @@ -1115,7 +1116,7 @@ sorry .db "SORRY",cr ; ; AT START, IT PRINTS OUT "(CR)OK(CR)", AND INITIALIZES THE ; STACK AND SOME OTHER INTERNAL VARIABLES. THEN IT PROMPTS -; ">" AND READS A LINE. IF THE LINE STARTS WITH A NON-ZERO +; ">" AND READS A LINE. IF THE LINE STARTS WITH A NON-ZERO ; NUMBER, THIS NUMBER IS THE LINE NUMBER. THE LINE NUMBER ; (IN 16 BIT BINARY) AND THE REST OF THE LINE (INCLUDING CR) ; IS STORED IN THE MEMORY. IF A LINE WITH THE SAME LINE @@ -1124,7 +1125,7 @@ sorry .db "SORRY",cr ; AND ANY EXISTING LINE WITH THE SAME LINE NUMBER IS DELETED. ; ; AFTER A LINE IS INSERTED, REPLACED, OR DELETED, THE PROGRAM -; LOOPS BACK AND ASKS FOR ANOTHER LINE. THIS LOOP WILL BE +; LOOPS BACK AND ASKS FOR ANOTHER LINE. THIS LOOP WILL BE ; TERMINATED WHEN IT READS A LINE WITH ZERO OR NO LINE ; NUMBER; AND CONTROL IS TRANSFERED TO "DIRECT". ; @@ -1134,80 +1135,80 @@ sorry .db "SORRY",cr ; BY THE CONTENT OF A MEMORY LOCATION LABELED "TXTUNF". ; ; THE MEMORY LOCATION "CURRNT" POINTS TO THE LINE NUMBER -; THAT IS CURRENTLY BEING INTERPRETED. WHILE WE ARE IN +; THAT IS CURRENTLY BEING INTERPRETED. WHILE WE ARE IN ; THIS LOOP OR WHILE WE ARE INTERPRETING A DIRECT COMMAND ; (SEE NEXT SECTION). "CURRNT" SHOULD POINT TO A 0. ;************************************************************* rstart: - ld sp,stack + ld sp,stack st1: - call crlf - sub a ; a=0 - ld de,ok ; print ok - call printstr - ld hl,st2 + 1 ; literal zero - ld (current),hl ; reset current line pointer + call crlf + sub a ; a=0 + ld de,ok ; print ok + call printstr + ld hl,st2 + 1 ; literal zero + ld (current),hl ; reset current line pointer st2: - ld hl,0000h - ld (loopvar),hl - ld (stkgos),hl + ld hl,0000h + ld (loopvar),hl + ld (stkgos),hl st3: - ld a,'>' ; initialise prompt - call getline - push de ; de points to end of line - ld de,buffer ; point de to beginning of line - call testnum ; check if it is a number - call skipspace - ld a,h ; hl = value of the number, or - or l ; 0 if no number was found - pop bc ; bc points to end of line - jp z,direct - dec de ; back up de and save the value of - ld a,h ; the value of the line number there - ld (de),a - dec de - ld a,l - ld (de),a - push bc ; bc,de point to begin,end - push de - ld a,c - sub e + ld a,'>' ; initialise prompt + call getline + push de ; de points to end of line + ld de,buffer ; point de to beginning of line + call testnum ; check if it is a number + call skipspace + ld a,h ; hl = value of the number, or + or l ; 0 if no number was found + pop bc ; bc points to end of line + jp z,direct + dec de ; back up de and save the value of + ld a,h ; the value of the line number there + ld (de),a + dec de + ld a,l + ld (de),a + push bc ; bc,de point to begin,end + push de + ld a,c + sub e - push af ; a = number of bytes in line - call findline ; find this line in save area - push de ; de points to save area - jr nz,st4 ; nz: line not found - push de ; z: found, delete it - call findnext ; find next line - ; de -> next line - pop bc ; bc -> line to be deleted - ld hl,(textunfilled) ; hl -> unfilled text area - call mvup ; move up to delete - ld h,b ; txtunf -> unfilled area - ld l,c - ld (textunfilled),hl + push af ; a = number of bytes in line + call findline ; find this line in save area + push de ; de points to save area + jr nz,st4 ; nz: line not found + push de ; z: found, delete it + call findnext ; find next line + ; de -> next line + pop bc ; bc -> line to be deleted + ld hl,(textunfilled) ; hl -> unfilled text area + call mvup ; move up to delete + ld h,b ; txtunf -> unfilled area + ld l,c + ld (textunfilled),hl st4: - pop bc ; get ready to insert - ld hl,(textunfilled) ; but first check if the length - pop af ; of new line is 3 (line# and cr) - push hl - cp 3h ; if so, do not insert - jr z,rstart ; must clear the stack - add a,l ; calculate new txtunf - ld l,a - ld a,0 - adc a,h - ld h,a ; hl -> new unfilled area - ld de,textend ; check to see if there is space - call comp - jp nc,qsorry ; no, sorry - ld (textunfilled),hl ; ok, update textunfilled - pop de ; de -> old unfilled area - call mvdown - pop de ; de,hl -> begin,end - pop hl - call mvup ; copy new line to save area - jr st3 + pop bc ; get ready to insert + ld hl,(textunfilled) ; but first check if the length + pop af ; of new line is 3 (line# and cr) + push hl + cp 3h ; if so, do not insert + jr z,rstart ; must clear the stack + add a,l ; calculate new txtunf + ld l,a + ld a,0 + adc a,h + ld h,a ; hl -> new unfilled area + ld de,textend ; check to see if there is space + call comp + jp nc,qsorry ; no, sorry + ld (textunfilled),hl ; ok, update textunfilled + pop de ; de -> old unfilled area + call mvdown + pop de ; de,hl -> begin,end + pop hl + call mvup ; copy new line to save area + jr st3 ;************************************************************* ; @@ -1248,47 +1249,47 @@ st4: ;************************************************************* #ifndef ZEMU bye: - call endchk ; ** Reboot ** - LD A,BID_BOOT ; BOOT BANK - LD HL,0 ; ADDRESS ZERO - CALL HB_BNKCALL ; DOES NOT RETURN - HALT + call endchk ; ** Reboot ** + LD A,BID_BOOT ; BOOT BANK + LD HL,0 ; ADDRESS ZERO + CALL HB_BNKCALL ; DOES NOT RETURN + HALT #endif new: - call endchk ; ** New ** - ld hl,textbegin - ld (textunfilled),hl + call endchk ; ** New ** + ld hl,textbegin + ld (textunfilled),hl clear: - call clrvars ; ** Clear ** - jp rstart + call clrvars ; ** Clear ** + jp rstart endd: - call endchk ; ** End ** - jp rstart + call endchk ; ** End ** + jp rstart run: - call endchk ; ** Run ** - ld de,textbegin + call endchk ; ** Run ** + ld de,textbegin runnxl: - ld hl,0h ; ** Run Next Line ** - call findlineptr - jp c,rstart + ld hl,0h ; ** Run Next Line ** + call findlineptr + jp c,rstart runtsl: - ex de,hl ; ** Run Tsl - ld (current),hl ; set current -> line # - ex de,hl - inc de - inc de + ex de,hl ; ** Run Tsl + ld (current),hl ; set current -> line # + ex de,hl + inc de + inc de runsml: - call chkio ; ** Run Same Line ** - ld hl, tab2-1 ; find the command in table 2 - jp exec ; and execute it + call chkio ; ** Run Same Line ** + ld hl, tab2-1 ; find the command in table 2 + jp exec ; and execute it goto: - call expr - push de ; save for error routine - call endchk ; must find a cr - call findline ; find the target line - jp nz, ahow ; no such line # - pop af ; clear the pushed de - jr runtsl ; go do it + call expr + push de ; save for error routine + call endchk ; must find a cr + call findline ; find the target line + jp nz, ahow ; no such line # + pop af ; clear the pushed de + jr runtsl ; go do it ;************************************************************* ; @@ -1301,7 +1302,7 @@ goto: ; ; PRINT COMMAND IS 'PRINT ....;' OR 'PRINT ....(CR)' ; WHERE '....' IS A LIST OF EXPRESIONS, FORMATS, BACK- -; ARROWS, AND STRINGS. THESE ITEMS ARE SEPERATED BY COMMAS. +; ARROWS, AND STRINGS. THESE ITEMS ARE SEPERATED BY COMMAS. ; ; A FORMAT IS A POUND SIGN FOLLOWED BY A NUMBER. IT CONTROLS ; THE NUMBER OF SPACES THE VALUE OF A EXPRESION IS GOING TO @@ -1319,77 +1320,77 @@ goto: ; ENDED WITH A COMMA, NO (CRLF) IS GENERATED. ;************************************************************* list: - call testnum ; check if there is a number - call endchk ; if no number we get a 0 - call findline ; find this or next line + call testnum ; check if there is a number + call endchk ; if no number we get a 0 + call findline ; find this or next line ls1: - jp c,rstart - call printline ; print the line - call chkio ; stop on ctrl-c - call findlineptr ; find the next line - jr ls1 ; and loop back + jp c,rstart + call printline ; print the line + call chkio ; stop on ctrl-c + call findlineptr ; find the next line + jr ls1 ; and loop back print: - ld c,6 ; c = number of spaces - call testc ; is it a semicolon? - .db ':' - .db pr2-$-1 - call crlf - jr runsml + ld c,6 ; c = number of spaces + call testc ; is it a semicolon? + .db ':' + .db pr2-$-1 + call crlf + jr runsml pr2: - call testc ; is it a cr? - .db cr - .db pr0-$-1 - call crlf - jr runnxl + call testc ; is it a cr? + .db cr + .db pr0-$-1 + call crlf + jr runnxl pr0: - call testc ; is it format? - .db '#' - .db pr1-$-1 - call expr - ld c,l - jr pr3 + call testc ; is it format? + .db '#' + .db pr1-$-1 + call expr + ld c,l + jr pr3 pr1: - call testc ; is it a dollar? - .db '$' - .db pr4-$-1 - call expr - ld c,l - call testc ; do we have a comma? - .db ',' - .db pr6-$-1 - push bc - call expr - pop bc - ld a,8 ; 8 bits? - cp c - jp nz,pr9 ; no, try 16 - call printhex8 ; yes, print a single hex byte - jp pr3 + call testc ; is it a dollar? + .db '$' + .db pr4-$-1 + call expr + ld c,l + call testc ; do we have a comma? + .db ',' + .db pr6-$-1 + push bc + call expr + pop bc + ld a,8 ; 8 bits? + cp c + jp nz,pr9 ; no, try 16 + call printhex8 ; yes, print a single hex byte + jp pr3 pr9: - ld a,10h ; 16 bits? - cp c - jp nz,qhow ; no, show error message - call printhex ; yes, print two hex bytes - jp pr3 + ld a,10h ; 16 bits? + cp c + jp nz,qhow ; no, show error message + call printhex ; yes, print two hex bytes + jp pr3 pr4: - call qtstg ; is it a string? - jr pr8 + call qtstg ; is it a string? + jr pr8 pr3: - call testc ; is it a comma? - .db ',' - .db pr6-$-1 - call fin - jr pr0 + call testc ; is it a comma? + .db ',' + .db pr6-$-1 + call fin + jr pr0 pr6: - call crlf ; list ends - call finish + call crlf ; list ends + call finish pr8: - call expr ; evaluate the expression - push bc - call printnum - pop bc - jr pr3 + call expr ; evaluate the expression + push bc + call printnum + pop bc + jr pr3 ;************************************************************* ; @@ -1398,7 +1399,7 @@ pr8: ; 'GOSUB EXPR;' OR 'GOSUB EXPR (CR)' IS LIKE THE 'GOTO' ; COMMAND, EXCEPT THAT THE CURRENT TEXT POINTER, STACK POINTER ; ETC. ARE SAVE SO THAT EXECUTION CAN BE CONTINUED AFTER THE -; SUBROUTINE 'RETURN'. IN ORDER THAT 'GOSUB' CAN BE NESTED +; SUBROUTINE 'RETURN'. IN ORDER THAT 'GOSUB' CAN BE NESTED ; (AND EVEN RECURSIVE), THE SAVE AREA MUST BE STACKED. ; THE STACK POINTER IS SAVED IN 'STKGOS', THE OLD 'STKGOS' IS ; SAVED IN THE STACK. IF WE ARE IN THE MAIN ROUTINE, 'STKGOS' @@ -1411,34 +1412,34 @@ pr8: ; NEVER HAD A 'GOSUB' AND IS THUS AN ERROR. ;************************************************************* gosub: - call pusha ; ** Gosub ** - call expr ; save the current "FOR" params - push de ; and text pointer - call findline ; find the target line - jp nz,ahow ; how? because it doesn't exist - ld hl,(current) ; found it, save old 'current' - push hl - ld hl,(stkgos) ; and 'stkgos' - push hl - ld hl,0000h ; and load new ones - ld (loopvar),hl - add hl,sp - ld (stkgos),hl - jp runtsl ; and run the line + call pusha ; ** Gosub ** + call expr ; save the current "FOR" params + push de ; and text pointer + call findline ; find the target line + jp nz,ahow ; how? because it doesn't exist + ld hl,(current) ; found it, save old 'current' + push hl + ld hl,(stkgos) ; and 'stkgos' + push hl + ld hl,0000h ; and load new ones + ld (loopvar),hl + add hl,sp + ld (stkgos),hl + jp runtsl ; and run the line return: - call endchk ; there must be a cr - ld hl,(stkgos) ; check old stack pointer - ld a,h ; - or l - jp z,what ; what? not found - ld sp,hl ; otherwise restore it - pop hl - ld (stkgos),hl - pop hl - ld (current),hl ; and old 'current' - pop de ; and old text pointer - call popa ; and old 'FOR' params - call finish ; and we're back + call endchk ; there must be a cr + ld hl,(stkgos) ; check old stack pointer + ld a,h ; + or l + jp z,what ; what? not found + ld sp,hl ; otherwise restore it + pop hl + ld (stkgos),hl + pop hl + ld (current),hl ; and old 'current' + pop de ; and old text pointer + call popa ; and old 'FOR' params + call finish ; and we're back ;************************************************************* ; @@ -1468,258 +1469,258 @@ return: ; DID NOT MATCH. EITHER WAY, TBI THEN ADDS THE 'STEP' TO ; THAT VARIABLE AND CHECK THE RESULT WITH THE LIMIT. IF IT ; IS WITHIN THE LIMIT, CONTROL LOOPS BACK TO THE COMMAND -; FOLLOWING THE 'FOR'. IF OUTSIDE THE LIMIT, THE SAVE AREA +; FOLLOWING THE 'FOR'. IF OUTSIDE THE LIMIT, THE SAVE AREA ; IS PURGED AND EXECUTION CONTINUES. ;************************************************************* for: - call pusha ; save old save area - call setval ; set the control variable - dec hl ; its address is hl - ld (loopvar),hl ; save that - ld hl,tab5-1 ; use 'exec' to find 'TO' - jp exec + call pusha ; save old save area + call setval ; set the control variable + dec hl ; its address is hl + ld (loopvar),hl ; save that + ld hl,tab5-1 ; use 'exec' to find 'TO' + jp exec fr1: - call expr ; evaluate the limit - ld (looplmt),hl ; and save it - ld hl,tab6-1 ; use 'exec' to find 'STEP' - jp exec + call expr ; evaluate the limit + ld (looplmt),hl ; and save it + ld hl,tab6-1 ; use 'exec' to find 'STEP' + jp exec fr2: - call expr ; found 'STEP' - jr fr4 + call expr ; found 'STEP' + jr fr4 fr3: - ld hl,0001h ; no 'STEP' so set to 1 + ld hl,0001h ; no 'STEP' so set to 1 fr4: - ld (loopinc),hl ; and save that too + ld (loopinc),hl ; and save that too fr5: - ld hl,(current) ; save current line number - ld (loopln),hl - ex de,hl ; and text pointer - ld (loopptr),hl - ld bc,0ah ; dig into stack to find loopvar - ld hl,(loopvar) - ex de,hl - ld h,b - ld l,b - add hl,sp - .db 3eh + ld hl,(current) ; save current line number + ld (loopln),hl + ex de,hl ; and text pointer + ld (loopptr),hl + ld bc,0ah ; dig into stack to find loopvar + ld hl,(loopvar) + ex de,hl + ld h,b + ld l,b + add hl,sp + .db 3eh fr7: - add hl,bc - ld a,(hl) - inc hl - or (hl) - jr z,fr8 - ld a,(hl) - dec hl - cp d - jr nz,fr7 - ld a,(hl) - cp e - jr nz,fr7 - ex de,hl - ld hl,0000h - add hl,sp - ld b,h - ld c,l - ld hl,000ah - add hl,de - call mvdown - ld sp,hl + add hl,bc + ld a,(hl) + inc hl + or (hl) + jr z,fr8 + ld a,(hl) + dec hl + cp d + jr nz,fr7 + ld a,(hl) + cp e + jr nz,fr7 + ex de,hl + ld hl,0000h + add hl,sp + ld b,h + ld c,l + ld hl,000ah + add hl,de + call mvdown + ld sp,hl fr8: - ld hl,(loopptr) ; all done - ex de,hl - call finish + ld hl,(loopptr) ; all done + ex de,hl + call finish next: - call testvar ; get address of variable - jp c,qwhat ; what, no variable - ld (varnext),hl ; yes, save it + call testvar ; get address of variable + jp c,qwhat ; what, no variable + ld (varnext),hl ; yes, save it nx0: - push de ; save the text pointer - ex de,hl - ld hl,(loopvar) ; get the variable in 'FOR' - ld a,h - or l ; if 0, there never was one - jp z,awhat - call comp ; else check them - jr z,nx3 ; yes, they agree - pop de ; no, complete current loop - call popa - ld hl,(varnext) ; and pop one level - jr nx0 ; go check again + push de ; save the text pointer + ex de,hl + ld hl,(loopvar) ; get the variable in 'FOR' + ld a,h + or l ; if 0, there never was one + jp z,awhat + call comp ; else check them + jr z,nx3 ; yes, they agree + pop de ; no, complete current loop + call popa + ld hl,(varnext) ; and pop one level + jr nx0 ; go check again nx3: - ld e,(hl) - inc hl - ld d,(hl) ; de = value of variable - ld hl,(loopinc) - push hl - ld a,h - xor d - ld a,d - add hl,de - jp m,nx4 - xor h - jp m,nx5 + ld e,(hl) + inc hl + ld d,(hl) ; de = value of variable + ld hl,(loopinc) + push hl + ld a,h + xor d + ld a,d + add hl,de + jp m,nx4 + xor h + jp m,nx5 nx4: - ex de,hl - ld hl,(loopvar) - ld (hl),e - inc hl - ld (hl),d - ld hl,(looplmt) - pop af - or a - jp p,nx1 ; step > 0 - ex de,hl ; step < 0 + ex de,hl + ld hl,(loopvar) + ld (hl),e + inc hl + ld (hl),d + ld hl,(looplmt) + pop af + or a + jp p,nx1 ; step > 0 + ex de,hl ; step < 0 nx1: - call ckhlde ; compare with limit - pop de ; restore the text pointer - jr c,nx2 ; over the limit - ld hl,(loopln) ; within the limit - ld (current),hl - ld hl,(loopptr) - ex de,hl - call finish + call ckhlde ; compare with limit + pop de ; restore the text pointer + jr c,nx2 ; over the limit + ld hl,(loopln) ; within the limit + ld (current),hl + ld hl,(loopptr) + ex de,hl + call finish nx5: - pop hl - pop de + pop hl + pop de nx2: - call popa ; purge this loop - call finish ; + call popa ; purge this loop + call finish ; init: - ld hl,start ; initialise random pointer - ld (rndptr),hl - ld hl,usrfunc ; initialise user defined function - ld (usrptr),hl - ld a,0c3h - ld (usrfunc),a ; initiase default USR() behaviour - ld hl,qhow ; (i.e. HOW? error) - ld (usrfunc+1),hl - ld hl,textbegin ; initialise text area pointers - ld (textunfilled),hl - ld (ocsw),a ; enable output control switch - call clrvars ; clear variables - call crlf - ld de,msg1 ; output welcome message - call printstr - call crlf - call size ; output free size message - call printnum - ld de,msg2 - call printstr - jp rstart + ld hl,start ; initialise random pointer + ld (rndptr),hl + ld hl,usrfunc ; initialise user defined function + ld (usrptr),hl + ld a,0c3h + ld (usrfunc),a ; initiase default USR() behaviour + ld hl,qhow ; (i.e. HOW? error) + ld (usrfunc+1),hl + ld hl,textbegin ; initialise text area pointers + ld (textunfilled),hl + ld (ocsw),a ; enable output control switch + call clrvars ; clear variables + call crlf + ld de,msg1 ; output welcome message + call printstr + call crlf + call size ; output free size message + call printnum + ld de,msg2 + call printstr + jp rstart chkio: - call haschar ; check if character available - ret z ; no, return - call getchar ; get the character - push bc ; is it a lf? - ld b,a - sub lf - jr z,io1 ; yes, ignore an return - ld a,b ; no, restore a and bc - pop bc - cp ctrlo ; is it ctrl-o? - jr nz,io2 ; no, done - ld a,(ocsw) ; toggle output control switch - cpl - ld (ocsw),a - jr chkio ; get next character + call haschar ; check if character available + ret z ; no, return + call getchar ; get the character + push bc ; is it a lf? + ld b,a + sub lf + jr z,io1 ; yes, ignore an return + ld a,b ; no, restore a and bc + pop bc + cp ctrlo ; is it ctrl-o? + jr nz,io2 ; no, done + ld a,(ocsw) ; toggle output control switch + cpl + ld (ocsw),a + jr chkio ; get next character io1: - ld a,0h ; clear - or a ; set the z-flag - pop bc ; restore bc - ret ; return with z set + ld a,0h ; clear + or a ; set the z-flag + pop bc ; restore bc + ret ; return with z set io2: - cp 60h ; is it lower case? - jp c,io3 ; no - and 0dfh ; yes, make upper case + cp 60h ; is it lower case? + jp c,io3 ; no + and 0dfh ; yes, make upper case io3: - cp ctrlc ; is it ctrl-c? - ret nz ; no - jp rstart ; yes, restart tasty basic + cp ctrlc ; is it ctrl-c? + ret nz ; no + jp rstart ; yes, restart tasty basic crlf: - ld a,cr + ld a,cr outc: - push af - ld a,(ocsw) ; check output control switch - or a - jr nz,oc1 ; output is enabled - pop af ; output is disabled - ret ; so return + push af + ld a,(ocsw) ; check output control switch + or a + jr nz,oc1 ; output is enabled + pop af ; output is disabled + ret ; so return oc1: - pop af - call putchar - cp cr ; was it a cr? - ret nz ; no, return - ld a,lf ; send a lf - call outc - ld a,cr ; restore register - ret ; and return + pop af + call putchar + cp cr ; was it a cr? + ret nz ; no, return + ld a,lf ; send a lf + call outc + ld a,cr ; restore register + ret ; and return putchar: #ifdef ZEMU - call uart_tx_ready ; see if transmit is available - out (tty_data),a ; and send it - ret + call uart_tx_ready ; see if transmit is available + out (tty_data),a ; and send it + ret uart_tx_ready: - push af + push af uart_tx_ready_loop: - in a,(tty_status) - bit tx_empty,a - jp z,uart_tx_ready_loop - pop af + in a,(tty_status) + bit tx_empty,a + jp z,uart_tx_ready_loop + pop af #else - PUSH AF - PUSH BC - PUSH DE - PUSH HL - ; OUTPUT CHARACTER TO CONSOLE VIA HBIOS - LD E,A ; OUTPUT CHAR TO E - LD C,CIODEV_CONSOLE ; CONSOLE UNIT TO C - LD B,BF_CIOOUT ; HBIOS FUNC: OUTPUT CHAR - RST 08 ; HBIOS OUTPUTS CHARACTER - POP HL - POP DE - POP BC - POP AF + PUSH AF + PUSH BC + PUSH DE + PUSH HL + ; OUTPUT CHARACTER TO CONSOLE VIA HBIOS + LD E,A ; OUTPUT CHAR TO E + LD C,CIO_CONSOLE ; CONSOLE UNIT TO C + LD B,BF_CIOOUT ; HBIOS FUNC: OUTPUT CHAR + RST 08 ; HBIOS OUTPUTS CHARACTER + POP HL + POP DE + POP BC + POP AF #endif - ret + ret haschar: #ifdef ZEMU - in a,(tty_status) ; check if character available - bit rx_full,a + in a,(tty_status) ; check if character available + bit rx_full,a #else - PUSH BC - PUSH DE - PUSH HL - ; GET CONSOLE INPUT STATUS VIA HBIOS - LD C,CIODEV_CONSOLE; CONSOLE UNIT TO C - LD B,BF_CIOIST ; HBIOS FUNC: INPUT STATUS - RST 08 ; HBIOS RETURNS STATUS IN A - POP HL - POP DE - POP BC + PUSH BC + PUSH DE + PUSH HL + ; GET CONSOLE INPUT STATUS VIA HBIOS + LD C,CIO_CONSOLE ; CONSOLE UNIT TO C + LD B,BF_CIOIST ; HBIOS FUNC: INPUT STATUS + RST 08 ; HBIOS RETURNS STATUS IN A + POP HL + POP DE + POP BC #endif - ret + ret getchar: #ifdef ZEMU - in a,(tty_data) ; get the character + in a,(tty_data) ; get the character #else - PUSH BC - PUSH DE - PUSH HL - ; INPUT CHARACTER FROM CONSOLE VIA HBIOS - LD C,CIODEV_CONSOLE ; CONSOLE UNIT TO C - LD B,BF_CIOIN ; HBIOS FUNC: INPUT CHAR - RST 08 ; HBIOS READS CHARACTDR - LD A,E ; MOVE CHARACTER TO A FOR RETURN - ; RESTORE REGISTERS (AF IS OUTPUT) - POP HL - POP DE - POP BC + PUSH BC + PUSH DE + PUSH HL + ; INPUT CHARACTER FROM CONSOLE VIA HBIOS + LD C,CIO_CONSOLE ; CONSOLE UNIT TO C + LD B,BF_CIOIN ; HBIOS FUNC: INPUT CHAR + RST 08 ; HBIOS READS CHARACTDR + LD A,E ; MOVE CHARACTER TO A FOR RETURN + ; RESTORE REGISTERS (AF IS OUTPUT) + POP HL + POP DE + POP BC #endif - ret + ret ;************************************************************* ; @@ -1735,7 +1736,7 @@ getchar: ; ALL DIRECT AND STATEMENT COMMANDS. ; ; A '.' IN THE STRING WILL TERMINATE THE TEST AND THE PARTIAL -; MATCH WILL BE CONSIDERED AS A MATCH. E.G., 'P.', 'PR.', +; MATCH WILL BE CONSIDERED AS A MATCH. E.G., 'P.', 'PR.', ; 'PRI.', 'PRIN.', OR 'PRINT' WILL ALL MATCH 'PRINT'. ; ; THE TABLE CONSISTS OF ANY NUMBER OF ITEMS. EACH ITEM @@ -1747,155 +1748,155 @@ getchar: ; STRING DOES NOT MATCH ANY OF THE OTHER ITEMS, IT WILL ; MATCH THIS NULL ITEM AS DEFAULT. ;************************************************************* -tab1: ; direct commands - .db "LIST" - dwa(list) - .db "RUN" - dwa(run) - .db "NEW" - dwa(new) - .db "CLEAR" - dwa(clear) +tab1: ; direct commands + .db "LIST" + dwa(list) + .db "RUN" + dwa(run) + .db "NEW" + dwa(new) + .db "CLEAR" + dwa(clear) #ifndef ZEMU - .db "BYE" - dwa(bye) + .db "BYE" + dwa(bye) #endif -tab2: ; direct/statement - .db "NEXT" - dwa(next) - .db "LET" - dwa(let) - .db "IF" - dwa(iff) - .db "GOTO" - dwa(goto) - .db "GOSUB" - dwa(gosub) - .db "RETURN" - dwa(return) - .db "REM" - dwa(rem) - .db "FOR" - dwa(for) - .db "INPUT" - dwa(input) - .db "PRINT" - dwa(print) - .db "POKE" - dwa(poke) - .db "END" - dwa(endd) - dwa(deflt) -tab4: ; functions - .db "PEEK" - dwa(peek) - .db "RND" - dwa(rnd) - .db "ABS" - dwa(abs) - .db "USR" - dwa(usrexec) - .db "SIZE" - dwa(size) - dwa(xp40) -tab5: ; 'TO' in 'FOR' - .db "TO" - dwa(fr1) -tab6: ; 'STEP' in 'FOR' - .db "STEP" - dwa(fr2) - dwa(fr3) -tab8: ; relational operators - .db ">=" - dwa(xp11) - .db "#" - dwa(xp12) - .db ">" - dwa(xp13) - .db "=" - dwa(xp15) - .db "<=" - dwa(xp14) - .db "<" - dwa(xp16) - dwa(xp17) +tab2: ; direct/statement + .db "NEXT" + dwa(next) + .db "LET" + dwa(let) + .db "IF" + dwa(iff) + .db "GOTO" + dwa(goto) + .db "GOSUB" + dwa(gosub) + .db "RETURN" + dwa(return) + .db "REM" + dwa(rem) + .db "FOR" + dwa(for) + .db "INPUT" + dwa(input) + .db "PRINT" + dwa(print) + .db "POKE" + dwa(poke) + .db "END" + dwa(endd) + dwa(deflt) +tab4: ; functions + .db "PEEK" + dwa(peek) + .db "RND" + dwa(rnd) + .db "ABS" + dwa(abs) + .db "USR" + dwa(usrexec) + .db "SIZE" + dwa(size) + dwa(xp40) +tab5: ; 'TO' in 'FOR' + .db "TO" + dwa(fr1) +tab6: ; 'STEP' in 'FOR' + .db "STEP" + dwa(fr2) + dwa(fr3) +tab8: ; relational operators + .db ">=" + dwa(xp11) + .db "#" + dwa(xp12) + .db ">" + dwa(xp13) + .db "=" + dwa(xp15) + .db "<=" + dwa(xp14) + .db "<" + dwa(xp16) + dwa(xp17) direct: - ld hl,tab1-1 ; ** Direct ** + ld hl,tab1-1 ; ** Direct ** exec: - call skipspace ; ** Exec ** - push de + call skipspace ; ** Exec ** + push de ex1: - ld a,(de) - inc de - cp '.' - jr z,ex3 - inc hl - cp (hl) - jr z,ex1 - ld a,7fh - dec de - cp (hl) - jr c,ex5 + ld a,(de) + inc de + cp '.' + jr z,ex3 + inc hl + cp (hl) + jr z,ex1 + ld a,7fh + dec de + cp (hl) + jr c,ex5 ex2: - inc hl - cp (hl) - jr nc,ex2 - inc hl - pop de - jr exec + inc hl + cp (hl) + jr nc,ex2 + inc hl + pop de + jr exec ex3: - ld a,7fh + ld a,7fh ex4: - inc hl - cp (hl) - jr nc,ex4 + inc hl + cp (hl) + jr nc,ex4 ex5: - ld a,(hl) - inc hl - ld l,(hl) - and 7fh - ld h,a - pop af - jp (hl) + ld a,(hl) + inc hl + ld l,(hl) + and 7fh + ld h,a + pop af + jp (hl) ;------------------------------------------------------------------------------- -LST_ROM: ; all the above _can_ be in rom - ; all following *must* be in ram - .org (TBC_LOC + 09feh) -usrptr: .ds 2 ; -> user defined function area - .org (TBC_LOC + 0a00h) -usrfunc .ds 2 ; start of user defined function area - .org (TBC_LOC + 0c00h) ; start of state -ocsw .ds 1 ; output control switch -current .ds 2 ; points to current line -stkgos .ds 2 ; saves sp in 'GOSUB' -varnext .ds 2 ; temp storage -stkinp .ds 2 ; save sp in 'INPUT' -loopvar .ds 2 ; 'FOR' loop save area -loopinc .ds 2 ; loop increment -looplmt .ds 2 ; loop limit -loopln .ds 2 ; loop line number -loopptr .ds 2 ; loop text pointer -rndptr .ds 2 ; random number pointer -textunfilled .ds 2 ; -> unfilled text area -textbegin .ds 2 ; start of text save area - .org (TBC_LOC + 07dffh) -textend .ds 0 ; end of text area -varbegin .ds 55 ; variable @(0) -varend .ds 0 ; end of variable area -buffer .ds 72 ; input buffer -bufend .ds 1 -stacklimit .ds 1 - .org (TBC_LOC + 07fffh) -stack .equ $ +LST_ROM: ; all the above _can_ be in rom + ; all following *must* be in ram + .org (TBC_LOC + 09feh) +usrptr: .ds 2 ; -> user defined function area + .org (TBC_LOC + 0a00h) +usrfunc .ds 2 ; start of user defined function area + .org (TBC_LOC + 0c00h) ; start of state +ocsw .ds 1 ; output control switch +current .ds 2 ; points to current line +stkgos .ds 2 ; saves sp in 'GOSUB' +varnext .ds 2 ; temp storage +stkinp .ds 2 ; save sp in 'INPUT' +loopvar .ds 2 ; 'FOR' loop save area +loopinc .ds 2 ; loop increment +looplmt .ds 2 ; loop limit +loopln .ds 2 ; loop line number +loopptr .ds 2 ; loop text pointer +rndptr .ds 2 ; random number pointer +textunfilled .ds 2 ; -> unfilled text area +textbegin .ds 2 ; start of text save area + .org (TBC_LOC + 07dffh) +textend .ds 0 ; end of text area +varbegin .ds 55 ; variable @(0) +varend .ds 0 ; end of variable area +buffer .ds 72 ; input buffer +bufend .ds 1 +stacklimit .ds 1 + .org (TBC_LOC + 07fffh) +stack .equ $ #ifndef ZEMU -SLACK .EQU (TBC_END - LST_ROM) - .FILL SLACK,'t' +SLACK .EQU (TBC_END - LST_ROM) + .FILL SLACK,'t' - .ECHO "TASTYBASIC space remaining: " - .ECHO SLACK - .ECHO " bytes.\n" + .ECHO "TASTYBASIC space remaining: " + .ECHO SLACK + .ECHO " bytes.\n" #endif - .end + .end diff --git a/Source/HBIOS/tms.asm b/Source/HBIOS/tms.asm index 49d317b7..4759cf61 100644 --- a/Source/HBIOS/tms.asm +++ b/Source/HBIOS/tms.asm @@ -14,17 +14,19 @@ ; TMS DRIVER - CONSTANTS ;====================================================================== ; -#IF (PLATFORM == PLT_N8) +#IF (TMSMODE == TMSMODE_N8) -TMS_DATREG .EQU N8_BASE + $18 ; READ/WRITE DATA -TMS_CMDREG .EQU N8_BASE + $19 ; READ STATUS / WRITE REG SEL +TMS_DATREG .EQU $98 ; READ/WRITE DATA +TMS_CMDREG .EQU $99 ; READ STATUS / WRITE REG SEL -TMS_PPIA .EQU N8_BASE + $04 ; PPI PORT A -TMS_PPIB .EQU N8_BASE + $05 ; PPI PORT B -TMS_PPIC .EQU N8_BASE + $06 ; PPI PORT C -TMS_PPIX .EQU N8_BASE + $07 ; PPI CONTROL PORT +TMS_PPIA .EQU $84 ; PPI PORT A +TMS_PPIB .EQU $85 ; PPI PORT B +TMS_PPIC .EQU $86 ; PPI PORT C +TMS_PPIX .EQU $87 ; PPI CONTROL PORT -#ELSE +#ENDIF + +#IF (TMSMODE == TMSMODE_SCG) TMS_DATREG .EQU $98 ; READ/WRITE DATA TMS_CMDREG .EQU $99 ; READ STATUS / WRITE REG SEL @@ -40,17 +42,20 @@ TMS_PPIX .EQU 0 ; PPI CONTROL PORT TMS_ROWS .EQU 24 TMS_COLS .EQU 40 ; +#DEFINE USEFONT8X8 +#DEFINE TMS_FONT FONT8X8 +; TERMENABLE .SET TRUE ; INCLUDE TERMINAL PSEUDODEVICE DRIVER ; ; TMS_IODELAY IS USED TO ADD RECOVERY TIME TO TMS9918 ACCESSES ; IF YOU SEE SCREEN CORRUPTION, ADJUST THIS!!! ; -#IF (PLATFORM == PLT_N8) -; BELOW WAS TUNED FOR N8 AT 18MHZ WITH 3 IO WAIT STATES -#DEFINE TMS_IODELAY NOP \ NOP \ NOP \ NOP \ NOP \ NOP \ NOP \ NOP +#IF (TMSMODE == TMSMODE_N8) +; BELOW WAS TUNED FOR N8 AT 18MHZ +#DEFINE TMS_IODELAY EX (SP),HL \ EX (SP),HL ; 38 W/S #ELSE ; BELOW WAS TUNED FOR SBC AT 8MHZ -#DEFINE TMS_IODELAY NOP \ NOP +#DEFINE TMS_IODELAY NOP \ NOP ; 8 W/S #ENDIF ; ;====================================================================== @@ -58,14 +63,16 @@ TERMENABLE .SET TRUE ; INCLUDE TERMINAL PSEUDODEVICE DRIVER ;====================================================================== ; TMS_INIT: -#IF (PLATFORM != PLT_N8) +#IF (CPUFAM == CPU_Z180) + CALL TMS_Z180IO +#ENDIF +; +#IF (TMSMODE == TMSMODE_SCG) LD A,$FF OUT (TMS_ACR),A ; INIT AUX CONTROL REG #ENDIF ; -#IF (PLATFORM == PLT_N8) LD IY,TMS_IDAT ; POINTER TO INSTANCE DATA -#ENDIF ; CALL NEWLINE ; FORMATTING PRTS("TMS: IO=0x$") @@ -82,8 +89,8 @@ TMS_INIT: TMS_INIT1: CALL TMS_CRTINIT ; SETUP THE TMS CHIP REGISTERS CALL TMS_LOADFONT ; LOAD FONT DATA FROM ROM TO TMS STRORAGE - CALL TMS_VDARES -#IF (PLATFORM == PLT_N8) + CALL TMS_VDARES1 +#IF (TMSMODE == TMSMODE_N8) CALL PPK_INIT ; INITIALIZE KEYBOARD DRIVER #ENDIF ; @@ -118,7 +125,7 @@ TMS_FNTBL: .DW TMS_VDAFIL .DW TMS_VDACPY .DW TMS_VDASCR -#IF (PLATFORM == PLT_N8) +#IF (TMSMODE == TMSMODE_N8) .DW PPK_STAT .DW PPK_FLUSH .DW PPK_READ @@ -147,6 +154,10 @@ TMS_VDAQRY: RET TMS_VDARES: +#IF (CPUFAM == CPU_Z180) + CALL TMS_Z180IO +#ENDIF +TMS_VDARES1: ; ENTRY POINT TO AVOID TMS_Z180IO RECURSION LD DE,0 ; ROW = 0, COL = 0 CALL TMS_XY ; SEND CURSOR TO TOP LEFT LD A,' ' ; BLANK THE SCREEN @@ -172,6 +183,9 @@ TMS_VDASCS: CALL PANIC ; NOT IMPLEMENTED (YET) TMS_VDASCP: +#IF (CPUFAM == CPU_Z180) + CALL TMS_Z180IO +#ENDIF CALL TMS_CLRCUR CALL TMS_XY ; SET CURSOR POSITION CALL TMS_SETCUR @@ -187,6 +201,9 @@ TMS_VDASCO: RET TMS_VDAWRC: +#IF (CPUFAM == CPU_Z180) + CALL TMS_Z180IO +#ENDIF CALL TMS_CLRCUR ; CURSOR OFF LD A,E ; CHARACTER TO WRITE GOES IN A CALL TMS_PUTCHAR ; PUT IT ON THE SCREEN @@ -195,6 +212,9 @@ TMS_VDAWRC: RET TMS_VDAFIL: +#IF (CPUFAM == CPU_Z180) + CALL TMS_Z180IO +#ENDIF CALL TMS_CLRCUR LD A,E ; FILL CHARACTER GOES IN A EX DE,HL ; FILL LENGTH GOES IN DE @@ -204,6 +224,9 @@ TMS_VDAFIL: RET TMS_VDACPY: +#IF (CPUFAM == CPU_Z180) + CALL TMS_Z180IO +#ENDIF CALL TMS_CLRCUR ; LENGTH IN HL, SOURCE ROW/COL IN DE, DEST IS TMS_POS ; BLKCPY USES: HL=SOURCE, DE=DEST, BC=COUNT @@ -217,6 +240,9 @@ TMS_VDACPY: RET TMS_VDASCR: +#IF (CPUFAM == CPU_Z180) + CALL TMS_Z180IO +#ENDIF CALL TMS_CLRCUR TMS_VDASCR0: LD A,E ; LOAD E INTO A @@ -238,8 +264,6 @@ TMS_VDASCR2: XOR A RET -#IF (PLATFORM != PLT_N8) - ; DUMMY FUNCTIONS BELOW BECAUSE SCG BOARD HAS NO ; KEYBOARD INTERFACE @@ -255,8 +279,6 @@ TMS_READ: LD E,26 ; RETURN (CTRL-Z) XOR A ; SIGNAL SUCCESS RET - -#ENDIF ; ;====================================================================== ; TMS DRIVER - PRIVATE DRIVER FUNCTIONS @@ -370,31 +392,43 @@ TMS_LOADFONT: ; SET WRITE ADDRESS TO $800 LD HL,$800 CALL TMS_WR + +#IF USELZSA2 + LD (TMS_STACK),SP ; SAVE STACK + LD HL,(TMS_STACK) ; AND SHIFT IT + LD DE,$2000 ; DOWN 4KB TO + CCF ; CREATE A + SBC HL,DE ; DECOMPRESSION BUFFER + LD SP,HL ; HL POINTS TO BUFFER + EX DE,HL ; START OF STACK BUFFER + PUSH DE ; SAVE IT + LD HL,TMS_FONT ; START OF FONT DATA + CALL DLZSA2 ; DECOMPRESS TO DE + POP HL ; RECALL STACK BUFFER POSITION +#ELSE + LD HL,TMS_FONT ; START OF FONT DATA +#ENDIF ; ; FILL $800 BYTES FROM FONTDATA - LD HL,FONT_TMS - LD DE,$100 * 8 + LD DE,$800 TMS_LOADFONT1: - LD B,8 -TMS_LOADFONT2: LD A,(HL) - PUSH AF - INC HL - DJNZ TMS_LOADFONT2 -; - LD B,8 -TMS_LOADFONT3: - POP AF OUT (TMS_DATREG),A TMS_IODELAY ; DELAY + INC HL DEC DE - DJNZ TMS_LOADFONT3 -; LD A,D OR E JR NZ,TMS_LOADFONT1 ; +#IF USELZSA2 + LD HL,(TMS_STACK) ; ERASE DECOMPRESS BUFFER + LD SP,HL ; BY RESTORING THE STACK + RET ; DONE +TMS_STACK .DW 0 +#ELSE RET +#ENDIF ; ;---------------------------------------------------------------------- ; VIRTUAL CURSOR MANAGEMENT @@ -678,6 +712,39 @@ TMS_BLKCPY3: ; RET ; +;---------------------------------------------------------------------- +; Z180 LOW SPEED I/O CODE BRACKETING +;---------------------------------------------------------------------- +; +#IF (CPUFAM == CPU_Z180) +; +TMS_Z180IO: + ; HOOK CALLERS RETURN TO RESTORE DCNTL + EX (SP),HL ; SAVE HL & HL := RET ADR + LD (TMS_Z180IOR),HL ; SET RET ADR + LD HL,TMS_Z180IOX ; HL := SPECIAL RETURN ADR + EX (SP),HL ; RESTORE HL, INS NEW RET ADR + ; SET Z180 MAX I/O WAIT STATES + PUSH AF ; SAVE AF + IN0 A,(Z180_DCNTL) ; GET CURRENT Z180 DCNTL + LD (TMS_DCNTL),A ; SAVE IT + OR %00110000 ; NEW DCNTL VALUE (MAX I/O W/S) + OUT0 (Z180_DCNTL),A ; IMPLEMENT IT + POP AF ; RESTORE AF + ; BACK TO CALLER +TMS_Z180IOR .EQU $+1 + JP $0000 ; BACK TO CALLER +; +TMS_Z180IOX: + ; RESTORE ORIGINAL DCNTL + PUSH AF ; SAVE AF + LD A,(TMS_DCNTL) ; ORIG DCNTL + OUT0 (Z180_DCNTL),A ; IMPLEMENT IT + POP AF ; RESTORE AF + RET ; DONE +; +#ENDIF +; ;================================================================================================== ; TMS DRIVER - DATA ;================================================================================================== @@ -740,6 +807,10 @@ TMS_INIT9918: ; TMS_INIT9918LEN .EQU $ - TMS_INIT9918 ; +#IF (CPUFAM == CPU_Z180) +TMS_DCNTL .DB $00 ; SAVE Z180 DCNTL AS NEEDED +#ENDIF +; ;================================================================================================== ; TMS DRIVER - INSTANCE DATA ;================================================================================================== diff --git a/Source/HBIOS/uart.asm b/Source/HBIOS/uart.asm index 2e8fdb8f..75466d50 100644 --- a/Source/HBIOS/uart.asm +++ b/Source/HBIOS/uart.asm @@ -251,7 +251,7 @@ UART_INITDEV1: JR Z,UART_INITDEV2 ; USE EFR REGISTER CP UART_16850 ; 16850? JR Z,UART_INITDEV2 ; USE EFR REGISTER - JR UART_INITDEV4 ; NO EFT, SKIP AHEAD + JR UART_INITDEV4 ; NO EFR, SKIP AHEAD ; UART_INITDEV2: ; WE HAVE AN EFR CAPABLE CHIP, SET EFR REGISTER @@ -326,6 +326,7 @@ UART_QUERY: UART_DEVICE: LD D,CIODEV_UART ; D := DEVICE TYPE LD E,(IY) ; E := PHYSICAL UNIT + LD C,$00 ; C := DEVICE TYPE, 0x00 IS RS-232 XOR A ; SIGNAL SUCCESS RET ; @@ -568,67 +569,77 @@ UART_DEV .DB 0 ; DEVICE NUM USED DURING INIT ; UART PORT TABLE ; UART_CFG: -#IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA) | (PLATFORM == PLT_ZETA2)) +#IF (UARTSBC) ; SBC/ZETA ONBOARD SERIAL PORT .DB 0 ; DEVICE NUMBER (UPDATED DURING INIT) .DB 0 ; UART TYPE .DB $68 ; IO PORT BASE (RBR, THR) .DB $68 + UART_LSR ; LINE STATUS PORT (LSR) - .DW DEFSERCFG ; LINE CONFIGURATION + .DW UARTCFG ; LINE CONFIGURATION .FILL 2,$FF ; FILLER #ENDIF -#IF (PLATFORM == PLT_SBC) | (PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4)) +#IF (UARTCAS) ; CASSETTE INTERFACE SERIAL PORT .DB 0 ; DEVICE NUMBER (UPDATED DURING INIT) .DB 0 ; UART TYPE .DB $80 ; IO PORT BASE (RBR, THR) .DB $80 + UART_LSR ; LINE STATUS PORT (LSR) - .DW SER_300_8N1 ; LINE CONFIGURATION + .DW UARTCASSPD ; LINE CONFIGURATION .FILL 2,$FF ; FILLER #ENDIF -#IF (PLATFORM == PLT_SBC) +#IF (UARTMFP) ; MF/PIC SERIAL PORT .DB 0 ; DEVICE NUMBER (UPDATED DURING INIT) .DB 0 ; UART TYPE .DB $48 ; IO PORT BASE (RBR, THR) .DB $48 + UART_LSR ; LINE STATUS PORT (LSR) - .DW DEFSERCFG ; LINE CONFIGURATION + .DW UARTCFG ; LINE CONFIGURATION .FILL 2,$FF ; FILLER #ENDIF -#IF (PLATFORM == PLT_SBC) | (PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4)) +#IF (UART4) ; 4UART SERIAL PORT A .DB 0 ; DEVICE NUMBER (UPDATED DURING INIT) .DB 0 ; UART TYPE .DB $C0 ; IO PORT BASE (RBR, THR) .DB $C0 + UART_LSR ; LINE STATUS PORT (LSR) - .DW DEFSERCFG ; LINE CONFIGURATION + .DW UARTCFG ; LINE CONFIGURATION .FILL 2,$FF ; FILLER -#ENDIF -#IF (PLATFORM == PLT_SBC) | (PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4)) ; 4UART SERIAL PORT B .DB 0 ; DEVICE NUMBER (UPDATED DURING INIT) .DB 0 ; UART TYPE .DB $C8 ; IO PORT BASE (RBR, THR) .DB $C8 + UART_LSR ; LINE STATUS PORT (LSR) - .DW DEFSERCFG ; LINE CONFIGURATION + .DW UARTCFG ; LINE CONFIGURATION .FILL 2,$FF ; FILLER -#ENDIF -#IF (PLATFORM == PLT_SBC) | (PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4)) ; 4UART SERIAL PORT C .DB 0 ; DEVICE NUMBER (UPDATED DURING INIT) .DB 0 ; UART TYPE .DB $D0 ; IO PORT BASE (RBR, THR) .DB $D0 + UART_LSR ; LINE STATUS PORT (LSR) - .DW DEFSERCFG ; LINE CONFIGURATION + .DW UARTCFG ; LINE CONFIGURATION .FILL 2,$FF ; FILLER -#ENDIF -#IF (PLATFORM == PLT_SBC) | (PLATFORM == PLT_N8) | (PLATFORM == PLT_MK4)) ; 4UART SERIAL PORT D .DB 0 ; DEVICE NUMBER (UPDATED DURING INIT) .DB 0 ; UART TYPE .DB $D8 ; IO PORT BASE (RBR, THR) .DB $D8 + UART_LSR ; LINE STATUS PORT (LSR) - .DW DEFSERCFG ; LINE CONFIGURATION + .DW UARTCFG ; LINE CONFIGURATION + .FILL 2,$FF ; FILLER +#ENDIF +#IF (UARTRC) + ; UARTRC SERIAL PORT A + .DB 0 ; DEVICE NUMBER (UPDATED DURING INIT) + .DB 0 ; UART TYPE + .DB $A0 ; IO PORT BASE (RBR, THR) + .DB $A0 + UART_LSR ; LINE STATUS PORT (LSR) + .DW UARTCFG ; LINE CONFIGURATION + .FILL 2,$FF ; FILLER + ; UARTRC SERIAL PORT B + .DB 0 ; DEVICE NUMBER (UPDATED DURING INIT) + .DB 0 ; UART TYPE + .DB $A8 ; IO PORT BASE (RBR, THR) + .DB $A8 + UART_LSR ; LINE STATUS PORT (LSR) + .DW UARTCFG ; LINE CONFIGURATION .FILL 2,$FF ; FILLER #ENDIF ; diff --git a/Source/HBIOS/uf.asm b/Source/HBIOS/uf.asm new file mode 100644 index 00000000..efc3d9b3 --- /dev/null +++ b/Source/HBIOS/uf.asm @@ -0,0 +1,182 @@ +;================================================================================================== +; ECB USB-FIFO DRIVER FOR WILL SOWERBUTTS ADAFRUIT BASED FT232H ECB-FIFO BOARD +; REFER https://www.retrobrewcomputers.org/doku.php?id=boards:ecb:usb-fifo:start +; PHIL SUMMERS (b1ackmai1er) +;================================================================================================== +; +; BASE PORT IS SET IN CFG_SBC.INC +; INTERRUPTS ARE NOT USED. +; ONLY ONE BOARD SUPPORTED. +; +; HBIOS CALLS: +; +; UF_PREINIT SETUP THE DISPATCH TABLE ENTRY AND INITIALIZE HARDWARE +; UF_INIT ANNOUNCE DEVICE DESCRIPTION AND PORT +; +FIFO_DATA .EQU (UFBASE+0) ; READ/WRITE DATA +FIFO_STATUS .EQU (UFBASE+1) ; READ/WRITE STATUS +FIFO_SEND_IMM .EQU (UFBASE+2) ; WRITE PORT TO FORCE BUFFER FLUSH +FIFO_BUFFER .EQU FALSE ; OPTION TO BUFFER OUTPUT FOR 17ms +; +UF_USB_ACTIVE .DB 0 ; USB CABLE CONNECTED STATUS FLAG +; +; DEVICE DESCRIPTION TABLE +; +UF_CFG: .DW SER_9600_8N1 ; DUMMY CONFIGURATION +; +; SETUP THE DISPATCH TABLE ENTRY AND INITIALIZE HARDWARE +; +UF_PREINIT: + LD HL,UF_CFG ; POINT TO START OF CFG TABLE + PUSH HL ; COPY CFG DATA PTR + PUSH HL + POP IY ; ... TO IY + CALL UF_INITUNIT ; HAND OFF TO GENERIC INIT CODE + LD (UF_USB_ACTIVE),A ; SAVE USB CONNECTION STATUS + POP DE ; GET ENTRY ADDRESS BACK, BUT PUT IN DE +; JR Z,UF_FAIL ; EXIT IF NO USB CONNECTION + LD BC,UF_FNTBL ; BC := FUNCTION TABLE ADDRESS + CALL CIO_ADDENT ; ADD ENTRY IF FOUND, BC:DE + XOR A ; SIGNAL SUCCESS +UF_FAIL: + RET ; AND RETURN +; +; INITIALIZATION ROUTINE +; +UF_INITUNIT: + CALL UF_DETECT ; DETERMINE TYPE + OR A ; SET FLAGS + RET Z ; ABORT IF NOTHING THERE + + ; SET DEFAULT CONFIG + LD DE,-1 ; LEAVE CONFIG ALONE + JR UF_INITDEV ; IMPLEMENT IT AND RETURN +; +; ANNOUNCE DEVICE DESCRIPTION AND PORT +; +UF_INIT: + CALL NEWLINE ; PRINT DEVICE + CALL PRTSTRD + .TEXT "USB-FIFO: IO=0x$") + LD A,UFBASE ; PRINT PORT + CALL PRTHEXBYTE + LD A,(UF_USB_ACTIVE) ; PRINT CONNECTION STATUS + OR A ; REQUIRES TERMINAL PROGRAM + RET NZ ; TO HAVE INITIALIZED PORT + CALL PRTSTRD ; ON PC SIDE. + .TEXT " No connection$" + RET +; +; INPUT A CHARACTER AND RETURN IT IN E +; +UF_IN: + CALL UF_IST ; CHAR WAITING? + JR Z,UF_IN ; LOOP IF NOT + LD C,FIFO_DATA ; C := INPUT PORT + IN E,(C) ; GET CHAR + XOR A ; SIGNAL SUCCESS + RET +; +; OUTPUT THE CHARACTER IN E +; +UF_OUT: + CALL UF_OST ; READY FOR CHAR? + JR Z,UF_OUT ; LOOP IF NOT + LD C,FIFO_DATA + OUT (C),E ; WRITE TO FIFO +#IF (FIFO_BUFFER) + OUT (FIFO_SEND_IMM),A ; SEND IMMEDIATE +#ENDIF + XOR A ; SIGNAL SUCCESS + RET +; +; INPUT STATUS - CAN WE SEND A CHARACTER +; +UF_IST: + IN A,(FIFO_STATUS) ; IS THE QUEUE EMPTY? + RLCA + CPL + AND 00000001B + RET +; +; OUTPUT STATUS - CAN WE OUTPUT A CHARACTER +; +UF_OST: + IN A,(FIFO_STATUS) ; IS THE SEND BUFFER FULL? + CPL + AND 00000001B + RET +; +; INITIALIZATION THE SETUP PARAMETER WORD AND INITIALIZE DEVICE +; SAVE NEW SPW IF NOT A RE-INIT. ALWAYS INITIALIZE DEVICE. +; SPW IS NOT VALIDATED BUT IT IS NOT USED FOR ANYTHING. +; +UF_INITDEV: +; + ; TEST FOR -1 WHICH MEANS USE CURRENT CONFIG (JUST REINIT) + LD A,D ; TEST DE FOR + AND E ; ... VALUE OF -1 + INC A ; ... SO Z SET IF -1 + JR NZ,UF_INITDEV1 ; IF DE == -1, REINIT CURRENT CONFIG +; + ; GET CURRENT SPW. WE ALWAYS RESAVE AT END + LD E,(IY+0) ; LOW BYTE + LD D,(IY+1) ; HIGH BYTE +; +UF_INITDEV1: + XOR A ; INTERRUPTS OFF + OUT (FIFO_STATUS),A +; +UF_FLUSH: + IN A,(FIFO_STATUS) ; IS THERE ANY DATA + RLCA ; IN THE BUFFER ? + JR C,UFBUFEMPTY ; EXIT IF EMPTY +; + IN A,(FIFO_DATA) ; CLEAR BUFFER BY READING + JR UF_FLUSH ; ALL THE DATA +UFBUFEMPTY: + LD (IY+0),E ; SAVE LOW WORD + LD (IY+1),D ; SAVE HI WORD + RET ; NZ STATUS HERE INDICATES FAIL. +; +; USB-FIFO WILL APPEAR AS A SERIAL DEVICE AT DEFAULT SERIAL MODE +; +UF_QUERY: + LD E,(IY+0) ; FIRST CONFIG BYTE TO E + LD D,(IY+1) ; SECOND CONFIG BYTE TO D + XOR A ; SIGNAL SUCCESS + RET ; DONE +; +; USB-FIFO WILL APPEAR AS A SERIAL DEVICE +; +UF_DEVICE: + LD D,CIODEV_UF ; D := DEVICE TYPE + XOR A ; SIGNAL SUCCESS + LD E,A ; E := PHYSICAL UNIT, ALWAYS 0 + LD C,A ; C := DEVICE TYPE, 0x00 IS RS-232 + RET +; +; USB-FIFO DETECTION ROUTINE +; +UF_DETECT: + IN A,(FIFO_STATUS) + AND 10000001B + SUB 10000001B ; A=0 CABLE DISCONNECTED + RET Z ; OR PC PORT CLOSED + LD A,1 ; A=1 CABLE CONNECTED + RET ; AND PC PORT OPEN +; +; DRIVER FUNCTION TABLE +; +UF_FNTBL: + .DW UF_IN + .DW UF_OUT + .DW UF_IST + .DW UF_OST + .DW UF_INITDEV + .DW UF_QUERY + .DW UF_DEVICE +#IF (($ - UF_FNTBL) != (CIO_FNCNT * 2)) + .ECHO "*** INVALID USB-FIFO FUNCTION TABLE ***\n" +#ENDIF +; diff --git a/Source/HBIOS/unlzsa2s.asm b/Source/HBIOS/unlzsa2s.asm new file mode 100644 index 00000000..5709fc07 --- /dev/null +++ b/Source/HBIOS/unlzsa2s.asm @@ -0,0 +1,166 @@ +; +; Size-optimized LZSA2 decompressor by spke & uniabis (139 bytes) +; +; ver.00 by spke for LZSA 1.0.0 (02-09/06/2019, 145 bytes); +; ver.01 by spke for LZSA 1.0.5 (24/07/2019, added support for backward decompression); +; ver.02 by uniabis (30/07/2019, 144(-1) bytes, +3.3% speed and support for Hitachi HD64180); +; ver.03 by spke for LZSA 1.0.7 (01/08/2019, 140(-4) bytes, -1.4% speed and small re-organization of macros); +; ver.04 by spke for LZSA 1.1.0 (26/09/2019, removed usage of IY, added full revision history) +; ver.05 by spke for LZSA 1.1.1 (11/10/2019, 139(-1) bytes, +0.1% speed) +; ver.051 by PSummers (14/1/2020), ROMWBW version. +; +; The data must be compressed using the command line compressor by Emmanuel Marty +; The compression is done as follows: +; +; lzsa.exe -f2 -r +; +; where option -r asks for the generation of raw (frame-less) data. +; +; The decompression is done in the standard way: +; +; ld hl,FirstByteOfCompressedData +; ld de,FirstByteOfMemoryForDecompressedData +; call DecompressLZSA2 +; +; Backward compression is also supported; you can compress files backward using: +; +; lzsa.exe -f2 -r -b +; +; and decompress the resulting files using: +; +; ld hl,LastByteOfCompressedData +; ld de,LastByteOfMemoryForDecompressedData +; call DecompressLZSA2 +; +; (do not forget to uncomment the BACKWARD_DECOMPRESS option in the decompressor). +; +; Of course, LZSA2 compression algorithms are (c) 2019 Emmanuel Marty, +; see https://github.com/emmanuel-marty/lzsa for more information +; +; Drop me an email if you have any comments/ideas/suggestions: zxintrospec@gmail.com +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. +; + +; DEFINE BACKWARD_DECOMPRESS ; uncomment for data compressed with option -b +; DEFINE HD64180 ; uncomment for systems using Hitachi HD64180 + + #IFNDEF BACKWARD_DECOMPRESS + + #DEFINE NEXT_HL \ + #DEFCONT \ inc hl + + #DEFINE ADD_OFFSET \ + #DEFCONT \ ex de,hl \ add hl,de + + #DEFINE BLOCKCOPY \ + #DEFCONT \ ldir + + #ELSE + + #DEFINE NEXT_HL \ + #DEFCONT \ dec hl + + #DEFINE ADD_OFFSET \ + #DEFCONT \ push hl \ or a \ sbc hl,de \ pop de + + #DEFINE BLOCKCOPY \ + #DEFCONT \ lddr + + #ENDIF + + #IFDEF HD64180 + + .ECHO "HD64180 " + + #DEFINE LD_IX_DE \ + #DEFCONT \ ld ixl,e \ ld ixh,d + + #DEFINE LD_DE_IX \ + #DEFCONT \ ld e,ixl \ ld d,ixh + + #ELSE + + .ECHO "Z80 " + + #DEFINE LD_IX_DE \ + #DEFCONT \ push de \ pop ix + + #DEFINE LD_DE_IX \ + #DEFCONT \ push ix \ pop de + + #ENDIF + +DLZSA2: + xor a \ ld b,a \ ex af,af' \ jr ReadToken + +CASE00x: call ReadNibble + ld e,a \ ld a,c + cp %00100000 \ rl e \ jr SaveOffset + +CASE0xx ld d,$FF \ cp %01000000 \ jr c,CASE00x + +CASE01x: cp %01100000 \ rl d + +OffsetReadE: ld e,(hl) \ NEXT_HL + +SaveOffset: LD_IX_DE + +MatchLen: and %00000111 \ add a,2 \ cp 9 \ call z,ExtendedCode + +CopyMatch: ld c,a + ex (sp),hl ; BC = len, DE = -offset, HL = dest, SP -> [src] + ADD_OFFSET ; BC = len, DE = dest, HL = dest+(-offset), SP -> [src] + BLOCKCOPY ; BC = 0, DE = dest + pop hl ; HL = src + +ReadToken: ld a,(hl) \ NEXT_HL \ push af + and %00011000 \ jr z,NoLiterals + + rrca \ rrca \ rrca + call pe,ExtendedCode + + ld c,a + BLOCKCOPY + +NoLiterals: pop af \ push de + or a \ jp p,CASE0xx + +CASE1xx: cp %11000000 \ jr nc,CASE11x + +CASE10x: call ReadNibble + ld d,a \ ld a,c + cp %10100000 ;: rl d + dec d \ rl d \ .DB $CA ; jr OffsetReadE ; #CA is JP Z,.. to skip all commands in CASE110 before jr OffsetReadE + +CASE110: ld d,(hl) \ NEXT_HL \ jr OffsetReadE + +CASE11x: cp %11100000 \ jr c,CASE110 + +CASE111: LD_DE_IX \ jr MatchLen + +ExtendedCode: call ReadNibble \ inc a \ jr z,ExtraByte + sub $F0+1 \ add a,c \ ret +ExtraByte ld a,15 \ add a,c \ add a,(hl) \ NEXT_HL \ ret nc + ld a,(hl) \ NEXT_HL + ld b,(hl) \ NEXT_HL \ ret nz + pop de \ pop de ; RET is not needed, because RET from ReadNibble is sufficient + +ReadNibble: ld c,a \ xor a \ ex af,af' \ ret m +UpdateNibble ld a,(hl) \ or $F0 \ ex af,af' + ld a,(hl) \ NEXT_HL \ or $0F + rrca \ rrca \ rrca \ rrca \ ret diff --git a/Source/HBIOS/usrrom.asm b/Source/HBIOS/usrrom.asm new file mode 100644 index 00000000..9a03a38d --- /dev/null +++ b/Source/HBIOS/usrrom.asm @@ -0,0 +1,81 @@ +; +; USRROM TEMPLATE FOR A CUSTOM USER ROM +; +#INCLUDE "std.asm" +; +CR .EQU 0DH +LF .EQU 0AH +; + .ORG USR_LOC +; + LD HL,BOOTMSG ; INTRODUCTION + CALL PRTSTR + + CALL CIN ; DO STUFF +; + LD A,00H ; RETURN TO ROM LOADER + LD HL,0000H + JP 0FFF9H +; RET +; +; +; +; PRINT A STRING AT ADDRESS SPECIFIED IN HL +; STRING MUST BE TERMINATED BY '$' +; USAGE: +; LD HL,MYSTR +; CALL PRTSTR +; ... +; MYSTR: .DB "HELLO$" +; +PRTSTR: LD A,(HL) + INC HL + CP '$' + RET Z + CALL COUT + JR PRTSTR +; +; OUTPUT CHARACTER IN A TO CONSOLE DEVICE +; +COUT: PUSH AF + PUSH BC + PUSH DE + LD B,01H + LD C,0 + LD E,A + RST 08 + POP DE + POP BC + POP AF + RET +; +; OUTPUT CHARACTER IN A TO CONSOLE DEVICE +; +COUTE: PUSH AF + LD A,E + CALL COUT + POP AF + RET +; +; WAIT FOR A CHARACTER FROM THE CONSOLE DEVICE AND RETURN IT IN A +; +CIN: PUSH BC + LD B,00H + LD C,00H + RST 08 + LD A,E + POP BC + RET +; +BOOTMSG:.DB CR,LF,CR,LF + .DB "No User ROM Installed." + .DB CR,LF,CR,LF + .DB "Press a key to return to Boot Loader.$" +; +SLACK .EQU (USR_END - $) + .FILL SLACK,00 + .ECHO "User ROM space remaining: " + .ECHO SLACK + .ECHO " bytes.\n" + .END + diff --git a/Source/HBIOS/util.asm b/Source/HBIOS/util.asm index 0b3448d1..3d9ee4d5 100644 --- a/Source/HBIOS/util.asm +++ b/Source/HBIOS/util.asm @@ -258,7 +258,7 @@ DB_BLKRD: LD C,16 ; SET FOR 16 LOCS PUSH HL ; SAVE STARTING HL DB_NXTONE: - LD A,(HL) ; GET BYTE + LD A,(HL) ; GET BYTE CALL PRTHEXBYTE ; PRINT IT CALL PC_SPACE ; DB_UPDH: @@ -295,7 +295,56 @@ DB_CONTD: JP DB_BLKRD ; DB_END: - RET ; + RET +; +; PRINT THE nTH STRING IN A LIST OF STRINGS WHERE EACH IS TERMINATED BY $ +; C REGISTER CONTAINS THE INDEX TO THE STRING TO BE DISPLAYED. +; A REGISTER CONTAINS A MASK TO BE APPLIED TO THE INDEX +; THE INDEX IS NORMALIZED TO A RANGE 0..N USING THE MASK AND THEN THE nTH +; STRING IS PRINTED IN A LIST DEFINED BY DE +; +; C = ATTRIBUTE +; A = MASK +; DE = STRING LIST +; +PRTIDXMSK: + PUSH BC +PRTIDXMSK0: + BIT 0,A + JR NZ,PRTIDXMSK1 + SRL A + SRL C + JR PRTIDXMSK0 +PRTIDXMSK1: + LD B,A + LD A,C + AND B + POP BC +; +; PRINT THE nTH STRING IN A LIST OF STRINGS WHERE EACH IS TERMINATED BY $ +; A REGISTER DEFINES THE nTH STRING IN THE LIST TO PRINT AND DE POINTS +; TO THE START OF THE STRING LIST. +; +; SLOW BUT IMPROVES CODE SIZE, READABILITY AND ELIMINATES THE NEED HAVE +; LIST OF POINTERS OR A LIST OF CONDITIONAL CHECKS. +; +PRTIDXDEA: + PUSH BC + LD C,A ; INDEX COUNT + OR A +PRTIDXDEA1: + JR Z,PRTIDXDEA3 +PRTIDXDEA2: + LD A,(DE) ; LOOP UNIT + INC DE ; WE REACH + CP '$' ; END OF STRING + JR NZ,PRTIDXDEA2 + DEC C ; AT STRING END. SO GO + JR PRTIDXDEA1 ; CHECK FOR INDEX MATCH +PRTIDXDEA3: + POP BC +; CALL WRITESTR ; FALL THROUGH TO WRITESTR +; RET ; ; OUTPUT A '$' TERMINATED STRING AT DE ; @@ -328,17 +377,17 @@ PANIC: LD DE,STR_PANIC CALL WRITESTR POP DE - CALL _REGDMP ; DUMP REGISTERS + CALL XREGDMP ; DUMP REGISTERS CALL CONTINUE ; CHECK W/ USER RET ; ; ; REGDMP: - CALL _REGDMP + CALL XREGDMP RET ; -_REGDMP: +XREGDMP: EX (SP),HL ; RET ADR TO HL, SAVE HL ON TOS LD (REGDMP_RET),HL ; SAVE RETURN ADDRESS POP HL ; RESTORE HL AND BURN STACK ENTRY @@ -517,8 +566,6 @@ BYTE2BCD1: POP BC RET -#IF (PLATFORM != PLT_UNA) - #IFDEF USEDELAY ; @@ -543,9 +590,11 @@ DELAY: ; 17TS (FROM INVOKING CALL) | DELAY1: ; | ; --- LOOP = ((CPUSCL * 16) - 5) TS ------------+ | DEC A ; 4TS | | -#IFDEF CPU_Z180 ; | | + #IF (BIOS == BIOS_WBW) ; | | + #IF (CPUFAM == CPU_Z180) ; | | OR A ; +4TS FOR Z180 | | -#ENDIF ; | | + #ENDIF ; | | + #ENDIF ; | | JR NZ,DELAY1 ; 12TS (NZ) / 7TS (Z) | | ; ----------------------------------------------+ | ; | @@ -574,17 +623,21 @@ VDELAY: ; 17TS (FROM INVOKING CALL) | ; | | VDELAY1: ; | | ; --- INNER LOOP = ((CPUSCL * 16) - 5) TS ------+ | | -#IFDEF CPU_Z180 ; | | | + #IF (BIOS == BIOS_WBW) ; | | | + #IF (CPUFAM == CPU_Z180) ; | | | OR A ; +4TS FOR Z180 | | | -#ENDIF ; | | | + #ENDIF ; | | | + #ENDIF ; | | | DEC A ; 4TS | | | JR NZ,VDELAY1 ; 12TS (NZ) / 7TS (Z) | | | ; ----------------------------------------------+ | | ; | | DEC DE ; 6TS | | -#IFDEF CPU_Z180 ; | | + #IF (BIOS == BIOS_WBW) ; | | | + #IF (CPUFAM == CPU_Z180) ; | | OR A ; +4TS FOR Z180 | | -#ENDIF ; | | + #ENDIF ; | | + #ENDIF ; | | LD A,D ; 4TS | | OR E ; 4TS | | JP NZ,VDELAY ; 10TS | | @@ -610,7 +663,7 @@ LDELAY: ; CPU SCALER := MAX(1, (PHIMHZ - 2)) ; DELAY_INIT: -#IF (PLATFORM == PLT_UNA) + #IF (BIOS == BIOS_UNA) LD C,$F8 ; UNA BIOS GET PHI FUNCTION RST 08 ; RETURNS SPEED IN HZ IN DE:HL LD B,4 ; DIVIDE MHZ IN DE:HL BY 100000H @@ -620,12 +673,12 @@ DELAY_INIT0: DJNZ DELAY_INIT0 ; ...RIGHT SHIFT DE BY 4. INC E ; FIX UP FOR VALUE TRUNCATION LD A,E ; PUT IN A -#ELSE + #ELSE LD B,BF_SYSGET ; HBIOS FUNC=GET SYS INFO LD C,BF_SYSGET_CPUINFO ; HBIOS SUBFUNC=GET CPU INFO RST 08 ; CALL HBIOS, RST 08 NOT YET INSTALLED LD A,L ; PUT SPEED IN MHZ IN ACCUM -#ENDIF + #ENDIF CP 3 ; TEST FOR <= 2 (SPECIAL HANDLING) JR C,DELAY_INIT1 ; IF <= 2, SPECIAL PROCESSING SUB 2 ; ADJUST AS REQUIRED BY DELAY FUNCTIONS @@ -636,13 +689,11 @@ DELAY_INIT2: LD (CPUSCL),A ; UPDATE CPU SCALER VALUE RET -#IF (CPUMHZ < 3) + #IF (CPUMHZ < 3) CPUSCL .DB 1 ; CPU SCALER MUST BE > 0 -#ELSE + #ELSE CPUSCL .DB CPUMHZ - 2 ; OTHERWISE 2 LESS THAN PHI MHZ -#ENDIF -; -#ENDIF + #ENDIF ; #ENDIF ; @@ -651,13 +702,15 @@ CPUSCL .DB CPUMHZ - 2 ; OTHERWISE 2 LESS THAN PHI MHZ ; NUMBER OF CALL/RET INVOCATIONS. A SINGLE CALL/RET IS ; 27 T-STATES ON A Z80, 25 T-STATES ON A Z180 ; -DLY64: CALL DLY32 -DLY32: CALL DLY16 -DLY16: CALL DLY8 -DLY8: CALL DLY4 -DLY4: CALL DLY2 -DLY2: CALL DLY1 -DLY1: RET +; ; Z80 Z180 +; ; ---- ---- +DLY64: CALL DLY32 ; 1728 1600 +DLY32: CALL DLY16 ; 864 800 +DLY16: CALL DLY8 ; 432 400 +DLY8: CALL DLY4 ; 216 200 +DLY4: CALL DLY2 ; 108 100 +DLY2: CALL DLY1 ; 54 50 +DLY1: RET ; 27 25 ; ; MULTIPLY 8-BIT VALUES ; IN: MULTIPLY H BY E @@ -740,7 +793,7 @@ DIV32X8: XOR A LD B,32 DIV32X8A: - ADD HL,HL + ADD HL,HL RL E RL D RLA @@ -749,7 +802,7 @@ DIV32X8A: SUB C INC L DIV32X8B: - DJNZ DIV32X8A + DJNZ DIV32X8A RET ; ; FILL MEMORY AT HL WITH VALUE A, LENGTH IN BC, ALL REGS USED @@ -935,19 +988,15 @@ SUB32: ; INCREMENT 32 BIT BINARY AT ADDRESS ; INC32HL: - PUSH HL - PUSH BC - LD B,4 -INC32HL1: INC (HL) + RET NZ INC HL - JR NZ,INC32HL2 - DJNZ INC32HL1 -INC32HL2: - POP BC - POP HL + INC (HL) + RET NZ + INC HL + INC (HL) + RET NZ + INC HL + INC (HL) RET - - - diff --git a/Source/HBIOS/vdu.asm b/Source/HBIOS/vdu.asm index 0d140608..bc888aa5 100644 --- a/Source/HBIOS/vdu.asm +++ b/Source/HBIOS/vdu.asm @@ -5,16 +5,16 @@ ; REVISED/ENHANCED BY DAN WERNER -- 11/7/2009 ; ROMWBW ADAPTATION BY: WAYNE WARTHEN -- 11/9/2012 ; 80X25, 80X30 AND MODE INFO AT BOOT ADDED BY: PHIL SUMMERS -- 2/3/2019 +; ADD CURSOR STYLE OPTIONS, IMPLEMENT VDU_VDASCS FUNCTION : PHIL SUMMERS -- 19/10/2019 ;====================================================================== ; ; VDU-DW.ZIP IS THE DEFAULY 10X8 FONT THAT SUITS 80X25 AND 80X26 MODE. ; IN 80X30 MODE THE DESCENDERS ARE MISSING. AN ALTERNATE 8x8 FONT MAY ; DISPLAY BETTER. THIS CAN BE ADDED TO THE ECB-VDU FONT EPROM AND -; SELECTED VIA ONBOARD JUMPERS.THE FONT ROM CAN CONTAIN EIGHT 2Kb FONTS. +; SELECTED VIA ONBOARD JUMPERS. THE FONT ROM CAN CONTAIN EIGHT 2Kb FONTS. ; ; TODO: ; - ADD REMAINING REGISTERS TO INIT -; - IMPLEMENT SET CURSOR STYLE (VDASCS) FUNCTION ; - IMPLEMENT ALTERNATE DISPLAY MODES? ; ;====================================================================== @@ -33,8 +33,19 @@ VDU_PPIB .EQU VDU_BASE + $05 ; PPI PORT B VDU_PPIC .EQU VDU_BASE + $06 ; PPI PORT C VDU_PPIX .EQU VDU_BASE + $07 ; PPI CONTROL PORT ; +VDU_NOBL .EQU 00000000B ; NO BLINK +VDU_NOCU .EQU 00100000B ; NO CURSOR +VDU_BFAS .EQU 01000000B ; BLINK AT X16 RATE +VDU_BSLO .EQU 01100000B ; BLINK AT X32 RATE +; +VDU_BLOK .EQU 0 ; BLOCK CURSOR +VDU_ULIN .EQU 1 ; UNDERLINE CURSOR +; +VDU_CSTY .EQU VDU_BLOK ; DEFAULT CURSOR STYLE +VDU_BLNK .EQU VDU_NOBL ; DEFAULT BLINK RATE +; TERMENABLE .SET TRUE ; INCLUDE TERMINAL PSEUDODEVICE DRIVER - +; #IF (VDUSIZ=V80X24) DLINES .EQU 24 DROWS .EQU 80 @@ -61,6 +72,15 @@ DROWS .EQU 80 DSCANL .EQU 12 #ENDIF ; +#IF VDU_CSTY=VDU_BLOK +VDU_R10 .EQU (VDU_BLNK + $00) +VDU_R11 .EQU DSCANL-1 +#ENDIF +; +#IF VDU_CSTY=VDU_ULIN +VDU_R10 .EQU (VDU_BLNK + DSCANL-1) +VDU_R11 .EQU DSCANL-1 +#ENDIF ; ;====================================================================== ; VDU DRIVER - INITIALIZATION @@ -74,7 +94,7 @@ VDU_INIT: LD A,VDU_RAMRD CALL PRTHEXBYTE - PRTS(" MODE= $") ; OUTPUT DISPLAY FORMAT + PRTS(" MODE=$") ; OUTPUT DISPLAY FORMAT LD A,DROWS CALL PRTDECB PRTS("X$") @@ -164,7 +184,24 @@ VDU_VDADEV: RET VDU_VDASCS: - CALL PANIC ; NOT IMPLEMENTED (YET) + LD A,D ; GET CURSOR FINISH. + AND 00001111B ; BOTTOM NIBBLE OF D + LD L,A ; SAVE IN L FOR R11 + + LD A,D ; GET CURSOR START. + AND 11110000B ; TOP NIBBLE IF D + RRCA + RRCA + RRCA ; COMBINE CURSOR START + RRCA ; AND CURSOR STYLE AND + OR VDU_CSTY ; SAVE IN H FOR R10 + LD H,A + + LD C,10 + CALL VDU_WRREGX ; UPDATE CURSOR + + XOR A + RET VDU_VDASCP: CALL VDU_XY @@ -235,7 +272,7 @@ VDU_WAITRDY: ;---------------------------------------------------------------------- ; UPDATE SY6845 REGISTERS ; VDU_WRREG WRITES VALUE IN A TO VDU REGISTER SPECIFIED IN C -; VDU_WRREGX WRITES VALUE IN DE TO VDU REGISTER PAIR IN C, C+1 +; VDU_WRREGX WRITES VALUE IN HL TO VDU REGISTER PAIR IN C, C+1 ;---------------------------------------------------------------------- ; VDU_WRREG: @@ -344,7 +381,7 @@ VDU_XY: JP VDU_WRREGX ; DO IT AND RETURN ; ;---------------------------------------------------------------------- -; WRITE VALUE IN A TO CURRENT VDU BUFFER POSTION, ADVANCE CURSOR +; WRITE VALUE IN A TO CURRENT VDU BUFFER POSITION, ADVANCE CURSOR ;---------------------------------------------------------------------- ; VDU_PUTCHAR: @@ -424,21 +461,13 @@ VDU_BLKCPY: LD A,31 ; PREP VDU FOR DATA R/W OUT (VDU_REG),A ; DO IT LD HL,VDU_BUF ; HL POINTS TO WORK BUFFER -; LD C,VDU_RAMWR ; LOAD C WITH VDU WRITE REGISTER + LD C,VDU_RAMRD ; LOAD C WITH VDU READ REGISTER VDU_BLKCPY1: ; VIDEO RAM -> BUFFER COPY LOOP CALL VDU_WAITRDY ; WAIT FOR VDU -;;;;;;;;;;;;;;;;; -; INI IS NOT WORKING FOR ME, GARBAGE DATA READS, NO IDEA WHY -; INI ; READ BYTE, DEC B, INC HL -; IN A,(VDU_DATA) ; BOGUS READ TO INCREMENT VDU RAM ADDRESS!!! -; JR NZ,VDU_BLKCPY1 ; LOOP TILL DONE -;;;;;;;;;;;;;;;;; - IN A,(VDU_RAMRD) ; READ DATA BYTE - LD (HL),A ; SAVE IN BUFFER - INC HL ; BUMP SOURCE ADDRESS + INI ; READ BYTE, DEC B, INC HL IN A,(VDU_DATA) ; BOGUS READ TO INCREMENT VDU RAM ADDRESS!!! - DJNZ VDU_BLKCPY1 ; LOOP TILL DONE + JR NZ,VDU_BLKCPY1 ; LOOP TILL DONE ; SETUP TO COPY FROM WORK BUFFER TO VDU DEST POP BC ; RECOVER THE COPY LENGTH @@ -530,13 +559,33 @@ VDU_POS .DW 0 ; CURRENT DISPLAY POSITION VDU_OFFSET .DW 0 ; CURRENT DISPLAY POSITION VDU_BUF .FILL 256,0 ; COPY BUFFER ; - +;================================================================================================== +; ECB-VDU VIDEO MODE DESCRIPTION +;==================================================================================================\ +; +; CCIR 625/50 VERSION (USED IN MOST OF THE WORLD) +; JUMPER K1 2-3, K2 1-2 FOR 2MHz CHAR CLOCK +; +; THE CCIR 625/50 TELEVISION STANDARD HAS 625 LINES INTERLACED AT 50 FIELDS PER SECOND. THIS WORKS +; OUT AS 50 FIELDS OF 312.5 LINES PER SECOND NON-INTERLACED AS USED HERE. +; HORIZONTAL LINE WIDTH IS 64uS. FOR A 2 MHz CHARACTER CLOCK (R0+1)/2000000 = 64uS +; NEAREST NUMBER OF LINES IS 312 = (R4+1) * (R9+1) + R5. +; 15625 / 312 = 50.08 FIELDS PER SECOND (NEAR ENOUGH-DGG) +; +; +; .DB 078H ; R8 MODE B7=0 TRANSPARENT UPDATE DURING BLANKING +; ; B6=1 PIN 34 IS UPDATE STROBE +; ; B5=1 DELAY CURSOR 1 CHARACTER +; ; B4=1 DELAY DISPLAY ENABLE 1 CHARACTER +; ; B3=1 TRANSPARENT MEMORY ADDRESSING +; ; B2=0 RAM STRAIGHT BINARY ADDRESSING +; ; B1,B0=0 NON-INTERLACE +VDU_INIT6845: #IF (VDUSIZ=V80X24) ;================================================================================================== ; VDU DRIVER - SY6845 REGISTER INITIALIZATION -80x24 10x8 ;================================================================================================== ; -VDU_INIT6845: .DB 07FH ; R0 TOTAL NUMBER OF HORIZONTAL CHARACTERS (DETERMINES HSYNC) .DB DROWS ; R1 NUMBER OF HORIZONTAL CHARACTERS DISPLAYED (80 COLUMNS) .DB 060H ; R2 HORIZONTAL SYNC POSITION @@ -545,27 +594,20 @@ VDU_INIT6845: .DB 002H ; R5 VERTICAL TOTAL ADJUST ( .DB DLINES ; R6 VERTICAL DISPLAYED (24 ROWS) .DB 01AH ; R7 VERTICAL SYNC - .DB 078H ; R8 MODE B7=0 TRANSPARENT UPDATE DURING BLANKING - ; B6=1 PIN 34 IS UPDATE STROBE - ; B5=1 DELAY CURSOR 1 CHARACTER - ; B4=1 DELAY DISPLAY ENABLE 1 CHARACTER - ; B3=1 TRANSPARENT MEMORY ADDRESSING - ; B2=0 RAM STRAIGHT BINARY ADDRESSING - ; B1,B0=0 NON-INTERLACE + .DB 078H ; R8 MODE .DB DSCANL-1 ; R9 SCAN LINE (LINES PER CHAR AND SPACING -1) - .DB 060H ; R10 CURSOR START RASTER - .DB DSCANL-1 ; R11 CURSOR END RASTER + .DB VDU_R10 ; R10 CURSOR START RASTER + .DB VDU_R11 ; R11 CURSOR END RASTER .DB 00H ; R12 START ADDRESS HI .DB 00H ; R13 START ADDRESS LO .DB 00H ; R14 CURSOR ADDRESS HI .DB 00H ; R15 CURSOR ADDRESS LO -; #ENDIF #IF (VDUSIZ=V80X25) ;================================================================================================== ; VDU DRIVER - SY6845 REGISTER INITIALIZATION -80x25 10x8 ;================================================================================================== -VDU_INIT6845: +; .DB 07FH ; R0 TOTAL NUMBER OF HORIZONTAL CHARACTERS (DETERMINES HSYNC) .DB DROWS ; R1 NUMBER OF HORIZONTAL CHARACTERS DISPLAYED =80 .DB 060H ; R2 HORIZONTAL SYNC POSITION @@ -574,27 +616,20 @@ VDU_INIT6845: .DB 002H ; R5 VERTICAL TOTAL ADJUST ( .DB DLINES ; R6 VERTICAL DISPLAYED (25 ROWS) .DB 01BH ; R7 VERTICAL SYNC - .DB 078H ; R8 MODE B7=0 TRANSPARENT UPDATE DURING BLANKING -; ; B6=1 PIN 34 IS UPDATE STROBE -; ; B5=1 DELAY CURSOR 1 CHARACTER -; ; B4=1 DELAY DISPLAY ENABLE 1 CHARACTER -; ; B3=1 TRANSPARENT MEMORY ADDRESSING -; ; B2=0 RAM STRAIGHT BINARY ADDRESSING -; ; B1,B0=0 NON-INTERLACE + .DB 078H ; R8 MODE .DB DSCANL-1 ; R9 SCAN LINE (LINES PER CHAR AND SPACING -1) - .DB 060H ; R10 CURSOR START RASTER - .DB DSCANL-1 ; R11 CURSOR END RASTER + .DB VDU_R10 ; R10 CURSOR START RASTER + .DB VDU_R11 ; R11 CURSOR END RASTER .DB 00H ; R12 START ADDRESS HI .DB 00H ; R13 START ADDRESS LO .DB 00H ; R14 CURSOR ADDRESS HI .DB 00H ; R15 CURSOR ADDRESS LO -; #ENDIF #IF (VDUSIZ=V80X30) ;================================================================================================== ; VDU DRIVER - SY6845 REGISTER INITIALIZATION -80x30 8x8 ;================================================================================================== -VDU_INIT6845: +; .DB 07FH ; R0 TOTAL NUMBER OF HORIZONTAL CHARACTERS (DETERMINES HSYNC) .DB DROWS ; R1 NUMBER OF HORIZONTAL CHARACTERS DISPLAYED =80 .DB 060H ; R2 HORIZONTAL SYNC POSITION @@ -603,27 +638,20 @@ VDU_INIT6845: .DB 00H ; R5 VERTICAL TOTAL ADJUST ( .DB DLINES ; R6 VERTICAL DISPLAYED (30 ROWS) .DB 22H ; R7 VERTICAL SYNC - .DB 078H ; R8 MODE B7=0 TRANSPARENT UPDATE DURING BLANKING -; ; B6=1 PIN 34 IS UPDATE STROBE -; ; B5=1 DELAY CURSOR 1 CHARACTER -; ; B4=1 DELAY DISPLAY ENABLE 1 CHARACTER -; ; B3=1 TRANSPARENT MEMORY ADDRESSING -; ; B2=0 RAM STRAIGHT BINARY ADDRESSING -; ; B1,B0=0 NON-INTERLACE + .DB 078H ; R8 MODE .DB DSCANL-1 ; R9 SCAN LINE (LINES PER CHAR AND SPACING -1) - .DB 060H ; R10 CURSOR START RASTER - .DB DSCANL-1 ; R11 CURSOR END RASTER + .DB VDU_R10 ; R10 CURSOR START RASTER + .DB VDU_R11 ; R11 CURSOR END RASTER .DB 00H ; R12 START ADDRESS HI .DB 00H ; R13 START ADDRESS LO .DB 00H ; R14 CURSOR ADDRESS HI .DB 00H ; R15 CURSOR ADDRESS LO -; #ENDIF #IF (VDUSIZ=V80X25B) ;================================================================================================== ; VDU DRIVER - SY6845 REGISTER INITIALIZATION -80x25 12x8 TO SUIT BLOCK GRAPHICS ;================================================================================================== -VDU_INIT6845: +; .DB 07FH ; R0 TOTAL NUMBER OF HORIZONTAL CHARACTERS (DETERMINES HSYNC) .DB DROWS ; R1 NUMBER OF HORIZONTAL CHARACTERS DISPLAYED =80 .DB 060H ; R2 HORIZONTAL SYNC POSITION @@ -632,27 +660,20 @@ VDU_INIT6845: .DB 00H ; R5 VERTICAL TOTAL ADJUST (312-(R4+1)*DSCANL) .DB DLINES ; R6 VERTICAL DISPLAY .DB 019H ; R7 VERTICAL SYNC (DLINES .. R4) - .DB 078H ; R8 MODE B7=0 TRANSPARENT UPDATE DURING BLANKING -; ; B6=1 PIN 34 IS UPDATE STROBE -; ; B5=1 DELAY CURSOR 1 CHARACTER -; ; B4=1 DELAY DISPLAY ENABLE 1 CHARACTER -; ; B3=1 TRANSPARENT MEMORY ADDRESSING -; ; B2=0 RAM STRAIGHT BINARY ADDRESSING -; ; B1,B0=0 NON-INTERLACE + .DB 078H ; R8 MODE .DB DSCANL-1 ; R9 SCAN LINE (LINES PER CHAR AND SPACING -1) - .DB 060H ; R10 CURSOR START RASTER - .DB DSCANL-1 ; R11 CURSOR END RASTER + .DB VDU_R10 ; R10 CURSOR START RASTER + .DB VDU_R11 ; R11 CURSOR END RASTER .DB 00H ; R12 START ADDRESS HI .DB 00H ; R13 START ADDRESS LO .DB 00H ; R14 CURSOR ADDRESS HI .DB 00H ; R15 CURSOR ADDRESS LO -; #ENDIF #IF (VDUSIZ=V80X24B) ;================================================================================================== ; VDU DRIVER - SY6845 REGISTER INITIALIZATION -80x24 12x8 TO SUIT BLOCK GRAPHICS ;================================================================================================== -VDU_INIT6845: +; .DB 07FH ; R0 TOTAL NUMBER OF HORIZONTAL CHARACTERS (DETERMINES HSYNC) .DB DROWS ; R1 NUMBER OF HORIZONTAL CHARACTERS DISPLAYED =80 .DB 060H ; R2 HORIZONTAL SYNC POSITION @@ -661,33 +682,18 @@ VDU_INIT6845: .DB 00H ; R5 VERTICAL TOTAL ADJUST (312-(R4+1)*DSCANL) .DB DLINES ; R6 VERTICAL DISPLAY .DB 018H ; R7 VERTICAL SYNC (DLINES .. R4) - .DB 078H ; R8 MODE B7=0 TRANSPARENT UPDATE DURING BLANKING -; ; B6=1 PIN 34 IS UPDATE STROBE -; ; B5=1 DELAY CURSOR 1 CHARACTER -; ; B4=1 DELAY DISPLAY ENABLE 1 CHARACTER -; ; B3=1 TRANSPARENT MEMORY ADDRESSING -; ; B2=0 RAM STRAIGHT BINARY ADDRESSING -; ; B1,B0=0 NON-INTERLACE + .DB 078H ; R8 MODE .DB DSCANL-1 ; R9 SCAN LINE (LINES PER CHAR AND SPACING -1) - .DB 060H ; R10 CURSOR START RASTER - .DB DSCANL-1 ; R11 CURSOR END RASTER + .DB VDU_R10 ; R10 CURSOR START RASTER + .DB VDU_R11 ; R11 CURSOR END RASTER .DB 00H ; R12 START ADDRESS HI .DB 00H ; R13 START ADDRESS LO .DB 00H ; R14 CURSOR ADDRESS HI .DB 00H ; R15 CURSOR ADDRESS LO ; #ENDIF -; CCIR 625/50 VERSION (USED IN MOST OF THE WORLD) -; JUMPER K1 2-3, K2 1-2 FOR 2MHz CHAR CLOCK -; -; THE CCIR 625/50 TELEVISION STANDARD HAS 625 LINES INTERLACED AT 50 FIELDS PER SECOND. THIS WORKS -; OUT AS 50 FIELDS OF 312.5 LINES PER SECOND NON-INTERLACED AS USED HERE. -; HORIZONTAL LINE WIDTH IS 64uS. FOR A 2 MHz CHARACTER CLOCK (R0+1)/2000000 = 64uS -; NEAREST NUMBER OF LINES IS 312 = (R4+1) * (R9+1) + R5. -; 15625 / 312 = 50.08 FIELDS PER SECOND (NEAR ENOUGH-DGG) -; ; -;================================================================================================== +;;================================================================================================== ; VDU DRIVER - INSTANCE DATA ;================================================================================================== ; diff --git a/Source/HBIOS/ver.inc b/Source/HBIOS/ver.inc deleted file mode 100644 index 00c91863..00000000 --- a/Source/HBIOS/ver.inc +++ /dev/null @@ -1,5 +0,0 @@ -#DEFINE RMJ 2 -#DEFINE RMN 9 -#DEFINE RUP 1 -#DEFINE RTP 0 -#DEFINE BIOSVER "2.9.1-pre.11" diff --git a/Source/HBIOS/vga.asm b/Source/HBIOS/vga.asm index 420afccc..5a285376 100644 --- a/Source/HBIOS/vga.asm +++ b/Source/HBIOS/vga.asm @@ -21,8 +21,63 @@ VGA_HI .EQU VGA_BASE + $05 ; BOARD RAM HI ADDRESS VGA_LO .EQU VGA_BASE + $06 ; BOARD RAM LO ADDRESS VGA_DAT .EQU VGA_BASE + $07 ; BOARD RAM BYTE R/W ; +VGA_NOBL .EQU 00000000B ; NO BLINK +VGA_NOCU .EQU 00100000B ; NO CURSOR +VGA_BFAS .EQU 01000000B ; BLINK AT X16 RATE +VGA_BSLO .EQU 01100000B ; BLINK AT X32 RATE +; +VGA_BLOK .EQU 0 ; BLOCK CURSOR +VGA_ULIN .EQU 1 ; UNDERLINE CURSOR +; +VGA_CSTY .EQU VGA_BLOK ; DEFAULT CURSOR STYLE +VGA_BLNK .EQU VGA_NOBL ; DEFAULT BLINK RATE +VGA_9BIT .EQU $0101 ; 9 BIT MSK-CFG +VGA_8BIT .EQU $0000 ; 8 BIT MSK-CFG + +VGA_NICE .EQU FALSE ; TRUE = SLOW BUT PRETTY +; +#IF (VGASIZ=V80X25) VGA_ROWS .EQU 25 VGA_COLS .EQU 80 +VGA_SCANL .EQU 16 +VGA_89BIT .EQU VGA_8BIT +#DEFINE USEFONT8X16 +#DEFINE VGA_FONT FONT8X16 +#ENDIF +#IF (VGASIZ=V80X30) +VGA_ROWS .EQU 30 +VGA_COLS .EQU 80 +VGA_SCANL .EQU 16 +VGA_89BIT .EQU VGA_8BIT +#DEFINE USEFONT8X16 +#DEFINE VGA_FONT FONT8X16 +#ENDIF +#IF (VGASIZ=V80X43) +VGA_ROWS .EQU 43 +VGA_COLS .EQU 80 +VGA_SCANL .EQU 11 +VGA_89BIT .EQU VGA_8BIT +#DEFINE USEFONT8X11 +#DEFINE VGA_FONT FONT8X11 +#ENDIF +#IF (VGASIZ=V80X60) +VGA_ROWS .EQU 60 +VGA_COLS .EQU 80 +VGA_SCANL .EQU 8 +VGA_89BIT .EQU VGA_8BIT +#DEFINE USEFONT8X8 +#DEFINE VGA_FONT FONT8X8 +#ENDIF +; +#IF VGA_CSTY=VGA_BLOK +VGA_R10 .EQU (VGA_BLNK + $00) +VGA_R11 .EQU VGA_SCANL-1 +#ENDIF +; +#IF VGA_CSTY=VGA_ULIN +VGA_R10 .EQU (VGA_BLNK + VGA_SCANL-1) +VGA_R11 .EQU VGA_SCANL-1 +#ENDIF ; #DEFINE DEFREGS REGS_VGA ; @@ -68,7 +123,7 @@ VGA_INIT1: ; HARDWARE INITIALIZATION CALL VGA_CRTINIT ; SETUP THE VGA CHIP REGISTERS - CALL VGA_LOADFONT ; LOAD FONT DATA FROM ROM TO VGA STRORAGE + CALL VGA_LOADFONT ; LOAD FONT DATA FROM ROM TO VGA STORAGE CALL VGA_VDARES CALL KBD_INIT ; INITIALIZE KEYBOARD DRIVER @@ -128,18 +183,18 @@ VGA_VDAQRY: VGA_VDARES: LD A,$07 ; ATTRIBUTE IS STANDARD WHITE ON BLACK LD (VGA_ATTR),A ; SAVE IT - XOR A ; ZERO (REVEVERSE, UNDERLINE, BLINK) + XOR A ; ZERO (REVERSE, UNDERLINE, BLINK) LD (VGA_RUB),A ; SAVE IT LD DE,0 ; ROW = 0, COL = 0 CALL VGA_XY ; SEND CURSOR TO TOP LEFT LD A,' ' ; BLANK THE SCREEN - LD DE,$800 ; FILL ENTIRE BUFFER + LD DE,VGA_ROWS*VGA_COLS ; FILL ENTIRE BUFFER CALL VGA_FILL ; DO IT LD DE,0 ; ROW = 0, COL = 0 CALL VGA_XY ; SEND CURSOR TO TOP LEFT - LD HL,$0404 ; SET VIDEO ENABLE BIT + LD HL,$0404 | VGA_89BIT; SET VIDEO ENABLE BIT CALL VGA_SETCFG ; DO IT XOR A @@ -291,7 +346,7 @@ VGA_REGWR: VGA_REGWRX: LD A,H ; SETUP MSB TO WRITE CALL VGA_REGWR ; DO IT - INC C ; NEXT CVDU REGISTER + INC C ; NEXT VDU REGISTER LD A,L ; SETUP LSB TO WRITE JR VGA_REGWR ; DO IT & RETURN ; @@ -416,7 +471,7 @@ VGA_PROBE: ;---------------------------------------------------------------------- ; VGA_CRTINIT: - LD HL,$FF00 ; ZERO ALL CFG BITS + LD HL,$FF00 | VGA_89BIT ; INITIAL CFG BITS CALL VGA_SETCFG ; DO IT CALL VGA_RES ; RESET CRTC (ALL REGS TO ZERO) @@ -457,24 +512,57 @@ VGA_CRTCDUMP1: ;---------------------------------------------------------------------- ; LOAD FONT DATA ;---------------------------------------------------------------------- + ; VGA_LOADFONT: - LD HL,$7000 ; CLEAR FONT PAGE NUM + LD HL,$7000 | VGA_89BIT ; CLEAR FONT PAGE NUM CALL VGA_SETCFG +#IF USELZSA2 + LD (VGA_STACK),SP ; SAVE STACK + LD HL,(VGA_STACK) ; AND SHIFT IT + LD DE,$2000 ; DOWN 4KB TO + CCF ; CREATE A + SBC HL,DE ; DECOMPRESSION BUFFER + LD SP,HL ; HL POINTS TO BUFFER + EX DE,HL ; START OF STACK BUFFER + PUSH DE ; SAVE IT + LD HL,VGA_FONT ; START OF FONT DATA + CALL DLZSA2 ; DECOMPRESS TO DE + POP HL ; RECALL STACK BUFFER POSITION +#ELSE + LD HL,VGA_FONT ; START OF FONT DATA +#ENDIF + LD DE,$7000 ; PAGE 7 OF VIDEO RAM - LD HL,FONT_HI ; START OF FONT DATA VGA_LOADFONT1: + LD B,VGA_SCANL ; # BYTES FOR EACH CHAR +VGA_LOADFONT2: LD A,(HL) ; GET NEXT BYTE CALL VGA_MEMWR ; MEM(DE) := A INC HL ; NEXT FONT BYTE INC DE ; NEXT MEM BYTE + DJNZ VGA_LOADFONT2 + + LD BC,16-VGA_SCANL ; MOVE TO NEXT + EX DE,HL ; 16 BYTE + ADD HL,BC ; CHARACTER + EX DE,HL + LD A,D CP $80 ; CHECK FOR END JR NZ,VGA_LOADFONT1 ; LOOP - LD HL,$7070 ; SET FONT PAGE NUM TO 7 + LD HL,$7070 | VGA_89BIT ; SET FONT PAGE NUM TO 7 CALL VGA_SETCFG + +#IF USELZSA2 + LD HL,(VGA_STACK) ; ERASE DECOMPRESS BUFFER + LD SP,HL ; BY RESTORING THE STACK RET ; DONE +VGA_STACK .DW 0 +#ELSE + RET +#ENDIF ; ;---------------------------------------------------------------------- ; SET CURSOR POSITION TO ROW IN D AND COLUMN IN E @@ -514,7 +602,7 @@ VGA_XY2IDX1: RET ; RETURN ; ;---------------------------------------------------------------------- -; WRITE VALUE IN A TO CURRENT VDU BUFFER POSTION, ADVANCE CURSOR +; WRITE VALUE IN A TO CURRENT VDU BUFFER POSITION, ADVANCE CURSOR ;---------------------------------------------------------------------- ; VGA_PUTCHAR: @@ -527,7 +615,9 @@ VGA_PUTCHAR: LD A,(VGA_ATTR) ; ATTRIBUTE LD L,A ; ... TO L ; WRITE CHAR & ATTR - ;CALL VGA_WAITSB ; WAIT FOR RETRACE +#IF (VGA_NICE) + CALL VGA_WAITSB ; WAIT FOR RETRACE +#ENDIF CALL VGA_MEMWRX ; UPDATE CURRENT POSITION LD HL,(VGA_POS) ; GET CURSOR POSITION @@ -574,7 +664,9 @@ VGA_FILL1: DEC C ; C := VGA ADDR LO OUT (C),L ; UDPATE LO ADDR INC C ; POINT TO DATA REG - ;CALL VGA_WAITSB ; WAIT FOR RETRACE +#IF (VGA_NICE) + CALL VGA_WAITSB ; WAIT FOR RETRACE +#ENDIF LD A,(VGA_ATTR) ; GET CUR ATTR OUT (C),A ; OUTPUT ATTR @@ -738,7 +830,9 @@ VGA_BLKCPY: EX DE,HL ; SWAP BACK VGA_BLKCPY1: - ;CALL VGA_WAITSB ; WAIT FOR RETRACE +#IF (VGA_NICE) + CALL VGA_WAITSB ; WAIT FOR RETRACE +#ENDIF ; GET NEXT SOURCE BYTE LD C,VGA_HI ; C := VGA_HI @@ -829,22 +923,23 @@ VGA_RUB .DB 0 ; REVERSE/UNDERLINE/BLINK (-----RUB) ; BIT 1: FG GREEN ; BIT 0: FG BLUE ; +#IF (VGASIZ=V80X25) ;=============================================================================== -; DEFAULT REGISTER VALUES +; 80x25x8 70hz REGISTER VALUES ;=============================================================================== ; REGS_VGA: .DB 0,100 - 1 ; HORZ TOT - 1 - .DB 1,80 ; HORZ DISP - .DB 2,80 + 2 ; HORZ DISP + HORZ FP + .DB 1,VGA_COLS ; HORZ DISP + .DB 2,VGA_COLS + 2 ; HORZ DISP + HORZ FP .DB 3,(2 << 4) | (12 & $0F) ; VERT SW, HORZ SW .DB 4,28 - 1 ; VERT TOT - 1 .DB 5,1 ; VERT TOT ADJ - .DB 6,25 ; VERT DISP - .DB 7,25 + 0 ; VERT DISP + VERT FP ROWS - .DB 9,16 - 1 ; CHAR HEIGHT - 1 - .DB 10,(13 | $60) ; CURSOR START & CURSOR BLINK - .DB 11,14 ; CURSOR END + .DB 6,VGA_ROWS ; VERT DISP + .DB 7,VGA_ROWS + 0 ; VERT DISP + VERT FP ROWS + .DB 9,VGA_SCANL - 1 ; CHAR HEIGHT - 1 + .DB 10,VGA_R10 ; CURSOR START & CURSOR BLINK + .DB 11,VGA_R11 ; CURSOR END .DB 12,($0000 >> 8) & $FF ; SCRN 1 START (HI) .DB 13,($0000 & $FF) ; SCRN 1 START (LO) .DB 18,-1 ; S2 ROW - 1 @@ -852,7 +947,79 @@ REGS_VGA: .DB 30,$01 | $08 ; CTL 1, 2 WINDOWS & ENABLE R27 VSYNC FINE ADJ .DB $FF ; END MARKER +#ENDIF +#IF (VGASIZ=V80X30) +;=============================================================================== +; 80x30x8 60hz REGISTER VALUES +;=============================================================================== +; +REGS_VGA: + .DB 0,100 - 1 ; HORZ TOT - 1 + .DB 1,VGA_COLS ; HORZ DISP + .DB 2,VGA_COLS + 2 ; HORZ DISP + HORZ FP + .DB 3,44 ; VERT SW, HORZ SW + .DB 4,33 - 1 ; VERT TOT - 1 + .DB 5,13 ; VERT TOT ADJ + .DB 6,VGA_ROWS ; VERT DISP + .DB 7,VGA_ROWS + 0 ; VERT DISP + VERT FP ROWS + .DB 9,VGA_SCANL - 1 ; CHAR HEIGHT - 1 + .DB 10,VGA_R10 ; CURSOR START & CURSOR BLINK + .DB 11,VGA_R11 ; CURSOR END + .DB 12,0 ; SCRN 1 START (HI) + .DB 13,0 ; SCRN 1 START (LO) + .DB 18,-1 ; S2 ROW - 1 + .DB 27,0 ; VERT SYNC POS ADJ + .DB 30,$01 | $08 ; CTL 1, 2 WINDOWS & ENABLE R27 VSYNC FINE ADJ + .DB $FF ; END MARKER +#ENDIF +#IF (VGASIZ=V80X43) +;=============================================================================== +; 80x43x8 60hz REGISTER VALUES +;=============================================================================== ; +REGS_VGA: + .DB 0,100 - 1 ; HORZ TOT - 1 + .DB 1,VGA_COLS ; HORZ DISP + .DB 2,VGA_COLS + 2 ; HORZ DISP + HORZ FP + .DB 3,44 ; VERT SW, HORZ SW + .DB 4,47 - 1 ; VERT TOT - 1 + .DB 5,8 ; VERT TOT ADJ + .DB 6,VGA_ROWS ; VERT DISP + .DB 7,VGA_ROWS + 0 ; VERT DISP + VERT FP ROWS + .DB 9,VGA_SCANL - 1 ; CHAR HEIGHT - 1 + .DB 10,VGA_R10 ; CURSOR START & CURSOR BLINK + .DB 11,VGA_R11 ; CURSOR END + .DB 12,0 ; SCRN 1 START (HI) + .DB 13,0 ; SCRN 1 START (LO) + .DB 18,-1 ; S2 ROW - 1 + .DB 27,0 ; VERT SYNC POS ADJ + .DB 30,$01 | $08 ; CTL 1, 2 WINDOWS & ENABLE R27 VSYNC FINE ADJ + .DB $FF ; END MARKER +#ENDIF +#IF (VGASIZ=V80X60) +;=============================================================================== +; 80x60X8 60hz REGISTER VALUES +;=============================================================================== +; +REGS_VGA: + .DB 0,100 - 1 ; HORZ TOT - 1 + .DB 1,VGA_COLS ; HORZ DISP + .DB 2,VGA_COLS + 2 ; HORZ DISP + HORZ FP + .DB 3,44 ; VERT SW, HORZ SW + .DB 4,66 - 1 ; VERT TOT - 1 + .DB 5,0 ; VERT TOT ADJ + .DB 6,VGA_ROWS ; VERT DISP + .DB 7,VGA_ROWS + 0 ; VERT DISP + VERT FP ROWS + .DB 9,VGA_SCANL - 1 ; CHAR HEIGHT - 1 + .DB 10,VGA_R10 ; CURSOR START & CURSOR BLINK + .DB 11,VGA_R11 ; CURSOR END + .DB 12,0 ; SCRN 1 START (HI) + .DB 13,0 ; SCRN 1 START (LO) + .DB 18,-1 ; S2 ROW - 1 + .DB 27,0 ; VERT SYNC POS ADJ + .DB 30,$01 | $08 ; CTL 1, 2 WINDOWS & ENABLE R27 VSYNC FINE ADJ + .DB $FF ; END MARKER +#ENDIF ;================================================================================================== ; VGA DRIVER - INSTANCE DATA ;================================================================================================== diff --git a/Source/HBIOS/xio.asm b/Source/HBIOS/xio.asm index 31a7d986..b1c15c3e 100644 --- a/Source/HBIOS/xio.asm +++ b/Source/HBIOS/xio.asm @@ -49,7 +49,7 @@ XIO_INIT: ; MINIMAL UART INIT ;OUT0 (Z180_CNTLB0),A ; -> CNTLB0 ; TRY TO IMPLEMENT CONFIGURED BAUD RATE - LD HL,DEFSERCFG ; SERIAL CONFIG WORD + LD HL,XIOCFG ; SERIAL CONFIG WORD LD A,H ; BYTE W/ ENCODED BAUD RATE AND $1F ; ISOLATE BITS LD L,A ; MOVE TO L @@ -65,7 +65,7 @@ XIO_INIT1: #IF ((PLATFORM == PLT_SBC) | (PLATFORM == PLT_ZETA) | (PLATFORM == PLT_ZETA2)) - LD DE,DEFSERCFG ; SERIAL CONFIG WORD + LD DE,XIOCFG ; SERIAL CONFIG WORD CALL XIO_COMPDIV ; COMPUTE DIVISOR TO BC LD A,$80 ; LCR := DLAB ON diff --git a/Source/Images/Build.cmd b/Source/Images/Build.cmd index 4d38b33c..2305cc74 100644 --- a/Source/Images/Build.cmd +++ b/Source/Images/Build.cmd @@ -1,15 +1,28 @@ @echo off setlocal -echo : -echo : Cleaning... -echo : -call Clean.cmd -echo : -echo : Building Floppy Disk Images... -echo : -call BuildFD.cmd -echo : -echo : Building Hard Disk Images... -echo : -call BuildHD.cmd +echo. +echo Building Floppy Disk Images... +echo. +call BuildFD.cmd cpm22 ..\cpm22\cpm_wbw.sys +call BuildFD.cmd zsdos ..\zsdos\zsys_wbw.sys +call BuildFD.cmd nzcom ..\zsdos\zsys_wbw.sys +call BuildFD.cmd cpm3 ..\cpm3\cpmldr.sys +call BuildFD.cmd zpm3 ..\cpm3\cpmldr.sys +call BuildFD.cmd ws4 + +echo. +echo Building Hard Disk Images... +echo. +call BuildHD.cmd cpm22 ..\cpm22\cpm_wbw.sys +call BuildHD.cmd zsdos ..\zsdos\zsys_wbw.sys +call BuildHD.cmd nzcom ..\zsdos\zsys_wbw.sys +call BuildHD.cmd cpm3 ..\cpm3\cpmldr.sys +call BuildHD.cmd zpm3 ..\cpm3\cpmldr.sys +call BuildHD.cmd ws4 + +if exist ..\BPBIOS\bpbio-ww.rel call BuildHD.cmd bp + +echo. +echo Building Combo Disk Image... +copy /b ..\..\Binary\hd_cpm22.img + ..\..\Binary\hd_zsdos.img + ..\..\Binary\hd_nzcom.img + ..\..\Binary\hd_cpm3.img + ..\..\Binary\hd_zpm3.img + ..\..\Binary\hd_ws4.img ..\..\Binary\hd_combo.img diff --git a/Source/Images/BuildFD.ps1 b/Source/Images/BuildFD.ps1 index 21781107..f482bc10 100644 --- a/Source/Images/BuildFD.ps1 +++ b/Source/Images/BuildFD.ps1 @@ -1,33 +1,69 @@ +#Param([Parameter(Mandatory)]$Disk, $SysFile="") +Param($Disk, $SysFile="") + $ErrorAction = 'Stop' -$CpmToolsPath = '../..\Tools\cpmtools' +$ImgFile = "fd_${Disk}.img" +$Fmt = "wbw_fd144" +$Size = 1440KB + +$CpmToolsPath = '../../Tools/cpmtools' $env:PATH = $CpmToolsPath + ';' + $env:PATH -$Blank = ([byte[]](0xE5) * 1440KB) +if (-not (Test-Path("d_${Disk}/"))) +{ + "Source directory d_${Disk} for disk ${Disk} not found!" + return +} -"Creating work file..." -if (!(Test-Path('Blank.tmp'))) {Set-Content -Value $Blank -Encoding byte -Path 'Blank.tmp'} +"Generating Floppy Disk ${Disk}..." + +#$Blank = ([string]([char]0xE5)) * $Size +#Set-Content -Value $Blank -NoNewLine -Path $ImgFile +$Blank = ([byte[]](0xE5) * $Size) +[System.IO.File]::WriteAllBytes($ImgFile, $Blank) + +if ($SysFile.Length -gt 0) +{ + "Adding System Image $SysFile..." + #$Sys = Get-Content -Path "$SysFile.sys" -Raw + #$Img = Get-Content -Path $ImgFile -Raw + #$NewImg = $Sys + $Img.SubString($Sys.Length, $Img.Length - $Sys.Length) + #Set-Content -NoNewLine -Path $ImgFile $NewImg + + $Cmd = "mkfs.cpm -f $Fmt -b $SysFile $ImgFile" + $Cmd + Invoke-Expression $Cmd +} + +for ($Usr=0; $Usr -lt 16; $Usr++) +{ + if (Test-Path ("d_${Disk}/u${Usr}/*")) + { + $Cmd = "cpmcp -f $Fmt $ImgFile d_${Disk}/u${Usr}/*.* ${Usr}:" + $Cmd + Invoke-Expression $Cmd + } +} -"Creating floppy disk images..." -for ($Dsk=0; $Dsk -lt 2; $Dsk++) +if (Test-Path("d_${Disk}.txt")) { - "Generating Floppy Disk ${Dsk}..." - copy Blank.tmp fd${Dsk}.img - for ($Usr=0; $Usr -lt 16; $Usr++) + foreach($Line in Get-Content "d_${Disk}.txt") { - if (Test-Path ("fd${Dsk}/u${Usr}/*")) + $Spec = $Line.Trim() + if (($Spec.Length -gt 0) -and ($Spec.Substring(0,1) -ne "#")) { - $Cmd = "cpmcp -f wbw_fd144 fd${Dsk}.img fd${Dsk}/u${Usr}/*.* ${Usr}:" + $Cmd = "cpmcp -f $Fmt $ImgFile ${Spec}" $Cmd Invoke-Expression $Cmd } } } -"Moving images into output directory..." -&$env:COMSPEC /c move fd*.img ..\..\Binary\ +"Moving image $ImgFile into output directory..." -Remove-Item *.tmp +#&$env:COMSPEC /c move $ImgFile ..\..\Binary\ +Move-Item $ImgFile -Destination "..\..\Binary\" -Force return \ No newline at end of file diff --git a/Source/Images/BuildHD.ps1 b/Source/Images/BuildHD.ps1 index 932753ee..eedfa4aa 100644 --- a/Source/Images/BuildHD.ps1 +++ b/Source/Images/BuildHD.ps1 @@ -1,39 +1,69 @@ +#Param([Parameter(Mandatory)]$Disk, $SysFile="") +Param($Disk, $SysFile="") + $ErrorAction = 'Stop' +$ImgFile = "hd_${Disk}.img" +$Fmt = "wbw_hd0" +$Size = (128KB * 65) + $CpmToolsPath = '../../Tools/cpmtools' $env:PATH = $CpmToolsPath + ';' + $env:PATH -$Blank = ([byte[]](0xE5) * (128KB * 65)) +if (-not (Test-Path("d_${Disk}/"))) +{ + "Source directory d_${Disk} for disk ${Disk} not found!" + return +} + +"Generating Hard Disk ${Disk}..." -"Creating work file..." -if (!(Test-Path('Blank.tmp'))) {Set-Content -Value $Blank -Encoding byte -Path 'Blank.tmp'} +#$Blank = ([string]([char]0xE5)) * $Size +#Set-Content -Value $Blank -NoNewLine -Path $ImgFile +$Blank = ([byte[]](0xE5) * $Size) +[System.IO.File]::WriteAllBytes($ImgFile, $Blank) + +if ($SysFile.Length -gt 0) +{ + "Adding System Image $SysFile..." + #$Sys = Get-Content -Path "$SysFile.sys" -Raw + #$Img = Get-Content -Path $ImgFile -Raw + #$NewImg = $Sys + $Img.SubString($Sys.Length, $Img.Length - $Sys.Length) + #Set-Content -NoNewLine -Path $ImgFile $NewImg + + $Cmd = "mkfs.cpm -f $Fmt -b $SysFile $ImgFile" + $Cmd + Invoke-Expression $Cmd +} -"Creating hard disk images..." -for ($Dsk=0; $Dsk -lt 2; $Dsk++) +for ($Usr=0; $Usr -lt 16; $Usr++) { - "Generating Hard Disk ${Dsk}..." - for ($Slice=0; $Slice -lt 4; $Slice++) + if (Test-Path ("d_${Disk}/u${Usr}/*")) { - "Adding files to slice ${Slice}..." - copy Blank.tmp slice${Slice}.tmp - for ($Usr=0; $Usr -lt 16; $Usr++) + $Cmd = "cpmcp -f $Fmt $ImgFile d_${Disk}/u${Usr}/*.* ${Usr}:" + $Cmd + Invoke-Expression $Cmd + } +} + +if (Test-Path("d_${Disk}.txt")) +{ + foreach($Line in Get-Content "d_${Disk}.txt") + { + $Spec = $Line.Trim() + if (($Spec.Length -gt 0) -and ($Spec.Substring(0,1) -ne "#")) { - if (Test-Path ("hd${Dsk}/s${Slice}/u${Usr}/*")) - { - $Cmd = "cpmcp -f wbw_hd0 slice${Slice}.tmp hd${Dsk}/s${Slice}/u${Usr}/*.* ${Usr}:" - $Cmd - Invoke-Expression $Cmd - } + $Cmd = "cpmcp -f $Fmt $ImgFile ${Spec}" + $Cmd + Invoke-Expression $Cmd } } - - "Combining slices into final disk image hd${Dsk}..." - &$env:COMSPEC /c copy /b slice*.tmp ..\..\Binary\hd${Dsk}.img - - Remove-Item slice*.tmp } -Remove-Item *.tmp +"Moving image $ImgFile into output directory..." + +#&$env:COMSPEC /c move $ImgFile ..\..\Binary\ +Move-Item $ImgFile -Destination "..\..\Binary\" -Force return \ No newline at end of file diff --git a/Source/Images/Clean.cmd b/Source/Images/Clean.cmd index ae874cd3..026d5786 100644 --- a/Source/Images/Clean.cmd +++ b/Source/Images/Clean.cmd @@ -3,3 +3,4 @@ setlocal if exist *.tmp del *.tmp if exist *.img del *.img +if exist *.sys del *.sys diff --git a/Source/Images/Common/CLRDIR.COM b/Source/Images/Common/CLRDIR.COM new file mode 100644 index 00000000..c6b05cc9 Binary files /dev/null and b/Source/Images/Common/CLRDIR.COM differ diff --git a/Source/Images/Common/COMPARE.COM b/Source/Images/Common/COMPARE.COM new file mode 100644 index 00000000..29fa41e6 Binary files /dev/null and b/Source/Images/Common/COMPARE.COM differ diff --git a/Source/Images/Common/DDTZ.COM b/Source/Images/Common/DDTZ.COM new file mode 100644 index 00000000..4f6eca6b Binary files /dev/null and b/Source/Images/Common/DDTZ.COM differ diff --git a/Source/Images/Common/FDISK80.COM b/Source/Images/Common/FDISK80.COM new file mode 100644 index 00000000..0d73cef2 Binary files /dev/null and b/Source/Images/Common/FDISK80.COM differ diff --git a/Source/Images/Common/FLASH.COM b/Source/Images/Common/FLASH.COM new file mode 100644 index 00000000..1b7fa64c Binary files /dev/null and b/Source/Images/Common/FLASH.COM differ diff --git a/Source/Images/Common/NULU.COM b/Source/Images/Common/NULU.COM new file mode 100644 index 00000000..fc5594b1 Binary files /dev/null and b/Source/Images/Common/NULU.COM differ diff --git a/Source/Images/fd0/u0/UNARC.COM b/Source/Images/Common/UNARC.COM similarity index 100% rename from Source/Images/fd0/u0/UNARC.COM rename to Source/Images/Common/UNARC.COM diff --git a/Source/Images/Common/ZAP.COM b/Source/Images/Common/ZAP.COM new file mode 100644 index 00000000..be4dbf38 Binary files /dev/null and b/Source/Images/Common/ZAP.COM differ diff --git a/Source/Images/Common/ZDE.COM b/Source/Images/Common/ZDE.COM new file mode 100644 index 00000000..f58310ba Binary files /dev/null and b/Source/Images/Common/ZDE.COM differ diff --git a/Source/Images/fd0/u0/ZDENST.COM b/Source/Images/Common/ZDENST.COM similarity index 100% rename from Source/Images/fd0/u0/ZDENST.COM rename to Source/Images/Common/ZDENST.COM diff --git a/Source/Images/Makefile b/Source/Images/Makefile new file mode 100644 index 00000000..4832dc37 --- /dev/null +++ b/Source/Images/Makefile @@ -0,0 +1,100 @@ +# +# this makefile subsumes all the work done in Build.cmd, Build{Hd,Fd}.* +# +SYSTEMS = ../CPM22/cpm_wbw.sys ../ZSDOS/zsys_wbw.sys ../CPM3/cpmldr.sys + +FDIMGS = fd_cpm22.img fd_zsdos.img fd_nzcom.img \ + fd_cpm3.img fd_zpm3.img fd_ws4.img +HDIMGS = hd_cpm22.img hd_zsdos.img hd_nzcom.img \ + hd_cpm3.img hd_zpm3.img hd_ws4.img +# HDIMGS += hd_bp.img + +OBJECTS = $(FDIMGS) $(HDIMGS) hd_combo.img +OTHERS = blank144 blankhd + +DEST=../../Binary + +TOOLS = ../../Tools +include $(TOOLS)/Makefile.inc + +DIFFPATH = $(DIFFTO)/Binary + +hd_combo.img: $(HDIMGS) + cat $(HDIMGS) > $@ + +# +# this somewhat impenetrable and fragile code is used to build each of the images +# at build time, a few variables are set (sys, fmt, type, size, d) based on the +# target to build. first, we build an empty image using the a tr, dd pipeline. +# we then scan the d_{d}/u* directories, copying in files to user numbers +# then process the d_{d}.txt file, copying in those files, and finally maybe put +# an OS at the start of each image +# +blank144: + @echo Making Blank Floppy of size 1440k + @LANG=en_US.US-ASCII tr '\000' '\345' /dev/null + +HDSIZE := $(shell expr 128 '*' 65) + +blankhd: + @echo Making Blank Hd of size $(HDSIZE)k + @LANG=en_US.US-ASCII tr '\000' '\345' /dev/null + +%.img: $(SYSTEMS) blank144 blankhd Makefile + @sys= ; \ + case $@ in \ + (*cpm22*) sys=../CPM22/cpm_wbw.sys;; \ + (*zsdos* | *nzcom*) sys=../ZSDOS/zsys_wbw.sys;; \ + (*cpm3* | *zpm3*) sys=../CPM3/cpmldr.sys;; \ + esac ; \ + if echo $@ | grep -q ^f ; then \ + fmt=wbw_fd144 ; type=fd_ ; proto=blank144 ; \ + else \ + fmt=wbw_hd0 ; type=hd_ ; proto=blankhd ; \ + fi ; \ + d=$$(echo $(basename $@) | sed s/$$type//) ; \ + echo Generating $@ ; \ + cp $$proto $@ ; \ + if [ "$$sys" ] ; then \ + echo copying system $$sys to $@ ; \ + $(BINDIR)/mkfs.cpm -f $$fmt -b $$sys $@ ; \ + fi ; \ + for u in $$(seq 0 15) ; do \ + dir=d_$$d/u$$u ; \ + if [ -d $$dir ] ; then \ + echo " " copying directory $$dir ; \ + for i in $$dir/* ; do \ + f=$$($(CASEFN) $$i) ; \ + echo " " $$f ; \ + $(CPMCP) -f $$fmt $@ $$f $$u: ; \ + done ; \ + fi ; \ + done ; \ + if [ -f d_$$d.txt ] ; then \ + echo " " copying files from d_$$d.txt ; \ + grep -v ^# d_$$d.txt | tr -d '\r' | while read file user ; do \ + rf=$$($(CASEFN) $$file | sort -V) ; \ + echo " " $$rf ; \ + if [ -z "$$rf" ] ; then \ + echo " " $$file missing ; \ + else \ + $(CPMCP) -f $$fmt $@ $$rf $$user ; \ + fi ; \ + done ; \ + fi ; \ + + +clean:: + @rm -f *.ls + +imgdiff: + @for i in $(FDIMGS) $(HDIMGS) ; do \ + echo $$i ; \ + if echo $$i | grep -q ^f ; then \ + fmt=wbw_fd144 ; \ + else \ + fmt=wbw_hd0 ; \ + fi ; \ + $(BINDIR)/cpmls -i -f $$fmt $$i > $$i.ls ; \ + $(BINDIR)/cpmls -i -f $$fmt $(DIFFPATH)/$$i > $$i.diff.ls ; \ + done \ diff --git a/Source/Images/ReadMe.txt b/Source/Images/ReadMe.txt index b984e4a4..fb4338e2 100644 --- a/Source/Images/ReadMe.txt +++ b/Source/Images/ReadMe.txt @@ -13,8 +13,8 @@ image to a floppy or hard disk (including CF and SD cards). In summary, CP/M files are placed inside of a pre-defined Windows directory structure. A script is then run to create the floppy and hard disk images from the directory tree contents. The resultant -images may be copied directly to floppy or hard disk media or used -for SIMH emulator disk images. +images may be copied directly to floppy or hard disk media or used as +SIMH emulator disk images. System Requirements ------------------- @@ -26,186 +26,436 @@ PowerShell. It is included in all versions after Windows XP. If you are using Windows XP, you will need to download it from Microsoft and install it (free download). +Although not documented here, the Linux/Mac build process will also +create disk images using a similar process based on Makefiles. + The cpmtools toolset is used to generate the actual disk images. -This toolset is included in the distribution. +This toolset is included in the distribution, so you do not need to +download or install it. Preparing the Source Directory Contents --------------------------------------- The script expects your files to be found inside a specific directory -structure. Note that you will see there are some CP/M files in the -Source directory tree in the distribution. These are simply test -files I used and have no specific meaing. You will probably want to -replace them with your own files as desired. - -If you look at the Images directory, you will find 4 -sub-directories. fd0 and fd1 will contain the files to be placed in -the two floppy images gneerated. hd0 and hd1 will contain the files -to be used to generate the two hard disk images. There is nothing -magic about the fact that there are two of each kind of image -generated. It just seemed like a good number to the author. A quick -review of the scripts and you will see it is very easy to modify the -number of images generated if you want. - -For floppy disks, the structure is: - - fd0 --+--> u0 - +--> u1 - | - +--> u15 - -Above, fd0 refers to the first floppy disk image and u0...u15 refer -to the user areas on the disk. You place whatever files you want on -fd0, user 0 in the fd0\u0 directory. You will notice that not all of -the u0...u15 directories exist. The script does not care and treats -a non-existent directory as a directory with no files. The fd1 -directory is exactly the same as fd0 -- it is simply the second -floppy image. +structure. The structure is: + + d_xxx --+--> u0 + +--> u1 + +--> u2 + | . + | . + | . + +--> u15 + +A given disk is reprsented by a directory named d_xxx where xxx can +be anything you want. Within the d_xxx directory, the CP/M user +areas are represented by subdirectories names u0 thru u15. The files +to be placed in the disk image are placed inside of the u0 thru u15 +directories depending on which user area you want the file(s) to +appear. You do not need to create all of the u## subdirectories, +only the ones corresponding to the user areas you want to put files in. + +To build the disk images, you run the Build.cmd batch file from a +command prompt. Build.cmd in turn invokes separate scripts to create +the floppy and hard disk images. + +As distributed, you will see that there are several d_ directories +populated with files. If you look at the Build.cmd +script, you will find that the names of each of these directories is +listed. If you want to add a new d_ directory to be converted into a +disk image, you will need to add the name of your new directory to +this list. Note that each d_ directory may be turned into a floppy +image or a hard disk image or both. At present, the scripts assume that the floppy media is 1.44MB. You will need to modify the scripts if you want to create different media. -For hard disks, the structure has one more level: - - hd0 --+--> s0 --+--> u0 - | +--> u1 - | | - | +--> u15 - | - +--> s1 --+--> u0 - | +--> u1 - | | - | +--> u15 - | - +--> s2 --+--> u0 - | +--> u1 - | | - | +--> u15 - | - +--> s3 --+--> u0 - +--> u1 - | - +--> u15 - -The above uses the same concept as the floppy disk source structure, -but includes an additional directory layer to represent the first 4 -slices of the hard disk. For most RomWBW builds, s0-s3 would show up -as the first 4 hard disk drive letters, frequently E: to H:. - -No files should be placed in the first two layers of the tree (hd0 or -s0-s3). All files go into the lowest level of the tree (u0-u15). As -above, empty or non-existent directories are not a problem for the -script. Just fill in or create the appropriate directories. The -only constraint is the the script will only look for two hard disks -(hd0-hd1), 4 slices (s0-s4), and 16 user areas (u0-u15). The number -of hard disks and number of slices could be changed by modifying the -generation scripts. - Building the Images ------------------- The image creation process simply traverses the directory structures -described above and builds a raw image each floppy disk or hard -disk. Note that cpmtools is used to generate the images and is +described above and builds a raw disk image for each floppy disk or +hard disk. Note that cpmtools is used to generate the images and is included in the distribution under the Tools directory. +Many of the disk images depend upon files that are produced by +building the shared components of RomWBW. Prior to running +the Build command in the Images directory, you should first +run the BuildShared command in the Source directory. + The scripts are intended to be run from a command prompt. Open a -command prompt and navigate to the Images directory. To build the -floppy disk images (fd0 and fd1), use the command "BuildFD". To build -the hard disk images (hd0, hd1), use the command "BuildHD". You can -use the command "BuildAll" to build both the floppy and hard disk -images in one run. +command prompt and navigate to the Images directory. Use the command +"Build" to build both the floppy and hard disk images in one run. +You can build a single disk image by running either BuildFD.cmd or +BuildHD.cmd with a single parameter specifying the disk name. After completion of the script, the resultant image files are placed -in the Binary directory with names such as fd0.img and hd0.img. +in the Binary directory with names such as fd_xxx.img and hd_xxx.img. -Below is sample output from building the hard disk images: - - | C:\Users\WWarthen\Projects\N8VEM\Build\RomWBW\Images>BuildHD - | Creating work file... - | Creating hard disk images... - | Generating Hard Disk 0... - | Adding files to slice 0... - | cpmcp -f wbw_hd0 slice0.tmp Source/hd0/s0/u0/*.* 0: - | cpmcp -f wbw_hd0 slice0.tmp Source/hd0/s0/u2/*.* 2: - | Adding files to slice 1... - | cpmcp -f wbw_hd0 slice1.tmp Source/hd0/s1/u0/*.* 0: - | Adding files to slice 2... - | Adding files to slice 3... - | Combining slices into final disk image hd0... - | slice0.tmp - | slice1.tmp - | slice2.tmp - | slice3.tmp - | 1 file(s) copied. - | Generating Hard Disk 1... - | Adding files to slice 0... - | Adding files to slice 1... - | Adding files to slice 2... - | Adding files to slice 3... - | Combining slices into final disk image hd1... - | slice0.tmp - | slice1.tmp - | slice2.tmp - | slice3.tmp - | 1 file(s) copied. - | - | C:\Users\WWarthen\Projects\N8VEM\Build\RomWBW\Images> +Sample output from running Build.cmd is provided at the end of +this file. Be aware that the script always builds the image file from scratch. It will not update the previous contents. Any contents of a pre-existing image file will be permanently destroyed. -Installing Images ------------------ - -First of all, a MAJOR WARNING!!!! The tools described below are -quite capable of obliterating your running Windows system drive. Use -with extreme caution and make sure you have backups. - -To install a floppy image on floppy media, you can use the tool -called RaWriteWin. This tool is included in the Tools directory of -the distribution. This tool will write your floppy image (fd0.img or -fd1.img) to a floppy disk using a raw block transfer. The tool is -GUI based and it's operation is self explanatory. - -To install a hard disk image on a CF card or SD card, you must have -the appropriate media card slot on your computer. If you do, you can -use the tool called Win32 Disk Imager. This tool is also included in -the Tools directory of the distribution. This tool will write your -hard disk image (hd0.img or hd1.img) to the designated media card. -This tool is also GUI based and self explanatory. - -Use of the SIMH emulator is outside of the scope of this document. -However, if you use SIMH, you will find that you can attach the hard -disk images to the emulator with lines such as the following in your -SIMH configuration file: - - | attach hdsk0 hd0.img - | set hdsk0 format=HDSK - | set hdsk0 geom=T:520/N:256/S:512 - | set hdsk0 wrtenb - -Making Disk Images Bootable ---------------------------- - -The current generation of these scripts does not make the resultant -media bootable. This is primarily because there are multiple choices -for what you can put on the boot tracks of the media and that is a -choice best left to the user. - -The simplest way to make a resultant image bootable is to do it from -your running CP/M system. Boot your system using the ROM selection, -then use the COPYSYS command to make the desired drive bootable. - -You would use a command like the following to make drive C bootable. - - | B>COPYSYS C:=CPM.SYS - -Notes ------ - -I realize these instructions are very minimal. I am happy to answer -questions. You will find the RetroBrew Computers Forum at -https://www.retrobrewcomputers.org/forum/ to be a great source of -information as well. \ No newline at end of file +Slices +------ + +A RomWBW CP/M filesystem is fixed at 8MB. This is because it is the +largest size filesystem supported by all common CP/M variants. Since +all modern hard disks (including SD Cards and CF Cards) are much +larger than 8MB, RomWBW supports the concept of "slices". This +simply means that you can concatenate multiple CP/M filesystems (up +to 256 of them) on a single physical hard disk and RomWBW will allow +you to assign drive letters to them and treat them as multiple +independent CP/M drives. + +The disk image creation scripts in this directory will only create a +single CP/M file system (i.e., a single slice). However, you can +easily create a multi-slice disk image by merely concatenating +multiple images together. For example, if you wanted to create a 2 +slice disk image that has ZSDOS in the first slice and Wordstar in +the second slice, you could use the following command from a Windows +command prompt: + + | C:\RomWBW\Binary>copy /b hd_zsdos.img + hd_ws.img hd_multi.img + +You can now write hd_multi.img onto your SD or CF Card and you will +have ZSDOS in the first slice and Wordstar in the second slice. + +The concept of slices applies ONLY to hard disks. Floppy disks are +not large enough to support multiple slices. + +Disk Images +----------- + +The standard RomWBW build process builds the disk images defined +in this directory. The resultant images are placed in the Binary +directory and are ready to copy to your media. + +A description of the specific image files is found in the file +called DiskList.txt in the Binary directory of the distribution. + +Sample Run +---------- + +Below is sample output from building the hard disk images: + +C:\Users\Wayne\Projects\RBC\Build\RomWBW\Source\Images>Build.cmd + | : + | : Cleaning... + | : + | : + | : Creating System Images + | : + | ..\bl\bl.bin + | ..\cpm22\os2ccp.bin + | ..\cpm22\os3bdos.bin + | ..\cbios\cbios_wbw.bin + | 1 file(s) copied. + | ..\bl\bl.bin + | ..\cpm22\os2ccp.bin + | ..\cpm22\os3bdos.bin + | ..\cbios\cbios_una.bin + | 1 file(s) copied. + | ..\bl\bl.bin + | ..\zcpr-dj\zcpr.bin + | ..\zsdos\zsdos.bin + | ..\cbios\cbios_wbw.bin + | 1 file(s) copied. + | ..\bl\bl.bin + | ..\zcpr-dj\zcpr.bin + | ..\zsdos\zsdos.bin + | ..\cbios\cbios_una.bin + | 1 file(s) copied. + | : + | : Building Floppy Disk Images... + | : + | Generating Floppy Disk cpm22... + | cpmcp -f wbw_fd144 fd_cpm22.img d_cpm22/u0/*.* 0: + | cpmcp -f wbw_fd144 fd_cpm22.img d_cpm22/u1/*.* 1: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/assign.com 0: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/fat.com 0: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/fdu.com 0: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/format.com 0: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/mode.com 0: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/osldr.com 0: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/rtc.com 0: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/survey.com 0: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/syscopy.com 0: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/sysgen.com 0: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/talk.com 0: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/timer.com 0: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/xm.com 0: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/inttest.com 0: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/tune.com 3: + | cpmcp -f wbw_fd144 fd_cpm22.img ../../Binary/Apps/Tunes/*.* 3: + | Adding System Image cpm_wbw... + | Moving image fd_cpm22.img into output directory... + | 1 file(s) moved. + | Generating Floppy Disk zsdos... + | cpmcp -f wbw_fd144 fd_zsdos.img d_zsdos/u0/*.* 0: + | cpmcp -f wbw_fd144 fd_zsdos.img d_zsdos/u1/*.* 1: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/assign.com 0: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/fat.com 0: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/fdu.com 0: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/format.com 0: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/mode.com 0: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/osldr.com 0: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/rtc.com 0: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/survey.com 0: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/syscopy.com 0: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/sysgen.com 0: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/talk.com 0: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/timer.com 0: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/xm.com 0: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/inttest.com 0: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/tune.com 3: + | cpmcp -f wbw_fd144 fd_zsdos.img ../../Binary/Apps/Tunes/*.* 3: + | Adding System Image zsys_wbw... + | Moving image fd_zsdos.img into output directory... + | 1 file(s) moved. + | Generating Floppy Disk nzcom... + | cpmcp -f wbw_fd144 fd_nzcom.img d_nzcom/u0/*.* 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/assign.com 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/fat.com 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/fdu.com 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/format.com 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/mode.com 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/osldr.com 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/rtc.com 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/survey.com 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/syscopy.com 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/sysgen.com 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/talk.com 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/timer.com 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/xm.com 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/inttest.com 0: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/tune.com 3: + | cpmcp -f wbw_fd144 fd_nzcom.img ../../Binary/Apps/Tunes/*.* 3: + | Adding System Image zsys_wbw... + | Moving image fd_nzcom.img into output directory... + | 1 file(s) moved. + | Generating Floppy Disk cpm3... + | cpmcp -f wbw_fd144 fd_cpm3.img d_cpm3/u0/*.* 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/cpmldr.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/ccp.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/gencpm.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/genres.dat 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/genbnk.dat 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/bios3.spr 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/bnkbios3.spr 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/bdos3.spr 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/bnkbdos3.spr 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/resbdos3.spr 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/cpm3res.sys 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/cpm3bnk.sys 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/gencpm.dat 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/cpm3.sys 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/readme.1st 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../CPM3/cpm3fix.pat 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/assign.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/fat.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/fdu.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/format.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/mode.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/osldr.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/rtc.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/survey.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/syscopy.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/sysgen.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/talk.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/timer.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/xm.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/inttest.com 0: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/tune.com 3: + | cpmcp -f wbw_fd144 fd_cpm3.img ../../Binary/Apps/Tunes/*.* 3: + | Moving image fd_cpm3.img into output directory... + | 1 file(s) moved. + | Generating Floppy Disk zpm3... + | cpmcp -f wbw_fd144 fd_zpm3.img d_zpm3/u0/*.* 0: + | cpmcp -f wbw_fd144 fd_zpm3.img d_zpm3/u10/*.* 10: + | cpmcp -f wbw_fd144 fd_zpm3.img d_zpm3/u14/*.* 14: + | cpmcp -f wbw_fd144 fd_zpm3.img d_zpm3/u15/*.* 15: + | cpmcp -f wbw_fd144 fd_zpm3.img ../ZPM3/zpmldr.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../ZPM3/cpmldr.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../ZPM3/autotog.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../ZPM3/clrhist.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../ZPM3/setz3.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../ZPM3/cpm3.sys 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../ZPM3/zccp.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../ZPM3/zinstal.zpm 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../ZPM3/startzpm.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../ZPM3/makedos.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../ZPM3/gencpm.dat 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../ZPM3/bnkbios3.spr 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../ZPM3/bnkbdos3.spr 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../ZPM3/resbdos3.spr 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/assign.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/fat.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/fdu.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/format.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/mode.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/osldr.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/rtc.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/survey.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/syscopy.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/sysgen.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/talk.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/timer.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/xm.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/inttest.com 0: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/tune.com 3: + | cpmcp -f wbw_fd144 fd_zpm3.img ../../Binary/Apps/Tunes/*.* 3: + | Moving image fd_zpm3.img into output directory... + | 1 file(s) moved. + | Generating Floppy Disk ws4... + | cpmcp -f wbw_fd144 fd_ws4.img d_ws4/u0/*.* 0: + | Moving image fd_ws4.img into output directory... + | 1 file(s) moved. + | : + | : Building Hard Disk Images... + | : + | Generating Hard Disk cpm22... + | cpmcp -f wbw_hd0 hd_cpm22.img d_cpm22/u0/*.* 0: + | cpmcp -f wbw_hd0 hd_cpm22.img d_cpm22/u1/*.* 1: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/assign.com 0: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/fat.com 0: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/fdu.com 0: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/format.com 0: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/mode.com 0: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/osldr.com 0: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/rtc.com 0: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/survey.com 0: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/syscopy.com 0: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/sysgen.com 0: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/talk.com 0: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/timer.com 0: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/xm.com 0: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/inttest.com 0: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/tune.com 3: + | cpmcp -f wbw_hd0 hd_cpm22.img ../../Binary/Apps/Tunes/*.* 3: + | Adding System Image cpm_wbw... + | Moving image hd_cpm22.img into output directory... + | 1 file(s) moved. + | Generating Hard Disk zsdos... + | cpmcp -f wbw_hd0 hd_zsdos.img d_zsdos/u0/*.* 0: + | cpmcp -f wbw_hd0 hd_zsdos.img d_zsdos/u1/*.* 1: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/assign.com 0: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/fat.com 0: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/fdu.com 0: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/format.com 0: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/mode.com 0: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/osldr.com 0: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/rtc.com 0: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/survey.com 0: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/syscopy.com 0: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/sysgen.com 0: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/talk.com 0: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/timer.com 0: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/xm.com 0: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/inttest.com 0: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/tune.com 3: + | cpmcp -f wbw_hd0 hd_zsdos.img ../../Binary/Apps/Tunes/*.* 3: + | Adding System Image zsys_wbw... + | Moving image hd_zsdos.img into output directory... + | 1 file(s) moved. + | Generating Hard Disk nzcom... + | cpmcp -f wbw_hd0 hd_nzcom.img d_nzcom/u0/*.* 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/assign.com 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/fat.com 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/fdu.com 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/format.com 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/mode.com 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/osldr.com 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/rtc.com 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/survey.com 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/syscopy.com 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/sysgen.com 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/talk.com 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/timer.com 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/xm.com 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/inttest.com 0: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/tune.com 3: + | cpmcp -f wbw_hd0 hd_nzcom.img ../../Binary/Apps/Tunes/*.* 3: + | Adding System Image zsys_wbw... + | Moving image hd_nzcom.img into output directory... + | 1 file(s) moved. + | Generating Hard Disk cpm3... + | cpmcp -f wbw_hd0 hd_cpm3.img d_cpm3/u0/*.* 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/cpmldr.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/ccp.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/gencpm.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/genres.dat 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/genbnk.dat 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/bios3.spr 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/bnkbios3.spr 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/bdos3.spr 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/bnkbdos3.spr 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/resbdos3.spr 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/cpm3res.sys 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/cpm3bnk.sys 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/gencpm.dat 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/cpm3.sys 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/readme.1st 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../CPM3/cpm3fix.pat 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/assign.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/fat.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/fdu.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/format.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/mode.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/osldr.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/rtc.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/survey.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/syscopy.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/sysgen.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/talk.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/timer.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/xm.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/inttest.com 0: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/tune.com 3: + | cpmcp -f wbw_hd0 hd_cpm3.img ../../Binary/Apps/Tunes/*.* 3: + | Moving image hd_cpm3.img into output directory... + | 1 file(s) moved. + | Generating Hard Disk zpm3... + | cpmcp -f wbw_hd0 hd_zpm3.img d_zpm3/u0/*.* 0: + | cpmcp -f wbw_hd0 hd_zpm3.img d_zpm3/u10/*.* 10: + | cpmcp -f wbw_hd0 hd_zpm3.img d_zpm3/u14/*.* 14: + | cpmcp -f wbw_hd0 hd_zpm3.img d_zpm3/u15/*.* 15: + | cpmcp -f wbw_hd0 hd_zpm3.img ../ZPM3/zpmldr.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../ZPM3/cpmldr.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../ZPM3/autotog.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../ZPM3/clrhist.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../ZPM3/setz3.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../ZPM3/cpm3.sys 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../ZPM3/zccp.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../ZPM3/zinstal.zpm 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../ZPM3/startzpm.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../ZPM3/makedos.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../ZPM3/gencpm.dat 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../ZPM3/bnkbios3.spr 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../ZPM3/bnkbdos3.spr 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../ZPM3/resbdos3.spr 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/assign.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/fat.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/fdu.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/format.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/mode.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/osldr.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/rtc.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/survey.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/syscopy.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/sysgen.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/talk.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/timer.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/xm.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/inttest.com 0: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/tune.com 3: + | cpmcp -f wbw_hd0 hd_zpm3.img ../../Binary/Apps/Tunes/*.* 3: + | Moving image hd_zpm3.img into output directory... + | 1 file(s) moved. + | Generating Hard Disk ws4... + | cpmcp -f wbw_hd0 hd_ws4.img d_ws4/u0/*.* 0: + | Moving image hd_ws4.img into output directory... + | 1 file(s) moved. diff --git a/Source/Images/d_bp.txt b/Source/Images/d_bp.txt new file mode 100644 index 00000000..fa445326 --- /dev/null +++ b/Source/Images/d_bp.txt @@ -0,0 +1,23 @@ +# +# Add RomWBW utilities +# +../../Binary/Apps/*.com 15: +# +# Add Tune sample files +# +../../Binary/Apps/Tunes/*.pt? 3: +../../Binary/Apps/Tunes/*.mym 3: +# +# Add ZSystem images +# +../BPBIOS/*.img 0: +../BPBIOS/*.rel 0: +../BPBIOS/*.zrl 0: +../BPBIOS/*.zex 0: +../BPBIOS/myterm.z3t 0: +../BPBIOS/Z34RCP11/z34rcp11.rel 0:rcp.zrl +../BPBIOS/NZFCP13/nzfcp13.rel 0:fcp.zrl +# +# Add Common Applications +# +Common/*.* 0: diff --git a/Source/Images/hd0/s0/u0/alien.dat b/Source/Images/d_bp/u0/ALIEN.DAT similarity index 100% rename from Source/Images/hd0/s0/u0/alien.dat rename to Source/Images/d_bp/u0/ALIEN.DAT diff --git a/Source/Images/fd0/u0/ASM.COM b/Source/Images/d_bp/u0/ASM.COM similarity index 100% rename from Source/Images/fd0/u0/ASM.COM rename to Source/Images/d_bp/u0/ASM.COM diff --git a/Source/Images/hd0/s0/u0/bpbuild.com b/Source/Images/d_bp/u0/BPBUILD.COM similarity index 100% rename from Source/Images/hd0/s0/u0/bpbuild.com rename to Source/Images/d_bp/u0/BPBUILD.COM diff --git a/Source/Images/hd0/s0/u0/bpcnfg.com b/Source/Images/d_bp/u0/BPCNFG.COM similarity index 100% rename from Source/Images/hd0/s0/u0/bpcnfg.com rename to Source/Images/d_bp/u0/BPCNFG.COM diff --git a/Source/Images/hd0/s0/u0/bpdbug.com b/Source/Images/d_bp/u0/BPDBUG.COM similarity index 100% rename from Source/Images/hd0/s0/u0/bpdbug.com rename to Source/Images/d_bp/u0/BPDBUG.COM diff --git a/Source/Images/hd0/s0/u0/bpformat.com b/Source/Images/d_bp/u0/BPFORMAT.COM similarity index 100% rename from Source/Images/hd0/s0/u0/bpformat.com rename to Source/Images/d_bp/u0/BPFORMAT.COM diff --git a/Source/Images/hd0/s0/u0/bpswap.com b/Source/Images/d_bp/u0/BPSWAP.COM similarity index 100% rename from Source/Images/hd0/s0/u0/bpswap.com rename to Source/Images/d_bp/u0/BPSWAP.COM diff --git a/Source/Images/hd0/s0/u0/bpsysgen.com b/Source/Images/d_bp/u0/BPSYSGEN.COM similarity index 100% rename from Source/Images/hd0/s0/u0/bpsysgen.com rename to Source/Images/d_bp/u0/BPSYSGEN.COM diff --git a/Source/Images/hd0/s0/u0/confz4.com b/Source/Images/d_bp/u0/CONFZ4.COM similarity index 100% rename from Source/Images/hd0/s0/u0/confz4.com rename to Source/Images/d_bp/u0/CONFZ4.COM diff --git a/Source/Images/fd0/u0/CR.COM b/Source/Images/d_bp/u0/CR.COM similarity index 100% rename from Source/Images/fd0/u0/CR.COM rename to Source/Images/d_bp/u0/CR.COM diff --git a/Source/Images/d_bp/u0/DDT.COM b/Source/Images/d_bp/u0/DDT.COM new file mode 100644 index 00000000..70e4ebfe Binary files /dev/null and b/Source/Images/d_bp/u0/DDT.COM differ diff --git a/Source/Images/fd0/u0/DDTZ.DOC b/Source/Images/d_bp/u0/DDTZ.DOC similarity index 100% rename from Source/Images/fd0/u0/DDTZ.DOC rename to Source/Images/d_bp/u0/DDTZ.DOC diff --git a/Source/Images/fd0/u0/DIRX.COM b/Source/Images/d_bp/u0/DIRX.COM similarity index 100% rename from Source/Images/fd0/u0/DIRX.COM rename to Source/Images/d_bp/u0/DIRX.COM diff --git a/Source/Images/fd0/u0/DUMP.COM b/Source/Images/d_bp/u0/DUMP.COM similarity index 100% rename from Source/Images/fd0/u0/DUMP.COM rename to Source/Images/d_bp/u0/DUMP.COM diff --git a/Source/Images/fd0/u0/ED.COM b/Source/Images/d_bp/u0/ED.COM similarity index 100% rename from Source/Images/fd0/u0/ED.COM rename to Source/Images/d_bp/u0/ED.COM diff --git a/Source/Images/hd0/s0/u0/emulate.com b/Source/Images/d_bp/u0/EMULATE.COM similarity index 100% rename from Source/Images/hd0/s0/u0/emulate.com rename to Source/Images/d_bp/u0/EMULATE.COM diff --git a/Source/Images/hd0/s0/u0/fa.com b/Source/Images/d_bp/u0/FA.COM similarity index 100% rename from Source/Images/hd0/s0/u0/fa.com rename to Source/Images/d_bp/u0/FA.COM diff --git a/Source/Images/hd0/s0/u0/hashini.com b/Source/Images/d_bp/u0/HASHINI.COM similarity index 100% rename from Source/Images/hd0/s0/u0/hashini.com rename to Source/Images/d_bp/u0/HASHINI.COM diff --git a/Source/Images/hd0/s0/u0/hdiag.com b/Source/Images/d_bp/u0/HDIAG.COM similarity index 100% rename from Source/Images/hd0/s0/u0/hdiag.com rename to Source/Images/d_bp/u0/HDIAG.COM diff --git a/Source/Images/hd0/s0/u0/help.com b/Source/Images/d_bp/u0/HELP.COM similarity index 100% rename from Source/Images/hd0/s0/u0/help.com rename to Source/Images/d_bp/u0/HELP.COM diff --git a/Source/Images/hd0/s0/u0/iniramd.com b/Source/Images/d_bp/u0/INIRAMD.COM similarity index 100% rename from Source/Images/hd0/s0/u0/iniramd.com rename to Source/Images/d_bp/u0/INIRAMD.COM diff --git a/Source/Images/fd1/u0/INITDIR.COM b/Source/Images/d_bp/u0/INITDIR.COM similarity index 100% rename from Source/Images/fd1/u0/INITDIR.COM rename to Source/Images/d_bp/u0/INITDIR.COM diff --git a/Source/Images/hd0/s0/u0/instal12.com b/Source/Images/d_bp/u0/INSTAL12.COM similarity index 100% rename from Source/Images/hd0/s0/u0/instal12.com rename to Source/Images/d_bp/u0/INSTAL12.COM diff --git a/Source/Images/hd0/s0/u0/iopinit.com b/Source/Images/d_bp/u0/IOPINIT.COM similarity index 100% rename from Source/Images/hd0/s0/u0/iopinit.com rename to Source/Images/d_bp/u0/IOPINIT.COM diff --git a/Source/Images/d_bp/u0/JETLDR.COM b/Source/Images/d_bp/u0/JETLDR.COM new file mode 100644 index 00000000..c33c0738 Binary files /dev/null and b/Source/Images/d_bp/u0/JETLDR.COM differ diff --git a/Source/Images/fd0/u0/LBREXT.COM b/Source/Images/d_bp/u0/LBREXT.COM similarity index 100% rename from Source/Images/fd0/u0/LBREXT.COM rename to Source/Images/d_bp/u0/LBREXT.COM diff --git a/Source/Images/d_bp/u0/LDDS.COM b/Source/Images/d_bp/u0/LDDS.COM new file mode 100644 index 00000000..357f1360 Binary files /dev/null and b/Source/Images/d_bp/u0/LDDS.COM differ diff --git a/Source/Images/d_bp/u0/LDNZT.COM b/Source/Images/d_bp/u0/LDNZT.COM new file mode 100644 index 00000000..87cf0b47 Binary files /dev/null and b/Source/Images/d_bp/u0/LDNZT.COM differ diff --git a/Source/Images/d_bp/u0/LDP2D.COM b/Source/Images/d_bp/u0/LDP2D.COM new file mode 100644 index 00000000..a89e03d8 Binary files /dev/null and b/Source/Images/d_bp/u0/LDP2D.COM differ diff --git a/Source/Images/hd0/s0/u0/ldr.com b/Source/Images/d_bp/u0/LDR.COM similarity index 100% rename from Source/Images/hd0/s0/u0/ldr.com rename to Source/Images/d_bp/u0/LDR.COM diff --git a/Source/Images/hd0/s0/u0/ldsys.com b/Source/Images/d_bp/u0/LDSYS.COM similarity index 100% rename from Source/Images/hd0/s0/u0/ldsys.com rename to Source/Images/d_bp/u0/LDSYS.COM diff --git a/Source/Images/fd0/u0/LIB.COM b/Source/Images/d_bp/u0/LIB.COM similarity index 100% rename from Source/Images/fd0/u0/LIB.COM rename to Source/Images/d_bp/u0/LIB.COM diff --git a/Source/Images/fd0/u0/LINK.COM b/Source/Images/d_bp/u0/LINK.COM similarity index 100% rename from Source/Images/fd0/u0/LINK.COM rename to Source/Images/d_bp/u0/LINK.COM diff --git a/Source/Images/fd0/u0/LOAD.COM b/Source/Images/d_bp/u0/LOAD.COM similarity index 100% rename from Source/Images/fd0/u0/LOAD.COM rename to Source/Images/d_bp/u0/LOAD.COM diff --git a/Source/Images/fd0/u0/MAC.COM b/Source/Images/d_bp/u0/MAC.COM similarity index 100% rename from Source/Images/fd0/u0/MAC.COM rename to Source/Images/d_bp/u0/MAC.COM diff --git a/Source/Images/fd0/u0/MBASIC.COM b/Source/Images/d_bp/u0/MBASIC.COM similarity index 100% rename from Source/Images/fd0/u0/MBASIC.COM rename to Source/Images/d_bp/u0/MBASIC.COM diff --git a/Source/Images/hd0/s0/u0/NZDEC23D.Z3T b/Source/Images/d_bp/u0/NZDEC23D.Z3T similarity index 100% rename from Source/Images/hd0/s0/u0/NZDEC23D.Z3T rename to Source/Images/d_bp/u0/NZDEC23D.Z3T diff --git a/Source/Images/fd1/u0/ZPATH.COM b/Source/Images/d_bp/u0/PATH.COM similarity index 100% rename from Source/Images/fd1/u0/ZPATH.COM rename to Source/Images/d_bp/u0/PATH.COM diff --git a/Source/Images/fd0/u0/PIP.COM b/Source/Images/d_bp/u0/PIP.COM similarity index 100% rename from Source/Images/fd0/u0/PIP.COM rename to Source/Images/d_bp/u0/PIP.COM diff --git a/Source/Images/fd1/u0/PUTDS.COM b/Source/Images/d_bp/u0/PUTDS.COM similarity index 100% rename from Source/Images/fd1/u0/PUTDS.COM rename to Source/Images/d_bp/u0/PUTDS.COM diff --git a/Source/Images/fd0/u0/RMAC.COM b/Source/Images/d_bp/u0/RMAC.COM similarity index 100% rename from Source/Images/fd0/u0/RMAC.COM rename to Source/Images/d_bp/u0/RMAC.COM diff --git a/Source/Images/hd0/s0/u0/setclok.com b/Source/Images/d_bp/u0/SETCLOK.COM similarity index 100% rename from Source/Images/hd0/s0/u0/setclok.com rename to Source/Images/d_bp/u0/SETCLOK.COM diff --git a/Source/Images/hd0/s0/u0/showhd.com b/Source/Images/d_bp/u0/SHOWHD.COM similarity index 100% rename from Source/Images/hd0/s0/u0/showhd.com rename to Source/Images/d_bp/u0/SHOWHD.COM diff --git a/Source/Images/hd0/s0/u0/sizeram.com b/Source/Images/d_bp/u0/SIZERAM.COM similarity index 100% rename from Source/Images/hd0/s0/u0/sizeram.com rename to Source/Images/d_bp/u0/SIZERAM.COM diff --git a/Source/Images/hd0/s0/u0/slowdown.com b/Source/Images/d_bp/u0/SLOWDOWN.COM similarity index 100% rename from Source/Images/hd0/s0/u0/slowdown.com rename to Source/Images/d_bp/u0/SLOWDOWN.COM diff --git a/Source/Images/hd0/s0/u0/speedup.com b/Source/Images/d_bp/u0/SPEEDUP.COM similarity index 100% rename from Source/Images/hd0/s0/u0/speedup.com rename to Source/Images/d_bp/u0/SPEEDUP.COM diff --git a/Source/Images/hd0/s0/u0/start01.com b/Source/Images/d_bp/u0/START01.COM similarity index 100% rename from Source/Images/hd0/s0/u0/start01.com rename to Source/Images/d_bp/u0/START01.COM diff --git a/Source/Images/fd0/u0/STAT.COM b/Source/Images/d_bp/u0/STAT.COM similarity index 100% rename from Source/Images/fd0/u0/STAT.COM rename to Source/Images/d_bp/u0/STAT.COM diff --git a/Source/Images/d_bp/u0/SUBMIT.COM b/Source/Images/d_bp/u0/SUBMIT.COM new file mode 100644 index 00000000..f651bfee Binary files /dev/null and b/Source/Images/d_bp/u0/SUBMIT.COM differ diff --git a/Source/Images/fd0/u0/SUPERSUB.COM b/Source/Images/d_bp/u0/SUPERSUB.COM similarity index 100% rename from Source/Images/fd0/u0/SUPERSUB.COM rename to Source/Images/d_bp/u0/SUPERSUB.COM diff --git a/Source/Images/hd0/s0/u0/sys.fcp b/Source/Images/d_bp/u0/SYS.FCP similarity index 100% rename from Source/Images/hd0/s0/u0/sys.fcp rename to Source/Images/d_bp/u0/SYS.FCP diff --git a/Source/Images/hd0/s0/u0/sys.ndr b/Source/Images/d_bp/u0/SYS.NDR similarity index 100% rename from Source/Images/hd0/s0/u0/sys.ndr rename to Source/Images/d_bp/u0/SYS.NDR diff --git a/Source/Images/hd0/s0/u0/sys.rcp b/Source/Images/d_bp/u0/SYS.RCP similarity index 100% rename from Source/Images/hd0/s0/u0/sys.rcp rename to Source/Images/d_bp/u0/SYS.RCP diff --git a/Source/Images/hd0/s0/u0/tcselect.com b/Source/Images/d_bp/u0/TCSELECT.COM similarity index 100% rename from Source/Images/hd0/s0/u0/tcselect.com rename to Source/Images/d_bp/u0/TCSELECT.COM diff --git a/Source/Images/hd0/s0/u0/tdd.com b/Source/Images/d_bp/u0/TDD.COM similarity index 100% rename from Source/Images/hd0/s0/u0/tdd.com rename to Source/Images/d_bp/u0/TDD.COM diff --git a/Source/Images/hd0/s0/u0/turbo.com b/Source/Images/d_bp/u0/TURBO.COM similarity index 100% rename from Source/Images/hd0/s0/u0/turbo.com rename to Source/Images/d_bp/u0/TURBO.COM diff --git a/Source/Images/fd0/u0/UNCR.COM b/Source/Images/d_bp/u0/UNCR.COM similarity index 100% rename from Source/Images/fd0/u0/UNCR.COM rename to Source/Images/d_bp/u0/UNCR.COM diff --git a/Source/Images/fd0/u0/UNZIP.COM b/Source/Images/d_bp/u0/UNZIP.COM similarity index 100% rename from Source/Images/fd0/u0/UNZIP.COM rename to Source/Images/d_bp/u0/UNZIP.COM diff --git a/Source/Images/hd0/s0/u0/URL.COM b/Source/Images/d_bp/u0/URL.COM similarity index 100% rename from Source/Images/hd0/s0/u0/URL.COM rename to Source/Images/d_bp/u0/URL.COM diff --git a/Source/Images/hd0/s0/u0/valias.com b/Source/Images/d_bp/u0/VALIAS.COM similarity index 100% rename from Source/Images/hd0/s0/u0/valias.com rename to Source/Images/d_bp/u0/VALIAS.COM diff --git a/Source/Images/fd0/u0/VIDATT.Z80 b/Source/Images/d_bp/u0/VIDATT.Z80 similarity index 100% rename from Source/Images/fd0/u0/VIDATT.Z80 rename to Source/Images/d_bp/u0/VIDATT.Z80 diff --git a/Source/Images/hd0/s0/u0/VT100TCP.Z3T b/Source/Images/d_bp/u0/VT100TCP.Z3T similarity index 100% rename from Source/Images/hd0/s0/u0/VT100TCP.Z3T rename to Source/Images/d_bp/u0/VT100TCP.Z3T diff --git a/Source/Images/d_bp/u0/WW.Z3T b/Source/Images/d_bp/u0/WW.Z3T new file mode 100644 index 00000000..004cc957 Binary files /dev/null and b/Source/Images/d_bp/u0/WW.Z3T differ diff --git a/Source/Images/fd0/u0/XSUB.COM b/Source/Images/d_bp/u0/XSUB.COM similarity index 100% rename from Source/Images/fd0/u0/XSUB.COM rename to Source/Images/d_bp/u0/XSUB.COM diff --git a/Source/Images/hd0/s0/u0/z3tcap.tcp b/Source/Images/d_bp/u0/Z3TCAP.TCP similarity index 100% rename from Source/Images/hd0/s0/u0/z3tcap.tcp rename to Source/Images/d_bp/u0/Z3TCAP.TCP diff --git a/Source/Images/hd0/s0/u0/Z3TCAP.Z3T b/Source/Images/d_bp/u0/Z3TCAP.Z3T similarity index 100% rename from Source/Images/hd0/s0/u0/Z3TCAP.Z3T rename to Source/Images/d_bp/u0/Z3TCAP.Z3T diff --git a/Source/Images/fd1/u0/ZCNFG.COM b/Source/Images/d_bp/u0/ZCNFG.COM similarity index 100% rename from Source/Images/fd1/u0/ZCNFG.COM rename to Source/Images/d_bp/u0/ZCNFG.COM diff --git a/Source/Images/hd0/s0/u0/zscfg2.com b/Source/Images/d_bp/u0/ZSCFG2.COM similarity index 100% rename from Source/Images/hd0/s0/u0/zscfg2.com rename to Source/Images/d_bp/u0/ZSCFG2.COM diff --git a/Source/Images/fd1/u0/ZSCONFIG.COM b/Source/Images/d_bp/u0/ZSCONFIG.COM similarity index 100% rename from Source/Images/fd1/u0/ZSCONFIG.COM rename to Source/Images/d_bp/u0/ZSCONFIG.COM diff --git a/Source/Images/d_bp/u0/ZSID.COM b/Source/Images/d_bp/u0/ZSID.COM new file mode 100644 index 00000000..686b3775 Binary files /dev/null and b/Source/Images/d_bp/u0/ZSID.COM differ diff --git a/Source/Images/hd0/s0/u0/zxd.com b/Source/Images/d_bp/u0/ZXD.COM similarity index 100% rename from Source/Images/hd0/s0/u0/zxd.com rename to Source/Images/d_bp/u0/ZXD.COM diff --git a/Source/Images/hd0/s0/u0/cp.bin b/Source/Images/d_bp/u0/cp.bin similarity index 100% rename from Source/Images/hd0/s0/u0/cp.bin rename to Source/Images/d_bp/u0/cp.bin diff --git a/Source/Images/hd0/s0/u0/nzcpr.zrl b/Source/Images/d_bp/u0/nzcpr.zrl similarity index 100% rename from Source/Images/hd0/s0/u0/nzcpr.zrl rename to Source/Images/d_bp/u0/nzcpr.zrl diff --git a/Source/Images/hd0/s0/u0/park.com b/Source/Images/d_bp/u0/park.com similarity index 100% rename from Source/Images/hd0/s0/u0/park.com rename to Source/Images/d_bp/u0/park.com diff --git a/Source/Images/hd0/s0/u0/spinup.com b/Source/Images/d_bp/u0/spinup.com similarity index 100% rename from Source/Images/hd0/s0/u0/spinup.com rename to Source/Images/d_bp/u0/spinup.com diff --git a/Source/Images/hd0/s0/u0/start.com b/Source/Images/d_bp/u0/start.com similarity index 100% rename from Source/Images/hd0/s0/u0/start.com rename to Source/Images/d_bp/u0/start.com diff --git a/Source/Images/hd0/s0/u10/z40.hlp b/Source/Images/d_bp/u10/Z40.HLP similarity index 100% rename from Source/Images/hd0/s0/u10/z40.hlp rename to Source/Images/d_bp/u10/Z40.HLP diff --git a/Source/Images/hd0/s0/u10/z40a.hlp b/Source/Images/d_bp/u10/Z40A.HLP similarity index 100% rename from Source/Images/hd0/s0/u10/z40a.hlp rename to Source/Images/d_bp/u10/Z40A.HLP diff --git a/Source/Images/hd0/s0/u10/z40b.hlp b/Source/Images/d_bp/u10/Z40B.HLP similarity index 100% rename from Source/Images/hd0/s0/u10/z40b.hlp rename to Source/Images/d_bp/u10/Z40B.HLP diff --git a/Source/Images/hd0/s0/u10/z40c.hlp b/Source/Images/d_bp/u10/Z40C.HLP similarity index 100% rename from Source/Images/hd0/s0/u10/z40c.hlp rename to Source/Images/d_bp/u10/Z40C.HLP diff --git a/Source/Images/hd0/s0/u10/z40d.hlp b/Source/Images/d_bp/u10/Z40D.HLP similarity index 100% rename from Source/Images/hd0/s0/u10/z40d.hlp rename to Source/Images/d_bp/u10/Z40D.HLP diff --git a/Source/Images/hd0/s0/u10/z40e.hlp b/Source/Images/d_bp/u10/Z40E.HLP similarity index 100% rename from Source/Images/hd0/s0/u10/z40e.hlp rename to Source/Images/d_bp/u10/Z40E.HLP diff --git a/Source/Images/hd0/s0/u10/z40f.hlp b/Source/Images/d_bp/u10/Z40F.HLP similarity index 100% rename from Source/Images/hd0/s0/u10/z40f.hlp rename to Source/Images/d_bp/u10/Z40F.HLP diff --git a/Source/Images/hd0/s0/u10/z40g.hlp b/Source/Images/d_bp/u10/Z40G.HLP similarity index 100% rename from Source/Images/hd0/s0/u10/z40g.hlp rename to Source/Images/d_bp/u10/Z40G.HLP diff --git a/Source/Images/hd0/s0/u14/bpcnfg.cfg b/Source/Images/d_bp/u14/BPCNFG.CFG similarity index 100% rename from Source/Images/hd0/s0/u14/bpcnfg.cfg rename to Source/Images/d_bp/u14/BPCNFG.CFG diff --git a/Source/Images/hd0/s0/u14/bpformat.cfg b/Source/Images/d_bp/u14/BPFORMAT.CFG similarity index 100% rename from Source/Images/hd0/s0/u14/bpformat.cfg rename to Source/Images/d_bp/u14/BPFORMAT.CFG diff --git a/Source/Images/hd0/s0/u14/copy.cfg b/Source/Images/d_bp/u14/COPY.CFG similarity index 100% rename from Source/Images/hd0/s0/u14/copy.cfg rename to Source/Images/d_bp/u14/COPY.CFG diff --git a/Source/Images/hd0/s0/u14/emulate.cfg b/Source/Images/d_bp/u14/EMULATE.CFG similarity index 100% rename from Source/Images/hd0/s0/u14/emulate.cfg rename to Source/Images/d_bp/u14/EMULATE.CFG diff --git a/Source/Images/hd0/s0/u14/hdiag.cfg b/Source/Images/d_bp/u14/HDIAG.CFG similarity index 100% rename from Source/Images/hd0/s0/u14/hdiag.cfg rename to Source/Images/d_bp/u14/HDIAG.CFG diff --git a/Source/Images/hd0/s0/u14/iniramd.cfg b/Source/Images/d_bp/u14/INIRAMD.CFG similarity index 100% rename from Source/Images/hd0/s0/u14/iniramd.cfg rename to Source/Images/d_bp/u14/INIRAMD.CFG diff --git a/Source/Images/fd1/u0/INITDIR.CFG b/Source/Images/d_bp/u14/INITDIR.CFG similarity index 100% rename from Source/Images/fd1/u0/INITDIR.CFG rename to Source/Images/d_bp/u14/INITDIR.CFG diff --git a/Source/Images/hd0/s0/u14/ldsys.cfg b/Source/Images/d_bp/u14/LDSYS.CFG similarity index 100% rename from Source/Images/hd0/s0/u14/ldsys.cfg rename to Source/Images/d_bp/u14/LDSYS.CFG diff --git a/Source/Images/hd0/s0/u14/setclok.cfg b/Source/Images/d_bp/u14/SETCLOK.CFG similarity index 100% rename from Source/Images/hd0/s0/u14/setclok.cfg rename to Source/Images/d_bp/u14/SETCLOK.CFG diff --git a/Source/Images/hd0/s0/u14/tdd.cfg b/Source/Images/d_bp/u14/TDD.CFG similarity index 100% rename from Source/Images/hd0/s0/u14/tdd.cfg rename to Source/Images/d_bp/u14/TDD.CFG diff --git a/Source/Images/fd1/u0/ZXD.CFG b/Source/Images/d_bp/u14/ZXD.CFG similarity index 100% rename from Source/Images/fd1/u0/ZXD.CFG rename to Source/Images/d_bp/u14/ZXD.CFG diff --git a/Source/Images/hd0/s0/u15/00-INDEX.TXT b/Source/Images/d_bp/u15/00-INDEX.TXT similarity index 100% rename from Source/Images/hd0/s0/u15/00-INDEX.TXT rename to Source/Images/d_bp/u15/00-INDEX.TXT diff --git a/Source/Images/hd0/s0/u15/ALIAS-1.LBR b/Source/Images/d_bp/u15/ALIAS-1.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/ALIAS-1.LBR rename to Source/Images/d_bp/u15/ALIAS-1.LBR diff --git a/Source/Images/hd0/s0/u15/ALIAS-2.LBR b/Source/Images/d_bp/u15/ALIAS-2.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/ALIAS-2.LBR rename to Source/Images/d_bp/u15/ALIAS-2.LBR diff --git a/Source/Images/hd0/s0/u15/ALIAS.COM b/Source/Images/d_bp/u15/ALIAS.COM similarity index 100% rename from Source/Images/hd0/s0/u15/ALIAS.COM rename to Source/Images/d_bp/u15/ALIAS.COM diff --git a/Source/Images/hd0/s0/u15/ALIAS.HLP b/Source/Images/d_bp/u15/ALIAS.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/ALIAS.HLP rename to Source/Images/d_bp/u15/ALIAS.HLP diff --git a/Source/Images/hd0/s0/u15/ALIAS.ZEX b/Source/Images/d_bp/u15/ALIAS.ZEX similarity index 100% rename from Source/Images/hd0/s0/u15/ALIAS.ZEX rename to Source/Images/d_bp/u15/ALIAS.ZEX diff --git a/Source/Images/hd0/s0/u15/ALIAS0.MAC b/Source/Images/d_bp/u15/ALIAS0.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/ALIAS0.MAC rename to Source/Images/d_bp/u15/ALIAS0.MAC diff --git a/Source/Images/hd0/s0/u15/ALIAS1.MAC b/Source/Images/d_bp/u15/ALIAS1.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/ALIAS1.MAC rename to Source/Images/d_bp/u15/ALIAS1.MAC diff --git a/Source/Images/hd0/s0/u15/CD.COM b/Source/Images/d_bp/u15/CD.COM similarity index 100% rename from Source/Images/hd0/s0/u15/CD.COM rename to Source/Images/d_bp/u15/CD.COM diff --git a/Source/Images/hd0/s0/u15/CD.MAC b/Source/Images/d_bp/u15/CD.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/CD.MAC rename to Source/Images/d_bp/u15/CD.MAC diff --git a/Source/Images/hd0/s0/u15/CLEAND15.LBR b/Source/Images/d_bp/u15/CLEAND15.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/CLEAND15.LBR rename to Source/Images/d_bp/u15/CLEAND15.LBR diff --git a/Source/Images/hd0/s0/u15/CLEANDIR.COM b/Source/Images/d_bp/u15/CLEANDIR.COM similarity index 100% rename from Source/Images/hd0/s0/u15/CLEANDIR.COM rename to Source/Images/d_bp/u15/CLEANDIR.COM diff --git a/Source/Images/hd0/s0/u15/CLEANDIR.MAC b/Source/Images/d_bp/u15/CLEANDIR.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/CLEANDIR.MAC rename to Source/Images/d_bp/u15/CLEANDIR.MAC diff --git a/Source/Images/hd0/s0/u15/CLNDR13B.LBR b/Source/Images/d_bp/u15/CLNDR13B.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/CLNDR13B.LBR rename to Source/Images/d_bp/u15/CLNDR13B.LBR diff --git a/Source/Images/hd0/s0/u15/CMD.COM b/Source/Images/d_bp/u15/CMD.COM similarity index 100% rename from Source/Images/hd0/s0/u15/CMD.COM rename to Source/Images/d_bp/u15/CMD.COM diff --git a/Source/Images/hd0/s0/u15/CMD.MAC b/Source/Images/d_bp/u15/CMD.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/CMD.MAC rename to Source/Images/d_bp/u15/CMD.MAC diff --git a/Source/Images/hd0/s0/u15/CMDFILES.HLP b/Source/Images/d_bp/u15/CMDFILES.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/CMDFILES.HLP rename to Source/Images/d_bp/u15/CMDFILES.HLP diff --git a/Source/Images/hd0/s0/u15/CMDRUN.COM b/Source/Images/d_bp/u15/CMDRUN.COM similarity index 100% rename from Source/Images/hd0/s0/u15/CMDRUN.COM rename to Source/Images/d_bp/u15/CMDRUN.COM diff --git a/Source/Images/hd0/s0/u15/CMDRUN.MAC b/Source/Images/d_bp/u15/CMDRUN.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/CMDRUN.MAC rename to Source/Images/d_bp/u15/CMDRUN.MAC diff --git a/Source/Images/hd0/s0/u15/COMMENT.COM b/Source/Images/d_bp/u15/COMMENT.COM similarity index 100% rename from Source/Images/hd0/s0/u15/COMMENT.COM rename to Source/Images/d_bp/u15/COMMENT.COM diff --git a/Source/Images/hd0/s0/u15/COMMENT.MAC b/Source/Images/d_bp/u15/COMMENT.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/COMMENT.MAC rename to Source/Images/d_bp/u15/COMMENT.MAC diff --git a/Source/Images/hd0/s0/u15/COMPARE.HLP b/Source/Images/d_bp/u15/COMPARE.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/COMPARE.HLP rename to Source/Images/d_bp/u15/COMPARE.HLP diff --git a/Source/Images/hd0/s0/u15/CPSEL.COM b/Source/Images/d_bp/u15/CPSEL.COM similarity index 100% rename from Source/Images/hd0/s0/u15/CPSEL.COM rename to Source/Images/d_bp/u15/CPSEL.COM diff --git a/Source/Images/hd0/s0/u15/CPSEL.MAC b/Source/Images/d_bp/u15/CPSEL.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/CPSEL.MAC rename to Source/Images/d_bp/u15/CPSEL.MAC diff --git a/Source/Images/hd0/s0/u15/CRC.COM b/Source/Images/d_bp/u15/CRC.COM similarity index 100% rename from Source/Images/hd0/s0/u15/CRC.COM rename to Source/Images/d_bp/u15/CRC.COM diff --git a/Source/Images/hd0/s0/u15/CRC.MAC b/Source/Images/d_bp/u15/CRC.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/CRC.MAC rename to Source/Images/d_bp/u15/CRC.MAC diff --git a/Source/Images/hd0/s0/u15/DBRCPREL.LBR b/Source/Images/d_bp/u15/DBRCPREL.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/DBRCPREL.LBR rename to Source/Images/d_bp/u15/DBRCPREL.LBR diff --git a/Source/Images/hd0/s0/u15/DEBUGRCP.AQM b/Source/Images/d_bp/u15/DEBUGRCP.AQM similarity index 100% rename from Source/Images/hd0/s0/u15/DEBUGRCP.AQM rename to Source/Images/d_bp/u15/DEBUGRCP.AQM diff --git a/Source/Images/hd0/s0/u15/DEBUGRCP.ASM b/Source/Images/d_bp/u15/DEBUGRCP.ASM similarity index 100% rename from Source/Images/hd0/s0/u15/DEBUGRCP.ASM rename to Source/Images/d_bp/u15/DEBUGRCP.ASM diff --git a/Source/Images/hd0/s0/u15/DEV.COM b/Source/Images/d_bp/u15/DEV.COM similarity index 100% rename from Source/Images/hd0/s0/u15/DEV.COM rename to Source/Images/d_bp/u15/DEV.COM diff --git a/Source/Images/hd0/s0/u15/DEV.MAC b/Source/Images/d_bp/u15/DEV.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/DEV.MAC rename to Source/Images/d_bp/u15/DEV.MAC diff --git a/Source/Images/hd0/s0/u15/DEV10.LBR b/Source/Images/d_bp/u15/DEV10.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/DEV10.LBR rename to Source/Images/d_bp/u15/DEV10.LBR diff --git a/Source/Images/hd0/s0/u15/DEVICE.COM b/Source/Images/d_bp/u15/DEVICE.COM similarity index 100% rename from Source/Images/hd0/s0/u15/DEVICE.COM rename to Source/Images/d_bp/u15/DEVICE.COM diff --git a/Source/Images/hd0/s0/u15/DEVICE.MAC b/Source/Images/d_bp/u15/DEVICE.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/DEVICE.MAC rename to Source/Images/d_bp/u15/DEVICE.MAC diff --git a/Source/Images/hd0/s0/u15/DEVICE10.LBR b/Source/Images/d_bp/u15/DEVICE10.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/DEVICE10.LBR rename to Source/Images/d_bp/u15/DEVICE10.LBR diff --git a/Source/Images/hd0/s0/u15/DIFF.COM b/Source/Images/d_bp/u15/DIFF.COM similarity index 100% rename from Source/Images/hd0/s0/u15/DIFF.COM rename to Source/Images/d_bp/u15/DIFF.COM diff --git a/Source/Images/hd0/s0/u15/DIFF.MAC b/Source/Images/d_bp/u15/DIFF.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/DIFF.MAC rename to Source/Images/d_bp/u15/DIFF.MAC diff --git a/Source/Images/hd0/s0/u15/DIFF21.LBR b/Source/Images/d_bp/u15/DIFF21.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/DIFF21.LBR rename to Source/Images/d_bp/u15/DIFF21.LBR diff --git a/Source/Images/hd0/s0/u15/DIR.COM b/Source/Images/d_bp/u15/DIR.COM similarity index 100% rename from Source/Images/hd0/s0/u15/DIR.COM rename to Source/Images/d_bp/u15/DIR.COM diff --git a/Source/Images/hd0/s0/u15/DIR.MAC b/Source/Images/d_bp/u15/DIR.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/DIR.MAC rename to Source/Images/d_bp/u15/DIR.MAC diff --git a/Source/Images/hd0/s0/u15/DIR14.LBR b/Source/Images/d_bp/u15/DIR14.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/DIR14.LBR rename to Source/Images/d_bp/u15/DIR14.LBR diff --git a/Source/Images/hd0/s0/u15/DIRS.HLP b/Source/Images/d_bp/u15/DIRS.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/DIRS.HLP rename to Source/Images/d_bp/u15/DIRS.HLP diff --git a/Source/Images/hd0/s0/u15/DPGFILE.LBR b/Source/Images/d_bp/u15/DPGFILE.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/DPGFILE.LBR rename to Source/Images/d_bp/u15/DPGFILE.LBR diff --git a/Source/Images/hd0/s0/u15/DPROG.COM b/Source/Images/d_bp/u15/DPROG.COM similarity index 100% rename from Source/Images/hd0/s0/u15/DPROG.COM rename to Source/Images/d_bp/u15/DPROG.COM diff --git a/Source/Images/hd0/s0/u15/DPROG.HLP b/Source/Images/d_bp/u15/DPROG.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/DPROG.HLP rename to Source/Images/d_bp/u15/DPROG.HLP diff --git a/Source/Images/hd0/s0/u15/DPROG.MAC b/Source/Images/d_bp/u15/DPROG.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/DPROG.MAC rename to Source/Images/d_bp/u15/DPROG.MAC diff --git a/Source/Images/hd0/s0/u15/DPROG12.LBR b/Source/Images/d_bp/u15/DPROG12.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/DPROG12.LBR rename to Source/Images/d_bp/u15/DPROG12.LBR diff --git a/Source/Images/hd0/s0/u15/DU3.COM b/Source/Images/d_bp/u15/DU3.COM similarity index 100% rename from Source/Images/hd0/s0/u15/DU3.COM rename to Source/Images/d_bp/u15/DU3.COM diff --git a/Source/Images/hd0/s0/u15/DU3.HLP b/Source/Images/d_bp/u15/DU3.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/DU3.HLP rename to Source/Images/d_bp/u15/DU3.HLP diff --git a/Source/Images/hd0/s0/u15/DU3.MAC b/Source/Images/d_bp/u15/DU3.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/DU3.MAC rename to Source/Images/d_bp/u15/DU3.MAC diff --git a/Source/Images/hd0/s0/u15/DU312.LBR b/Source/Images/d_bp/u15/DU312.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/DU312.LBR rename to Source/Images/d_bp/u15/DU312.LBR diff --git a/Source/Images/hd0/s0/u15/ECHO.COM b/Source/Images/d_bp/u15/ECHO.COM similarity index 100% rename from Source/Images/hd0/s0/u15/ECHO.COM rename to Source/Images/d_bp/u15/ECHO.COM diff --git a/Source/Images/hd0/s0/u15/ECHO.MAC b/Source/Images/d_bp/u15/ECHO.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/ECHO.MAC rename to Source/Images/d_bp/u15/ECHO.MAC diff --git a/Source/Images/hd0/s0/u15/ERASE.COM b/Source/Images/d_bp/u15/ERASE.COM similarity index 100% rename from Source/Images/hd0/s0/u15/ERASE.COM rename to Source/Images/d_bp/u15/ERASE.COM diff --git a/Source/Images/hd0/s0/u15/ERASE.MAC b/Source/Images/d_bp/u15/ERASE.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/ERASE.MAC rename to Source/Images/d_bp/u15/ERASE.MAC diff --git a/Source/Images/hd0/s0/u15/ERROR1.COM b/Source/Images/d_bp/u15/ERROR1.COM similarity index 100% rename from Source/Images/hd0/s0/u15/ERROR1.COM rename to Source/Images/d_bp/u15/ERROR1.COM diff --git a/Source/Images/hd0/s0/u15/ERROR1.MAC b/Source/Images/d_bp/u15/ERROR1.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/ERROR1.MAC rename to Source/Images/d_bp/u15/ERROR1.MAC diff --git a/Source/Images/hd0/s0/u15/ERROR2.COM b/Source/Images/d_bp/u15/ERROR2.COM similarity index 100% rename from Source/Images/hd0/s0/u15/ERROR2.COM rename to Source/Images/d_bp/u15/ERROR2.COM diff --git a/Source/Images/hd0/s0/u15/ERROR2.MAC b/Source/Images/d_bp/u15/ERROR2.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/ERROR2.MAC rename to Source/Images/d_bp/u15/ERROR2.MAC diff --git a/Source/Images/hd0/s0/u15/ERROR3.COM b/Source/Images/d_bp/u15/ERROR3.COM similarity index 100% rename from Source/Images/hd0/s0/u15/ERROR3.COM rename to Source/Images/d_bp/u15/ERROR3.COM diff --git a/Source/Images/hd0/s0/u15/ERROR3.MAC b/Source/Images/d_bp/u15/ERROR3.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/ERROR3.MAC rename to Source/Images/d_bp/u15/ERROR3.MAC diff --git a/Source/Images/hd0/s0/u15/ERROR4.COM b/Source/Images/d_bp/u15/ERROR4.COM similarity index 100% rename from Source/Images/hd0/s0/u15/ERROR4.COM rename to Source/Images/d_bp/u15/ERROR4.COM diff --git a/Source/Images/hd0/s0/u15/ERROR4.MAC b/Source/Images/d_bp/u15/ERROR4.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/ERROR4.MAC rename to Source/Images/d_bp/u15/ERROR4.MAC diff --git a/Source/Images/hd0/s0/u15/ERRORX.COM b/Source/Images/d_bp/u15/ERRORX.COM similarity index 100% rename from Source/Images/hd0/s0/u15/ERRORX.COM rename to Source/Images/d_bp/u15/ERRORX.COM diff --git a/Source/Images/hd0/s0/u15/ERRORX.MAC b/Source/Images/d_bp/u15/ERRORX.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/ERRORX.MAC rename to Source/Images/d_bp/u15/ERRORX.MAC diff --git a/Source/Images/hd0/s0/u15/FILES.BBS b/Source/Images/d_bp/u15/FILES.BBS similarity index 100% rename from Source/Images/hd0/s0/u15/FILES.BBS rename to Source/Images/d_bp/u15/FILES.BBS diff --git a/Source/Images/hd0/s0/u15/FINDF.COM b/Source/Images/d_bp/u15/FINDF.COM similarity index 100% rename from Source/Images/hd0/s0/u15/FINDF.COM rename to Source/Images/d_bp/u15/FINDF.COM diff --git a/Source/Images/hd0/s0/u15/FINDF.MAC b/Source/Images/d_bp/u15/FINDF.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/FINDF.MAC rename to Source/Images/d_bp/u15/FINDF.MAC diff --git a/Source/Images/hd0/s0/u15/FINDF26.LBR b/Source/Images/d_bp/u15/FINDF26.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/FINDF26.LBR rename to Source/Images/d_bp/u15/FINDF26.LBR diff --git a/Source/Images/hd0/s0/u15/FINDF26A.LBR b/Source/Images/d_bp/u15/FINDF26A.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/FINDF26A.LBR rename to Source/Images/d_bp/u15/FINDF26A.LBR diff --git a/Source/Images/hd0/s0/u15/GOTO.COM b/Source/Images/d_bp/u15/GOTO.COM similarity index 100% rename from Source/Images/hd0/s0/u15/GOTO.COM rename to Source/Images/d_bp/u15/GOTO.COM diff --git a/Source/Images/hd0/s0/u15/GOTO.MAC b/Source/Images/d_bp/u15/GOTO.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/GOTO.MAC rename to Source/Images/d_bp/u15/GOTO.MAC diff --git a/Source/Images/hd0/s0/u15/GRDEMO.LBR b/Source/Images/d_bp/u15/GRDEMO.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/GRDEMO.LBR rename to Source/Images/d_bp/u15/GRDEMO.LBR diff --git a/Source/Images/hd0/s0/u15/HELP.COM b/Source/Images/d_bp/u15/HELP.COM similarity index 100% rename from Source/Images/hd0/s0/u15/HELP.COM rename to Source/Images/d_bp/u15/HELP.COM diff --git a/Source/Images/hd0/s0/u15/HELP.HLP b/Source/Images/d_bp/u15/HELP.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/HELP.HLP rename to Source/Images/d_bp/u15/HELP.HLP diff --git a/Source/Images/hd0/s0/u15/HELP.MAC b/Source/Images/d_bp/u15/HELP.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/HELP.MAC rename to Source/Images/d_bp/u15/HELP.MAC diff --git a/Source/Images/hd0/s0/u15/HELPCK.COM b/Source/Images/d_bp/u15/HELPCK.COM similarity index 100% rename from Source/Images/hd0/s0/u15/HELPCK.COM rename to Source/Images/d_bp/u15/HELPCK.COM diff --git a/Source/Images/hd0/s0/u15/HELPCK.MAC b/Source/Images/d_bp/u15/HELPCK.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/HELPCK.MAC rename to Source/Images/d_bp/u15/HELPCK.MAC diff --git a/Source/Images/hd0/s0/u15/HELPPR.COM b/Source/Images/d_bp/u15/HELPPR.COM similarity index 100% rename from Source/Images/hd0/s0/u15/HELPPR.COM rename to Source/Images/d_bp/u15/HELPPR.COM diff --git a/Source/Images/hd0/s0/u15/HELPPR.MAC b/Source/Images/d_bp/u15/HELPPR.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/HELPPR.MAC rename to Source/Images/d_bp/u15/HELPPR.MAC diff --git a/Source/Images/hd0/s0/u15/HELPSYS.HLP b/Source/Images/d_bp/u15/HELPSYS.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/HELPSYS.HLP rename to Source/Images/d_bp/u15/HELPSYS.HLP diff --git a/Source/Images/hd0/s0/u15/IF.COM b/Source/Images/d_bp/u15/IF.COM similarity index 100% rename from Source/Images/hd0/s0/u15/IF.COM rename to Source/Images/d_bp/u15/IF.COM diff --git a/Source/Images/hd0/s0/u15/IF.HLP b/Source/Images/d_bp/u15/IF.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/IF.HLP rename to Source/Images/d_bp/u15/IF.HLP diff --git a/Source/Images/hd0/s0/u15/IF.MAC b/Source/Images/d_bp/u15/IF.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/IF.MAC rename to Source/Images/d_bp/u15/IF.MAC diff --git a/Source/Images/hd0/s0/u15/IFSTAT.COM b/Source/Images/d_bp/u15/IFSTAT.COM similarity index 100% rename from Source/Images/hd0/s0/u15/IFSTAT.COM rename to Source/Images/d_bp/u15/IFSTAT.COM diff --git a/Source/Images/hd0/s0/u15/IFSTAT.MAC b/Source/Images/d_bp/u15/IFSTAT.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/IFSTAT.MAC rename to Source/Images/d_bp/u15/IFSTAT.MAC diff --git a/Source/Images/hd0/s0/u15/IO.HLP b/Source/Images/d_bp/u15/IO.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/IO.HLP rename to Source/Images/d_bp/u15/IO.HLP diff --git a/Source/Images/hd0/s0/u15/LDR-UPD.COM b/Source/Images/d_bp/u15/LDR-UPD.COM similarity index 100% rename from Source/Images/hd0/s0/u15/LDR-UPD.COM rename to Source/Images/d_bp/u15/LDR-UPD.COM diff --git a/Source/Images/hd0/s0/u15/LDR-UPD.MSG b/Source/Images/d_bp/u15/LDR-UPD.MSG similarity index 100% rename from Source/Images/hd0/s0/u15/LDR-UPD.MSG rename to Source/Images/d_bp/u15/LDR-UPD.MSG diff --git a/Source/Images/hd0/s0/u15/LDR.COM b/Source/Images/d_bp/u15/LDR.COM similarity index 100% rename from Source/Images/hd0/s0/u15/LDR.COM rename to Source/Images/d_bp/u15/LDR.COM diff --git a/Source/Images/hd0/s0/u15/LDR.MAC b/Source/Images/d_bp/u15/LDR.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/LDR.MAC rename to Source/Images/d_bp/u15/LDR.MAC diff --git a/Source/Images/hd0/s0/u15/LDR15.LBR b/Source/Images/d_bp/u15/LDR15.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/LDR15.LBR rename to Source/Images/d_bp/u15/LDR15.LBR diff --git a/Source/Images/hd0/s0/u15/LGET11.LBR b/Source/Images/d_bp/u15/LGET11.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/LGET11.LBR rename to Source/Images/d_bp/u15/LGET11.LBR diff --git a/Source/Images/hd0/s0/u15/LLF11.LBR b/Source/Images/d_bp/u15/LLF11.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/LLF11.LBR rename to Source/Images/d_bp/u15/LLF11.LBR diff --git a/Source/Images/hd0/s0/u15/LUZ3.LBR b/Source/Images/d_bp/u15/LUZ3.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/LUZ3.LBR rename to Source/Images/d_bp/u15/LUZ3.LBR diff --git a/Source/Images/hd0/s0/u15/LX12.LBR b/Source/Images/d_bp/u15/LX12.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/LX12.LBR rename to Source/Images/d_bp/u15/LX12.LBR diff --git a/Source/Images/hd0/s0/u15/LX14.LBR b/Source/Images/d_bp/u15/LX14.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/LX14.LBR rename to Source/Images/d_bp/u15/LX14.LBR diff --git a/Source/Images/hd0/s0/u15/MCOPY.COM b/Source/Images/d_bp/u15/MCOPY.COM similarity index 100% rename from Source/Images/hd0/s0/u15/MCOPY.COM rename to Source/Images/d_bp/u15/MCOPY.COM diff --git a/Source/Images/hd0/s0/u15/MCOPY.MAC b/Source/Images/d_bp/u15/MCOPY.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/MCOPY.MAC rename to Source/Images/d_bp/u15/MCOPY.MAC diff --git a/Source/Images/hd0/s0/u15/MCOPY44.LBR b/Source/Images/d_bp/u15/MCOPY44.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/MCOPY44.LBR rename to Source/Images/d_bp/u15/MCOPY44.LBR diff --git a/Source/Images/hd0/s0/u15/MCOPY45B.LBR b/Source/Images/d_bp/u15/MCOPY45B.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/MCOPY45B.LBR rename to Source/Images/d_bp/u15/MCOPY45B.LBR diff --git a/Source/Images/hd0/s0/u15/MENU.COM b/Source/Images/d_bp/u15/MENU.COM similarity index 100% rename from Source/Images/hd0/s0/u15/MENU.COM rename to Source/Images/d_bp/u15/MENU.COM diff --git a/Source/Images/hd0/s0/u15/MENU.HLP b/Source/Images/d_bp/u15/MENU.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/MENU.HLP rename to Source/Images/d_bp/u15/MENU.HLP diff --git a/Source/Images/hd0/s0/u15/MENU.MAC b/Source/Images/d_bp/u15/MENU.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/MENU.MAC rename to Source/Images/d_bp/u15/MENU.MAC diff --git a/Source/Images/hd0/s0/u15/MENUCK.COM b/Source/Images/d_bp/u15/MENUCK.COM similarity index 100% rename from Source/Images/hd0/s0/u15/MENUCK.COM rename to Source/Images/d_bp/u15/MENUCK.COM diff --git a/Source/Images/hd0/s0/u15/MENUCK.MAC b/Source/Images/d_bp/u15/MENUCK.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/MENUCK.MAC rename to Source/Images/d_bp/u15/MENUCK.MAC diff --git a/Source/Images/hd0/s0/u15/MKDIR.COM b/Source/Images/d_bp/u15/MKDIR.COM similarity index 100% rename from Source/Images/hd0/s0/u15/MKDIR.COM rename to Source/Images/d_bp/u15/MKDIR.COM diff --git a/Source/Images/hd0/s0/u15/MKDIR.MAC b/Source/Images/d_bp/u15/MKDIR.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/MKDIR.MAC rename to Source/Images/d_bp/u15/MKDIR.MAC diff --git a/Source/Images/hd0/s0/u15/MU3.COM b/Source/Images/d_bp/u15/MU3.COM similarity index 100% rename from Source/Images/hd0/s0/u15/MU3.COM rename to Source/Images/d_bp/u15/MU3.COM diff --git a/Source/Images/hd0/s0/u15/MU3.HLP b/Source/Images/d_bp/u15/MU3.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/MU3.HLP rename to Source/Images/d_bp/u15/MU3.HLP diff --git a/Source/Images/hd0/s0/u15/MU3.MAC b/Source/Images/d_bp/u15/MU3.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/MU3.MAC rename to Source/Images/d_bp/u15/MU3.MAC diff --git a/Source/Images/hd0/s0/u15/NDIRS.HLP b/Source/Images/d_bp/u15/NDIRS.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/NDIRS.HLP rename to Source/Images/d_bp/u15/NDIRS.HLP diff --git a/Source/Images/hd0/s0/u15/NEWTCAP.Z80 b/Source/Images/d_bp/u15/NEWTCAP.Z80 similarity index 100% rename from Source/Images/hd0/s0/u15/NEWTCAP.Z80 rename to Source/Images/d_bp/u15/NEWTCAP.Z80 diff --git a/Source/Images/hd0/s0/u15/NOTE.COM b/Source/Images/d_bp/u15/NOTE.COM similarity index 100% rename from Source/Images/hd0/s0/u15/NOTE.COM rename to Source/Images/d_bp/u15/NOTE.COM diff --git a/Source/Images/hd0/s0/u15/NOTE.MAC b/Source/Images/d_bp/u15/NOTE.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/NOTE.MAC rename to Source/Images/d_bp/u15/NOTE.MAC diff --git a/Source/Images/hd0/s0/u15/PAGE.COM b/Source/Images/d_bp/u15/PAGE.COM similarity index 100% rename from Source/Images/hd0/s0/u15/PAGE.COM rename to Source/Images/d_bp/u15/PAGE.COM diff --git a/Source/Images/hd0/s0/u15/PAGE.MAC b/Source/Images/d_bp/u15/PAGE.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/PAGE.MAC rename to Source/Images/d_bp/u15/PAGE.MAC diff --git a/Source/Images/hd0/s0/u15/PATH.COM b/Source/Images/d_bp/u15/PATH.COM similarity index 100% rename from Source/Images/hd0/s0/u15/PATH.COM rename to Source/Images/d_bp/u15/PATH.COM diff --git a/Source/Images/hd0/s0/u15/PATH.MAC b/Source/Images/d_bp/u15/PATH.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/PATH.MAC rename to Source/Images/d_bp/u15/PATH.MAC diff --git a/Source/Images/hd0/s0/u15/PATH31.LBR b/Source/Images/d_bp/u15/PATH31.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/PATH31.LBR rename to Source/Images/d_bp/u15/PATH31.LBR diff --git a/Source/Images/hd0/s0/u15/PPAL.DOC b/Source/Images/d_bp/u15/PPAL.DOC similarity index 100% rename from Source/Images/hd0/s0/u15/PPAL.DOC rename to Source/Images/d_bp/u15/PPAL.DOC diff --git a/Source/Images/hd0/s0/u15/PRINT.COM b/Source/Images/d_bp/u15/PRINT.COM similarity index 100% rename from Source/Images/hd0/s0/u15/PRINT.COM rename to Source/Images/d_bp/u15/PRINT.COM diff --git a/Source/Images/hd0/s0/u15/PRINT.MAC b/Source/Images/d_bp/u15/PRINT.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/PRINT.MAC rename to Source/Images/d_bp/u15/PRINT.MAC diff --git a/Source/Images/hd0/s0/u15/PRINTHLP.LBR b/Source/Images/d_bp/u15/PRINTHLP.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/PRINTHLP.LBR rename to Source/Images/d_bp/u15/PRINTHLP.LBR diff --git a/Source/Images/hd0/s0/u15/PROTECT.COM b/Source/Images/d_bp/u15/PROTECT.COM similarity index 100% rename from Source/Images/hd0/s0/u15/PROTECT.COM rename to Source/Images/d_bp/u15/PROTECT.COM diff --git a/Source/Images/hd0/s0/u15/PROTECT.MAC b/Source/Images/d_bp/u15/PROTECT.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/PROTECT.MAC rename to Source/Images/d_bp/u15/PROTECT.MAC diff --git a/Source/Images/hd0/s0/u15/PWD.COM b/Source/Images/d_bp/u15/PWD.COM similarity index 100% rename from Source/Images/hd0/s0/u15/PWD.COM rename to Source/Images/d_bp/u15/PWD.COM diff --git a/Source/Images/hd0/s0/u15/PWD.MAC b/Source/Images/d_bp/u15/PWD.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/PWD.MAC rename to Source/Images/d_bp/u15/PWD.MAC diff --git a/Source/Images/hd0/s0/u15/QUIET.COM b/Source/Images/d_bp/u15/QUIET.COM similarity index 100% rename from Source/Images/hd0/s0/u15/QUIET.COM rename to Source/Images/d_bp/u15/QUIET.COM diff --git a/Source/Images/hd0/s0/u15/QUIET.MAC b/Source/Images/d_bp/u15/QUIET.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/QUIET.MAC rename to Source/Images/d_bp/u15/QUIET.MAC diff --git a/Source/Images/hd0/s0/u15/READ.ME b/Source/Images/d_bp/u15/READ.ME similarity index 100% rename from Source/Images/hd0/s0/u15/READ.ME rename to Source/Images/d_bp/u15/READ.ME diff --git a/Source/Images/hd0/s0/u15/RECORD.COM b/Source/Images/d_bp/u15/RECORD.COM similarity index 100% rename from Source/Images/hd0/s0/u15/RECORD.COM rename to Source/Images/d_bp/u15/RECORD.COM diff --git a/Source/Images/hd0/s0/u15/RECORD.MAC b/Source/Images/d_bp/u15/RECORD.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/RECORD.MAC rename to Source/Images/d_bp/u15/RECORD.MAC diff --git a/Source/Images/hd0/s0/u15/REG.COM b/Source/Images/d_bp/u15/REG.COM similarity index 100% rename from Source/Images/hd0/s0/u15/REG.COM rename to Source/Images/d_bp/u15/REG.COM diff --git a/Source/Images/hd0/s0/u15/REG.MAC b/Source/Images/d_bp/u15/REG.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/REG.MAC rename to Source/Images/d_bp/u15/REG.MAC diff --git a/Source/Images/hd0/s0/u15/RENAME.COM b/Source/Images/d_bp/u15/RENAME.COM similarity index 100% rename from Source/Images/hd0/s0/u15/RENAME.COM rename to Source/Images/d_bp/u15/RENAME.COM diff --git a/Source/Images/hd0/s0/u15/RENAME.MAC b/Source/Images/d_bp/u15/RENAME.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/RENAME.MAC rename to Source/Images/d_bp/u15/RENAME.MAC diff --git a/Source/Images/hd0/s0/u15/RENAME31.LBR b/Source/Images/d_bp/u15/RENAME31.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/RENAME31.LBR rename to Source/Images/d_bp/u15/RENAME31.LBR diff --git a/Source/Images/hd0/s0/u15/SAK.COM b/Source/Images/d_bp/u15/SAK.COM similarity index 100% rename from Source/Images/hd0/s0/u15/SAK.COM rename to Source/Images/d_bp/u15/SAK.COM diff --git a/Source/Images/hd0/s0/u15/SAK.MAC b/Source/Images/d_bp/u15/SAK.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/SAK.MAC rename to Source/Images/d_bp/u15/SAK.MAC diff --git a/Source/Images/hd0/s0/u15/SETFILE.COM b/Source/Images/d_bp/u15/SETFILE.COM similarity index 100% rename from Source/Images/hd0/s0/u15/SETFILE.COM rename to Source/Images/d_bp/u15/SETFILE.COM diff --git a/Source/Images/hd0/s0/u15/SETFILE.MAC b/Source/Images/d_bp/u15/SETFILE.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/SETFILE.MAC rename to Source/Images/d_bp/u15/SETFILE.MAC diff --git a/Source/Images/hd0/s0/u15/SF.COM b/Source/Images/d_bp/u15/SF.COM similarity index 100% rename from Source/Images/hd0/s0/u15/SF.COM rename to Source/Images/d_bp/u15/SF.COM diff --git a/Source/Images/hd0/s0/u15/SF.Z80 b/Source/Images/d_bp/u15/SF.Z80 similarity index 100% rename from Source/Images/hd0/s0/u15/SF.Z80 rename to Source/Images/d_bp/u15/SF.Z80 diff --git a/Source/Images/hd0/s0/u15/SH.COM b/Source/Images/d_bp/u15/SH.COM similarity index 100% rename from Source/Images/hd0/s0/u15/SH.COM rename to Source/Images/d_bp/u15/SH.COM diff --git a/Source/Images/hd0/s0/u15/SH.HLP b/Source/Images/d_bp/u15/SH.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/SH.HLP rename to Source/Images/d_bp/u15/SH.HLP diff --git a/Source/Images/hd0/s0/u15/SH.MAC b/Source/Images/d_bp/u15/SH.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/SH.MAC rename to Source/Images/d_bp/u15/SH.MAC diff --git a/Source/Images/hd0/s0/u15/SH11.LBR b/Source/Images/d_bp/u15/SH11.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/SH11.LBR rename to Source/Images/d_bp/u15/SH11.LBR diff --git a/Source/Images/hd0/s0/u15/SHCTRL.COM b/Source/Images/d_bp/u15/SHCTRL.COM similarity index 100% rename from Source/Images/hd0/s0/u15/SHCTRL.COM rename to Source/Images/d_bp/u15/SHCTRL.COM diff --git a/Source/Images/hd0/s0/u15/SHCTRL.MAC b/Source/Images/d_bp/u15/SHCTRL.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/SHCTRL.MAC rename to Source/Images/d_bp/u15/SHCTRL.MAC diff --git a/Source/Images/hd0/s0/u15/SHDEFINE.COM b/Source/Images/d_bp/u15/SHDEFINE.COM similarity index 100% rename from Source/Images/hd0/s0/u15/SHDEFINE.COM rename to Source/Images/d_bp/u15/SHDEFINE.COM diff --git a/Source/Images/hd0/s0/u15/SHDEFINE.MAC b/Source/Images/d_bp/u15/SHDEFINE.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/SHDEFINE.MAC rename to Source/Images/d_bp/u15/SHDEFINE.MAC diff --git a/Source/Images/hd0/s0/u15/SHFILE.COM b/Source/Images/d_bp/u15/SHFILE.COM similarity index 100% rename from Source/Images/hd0/s0/u15/SHFILE.COM rename to Source/Images/d_bp/u15/SHFILE.COM diff --git a/Source/Images/hd0/s0/u15/SHFILE.MAC b/Source/Images/d_bp/u15/SHFILE.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/SHFILE.MAC rename to Source/Images/d_bp/u15/SHFILE.MAC diff --git a/Source/Images/hd0/s0/u15/SHOW.COM b/Source/Images/d_bp/u15/SHOW.COM similarity index 100% rename from Source/Images/hd0/s0/u15/SHOW.COM rename to Source/Images/d_bp/u15/SHOW.COM diff --git a/Source/Images/hd0/s0/u15/SHOW.MAC b/Source/Images/d_bp/u15/SHOW.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/SHOW.MAC rename to Source/Images/d_bp/u15/SHOW.MAC diff --git a/Source/Images/hd0/s0/u15/SHSET.COM b/Source/Images/d_bp/u15/SHSET.COM similarity index 100% rename from Source/Images/hd0/s0/u15/SHSET.COM rename to Source/Images/d_bp/u15/SHSET.COM diff --git a/Source/Images/hd0/s0/u15/SHSET.MAC b/Source/Images/d_bp/u15/SHSET.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/SHSET.MAC rename to Source/Images/d_bp/u15/SHSET.MAC diff --git a/Source/Images/hd0/s0/u15/SHUTILS2.LBR b/Source/Images/d_bp/u15/SHUTILS2.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/SHUTILS2.LBR rename to Source/Images/d_bp/u15/SHUTILS2.LBR diff --git a/Source/Images/hd0/s0/u15/SHVAR.COM b/Source/Images/d_bp/u15/SHVAR.COM similarity index 100% rename from Source/Images/hd0/s0/u15/SHVAR.COM rename to Source/Images/d_bp/u15/SHVAR.COM diff --git a/Source/Images/hd0/s0/u15/SHVAR.MAC b/Source/Images/d_bp/u15/SHVAR.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/SHVAR.MAC rename to Source/Images/d_bp/u15/SHVAR.MAC diff --git a/Source/Images/hd0/s0/u15/SHVAR11.LBR b/Source/Images/d_bp/u15/SHVAR11.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/SHVAR11.LBR rename to Source/Images/d_bp/u15/SHVAR11.LBR diff --git a/Source/Images/hd0/s0/u15/SUB.COM b/Source/Images/d_bp/u15/SUB.COM similarity index 100% rename from Source/Images/hd0/s0/u15/SUB.COM rename to Source/Images/d_bp/u15/SUB.COM diff --git a/Source/Images/hd0/s0/u15/SUB.MAC b/Source/Images/d_bp/u15/SUB.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/SUB.MAC rename to Source/Images/d_bp/u15/SUB.MAC diff --git a/Source/Images/hd0/s0/u15/SYSENV.AQM b/Source/Images/d_bp/u15/SYSENV.AQM similarity index 100% rename from Source/Images/hd0/s0/u15/SYSENV.AQM rename to Source/Images/d_bp/u15/SYSENV.AQM diff --git a/Source/Images/hd0/s0/u15/SYSENV.ASM b/Source/Images/d_bp/u15/SYSENV.ASM similarity index 100% rename from Source/Images/hd0/s0/u15/SYSENV.ASM rename to Source/Images/d_bp/u15/SYSENV.ASM diff --git a/Source/Images/hd0/s0/u15/SYSENV.LIB b/Source/Images/d_bp/u15/SYSENV.LIB similarity index 100% rename from Source/Images/hd0/s0/u15/SYSENV.LIB rename to Source/Images/d_bp/u15/SYSENV.LIB diff --git a/Source/Images/hd0/s0/u15/SYSFCP.AQM b/Source/Images/d_bp/u15/SYSFCP.AQM similarity index 100% rename from Source/Images/hd0/s0/u15/SYSFCP.AQM rename to Source/Images/d_bp/u15/SYSFCP.AQM diff --git a/Source/Images/hd0/s0/u15/SYSFCP.ASM b/Source/Images/d_bp/u15/SYSFCP.ASM similarity index 100% rename from Source/Images/hd0/s0/u15/SYSFCP.ASM rename to Source/Images/d_bp/u15/SYSFCP.ASM diff --git a/Source/Images/hd0/s0/u15/SYSFCP1.LIB b/Source/Images/d_bp/u15/SYSFCP1.LIB similarity index 100% rename from Source/Images/hd0/s0/u15/SYSFCP1.LIB rename to Source/Images/d_bp/u15/SYSFCP1.LIB diff --git a/Source/Images/hd0/s0/u15/SYSFCP11.LBR b/Source/Images/d_bp/u15/SYSFCP11.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/SYSFCP11.LBR rename to Source/Images/d_bp/u15/SYSFCP11.LBR diff --git a/Source/Images/hd0/s0/u15/SYSFCP2.LIB b/Source/Images/d_bp/u15/SYSFCP2.LIB similarity index 100% rename from Source/Images/hd0/s0/u15/SYSFCP2.LIB rename to Source/Images/d_bp/u15/SYSFCP2.LIB diff --git a/Source/Images/hd0/s0/u15/SYSIOP.AQM b/Source/Images/d_bp/u15/SYSIOP.AQM similarity index 100% rename from Source/Images/hd0/s0/u15/SYSIOP.AQM rename to Source/Images/d_bp/u15/SYSIOP.AQM diff --git a/Source/Images/hd0/s0/u15/SYSIOP.ASM b/Source/Images/d_bp/u15/SYSIOP.ASM similarity index 100% rename from Source/Images/hd0/s0/u15/SYSIOP.ASM rename to Source/Images/d_bp/u15/SYSIOP.ASM diff --git a/Source/Images/hd0/s0/u15/SYSNDR.AQM b/Source/Images/d_bp/u15/SYSNDR.AQM similarity index 100% rename from Source/Images/hd0/s0/u15/SYSNDR.AQM rename to Source/Images/d_bp/u15/SYSNDR.AQM diff --git a/Source/Images/hd0/s0/u15/SYSNDR.ASM b/Source/Images/d_bp/u15/SYSNDR.ASM similarity index 100% rename from Source/Images/hd0/s0/u15/SYSNDR.ASM rename to Source/Images/d_bp/u15/SYSNDR.ASM diff --git a/Source/Images/hd0/s0/u15/SYSNDR.LIB b/Source/Images/d_bp/u15/SYSNDR.LIB similarity index 100% rename from Source/Images/hd0/s0/u15/SYSNDR.LIB rename to Source/Images/d_bp/u15/SYSNDR.LIB diff --git a/Source/Images/hd0/s0/u15/SYSRCP.AQM b/Source/Images/d_bp/u15/SYSRCP.AQM similarity index 100% rename from Source/Images/hd0/s0/u15/SYSRCP.AQM rename to Source/Images/d_bp/u15/SYSRCP.AQM diff --git a/Source/Images/hd0/s0/u15/SYSRCP.ASM b/Source/Images/d_bp/u15/SYSRCP.ASM similarity index 100% rename from Source/Images/hd0/s0/u15/SYSRCP.ASM rename to Source/Images/d_bp/u15/SYSRCP.ASM diff --git a/Source/Images/hd0/s0/u15/SYSRCP.HLP b/Source/Images/d_bp/u15/SYSRCP.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/SYSRCP.HLP rename to Source/Images/d_bp/u15/SYSRCP.HLP diff --git a/Source/Images/hd0/s0/u15/SYSRCP1.LIB b/Source/Images/d_bp/u15/SYSRCP1.LIB similarity index 100% rename from Source/Images/hd0/s0/u15/SYSRCP1.LIB rename to Source/Images/d_bp/u15/SYSRCP1.LIB diff --git a/Source/Images/hd0/s0/u15/SYSRCP11.LBR b/Source/Images/d_bp/u15/SYSRCP11.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/SYSRCP11.LBR rename to Source/Images/d_bp/u15/SYSRCP11.LBR diff --git a/Source/Images/hd0/s0/u15/SYSRCP2.LIB b/Source/Images/d_bp/u15/SYSRCP2.LIB similarity index 100% rename from Source/Images/hd0/s0/u15/SYSRCP2.LIB rename to Source/Images/d_bp/u15/SYSRCP2.LIB diff --git a/Source/Images/hd0/s0/u15/SYSRCP3.LIB b/Source/Images/d_bp/u15/SYSRCP3.LIB similarity index 100% rename from Source/Images/hd0/s0/u15/SYSRCP3.LIB rename to Source/Images/d_bp/u15/SYSRCP3.LIB diff --git a/Source/Images/hd0/s0/u15/SYSRCP4.LIB b/Source/Images/d_bp/u15/SYSRCP4.LIB similarity index 100% rename from Source/Images/hd0/s0/u15/SYSRCP4.LIB rename to Source/Images/d_bp/u15/SYSRCP4.LIB diff --git a/Source/Images/hd0/s0/u15/T3M-HI2.Z80 b/Source/Images/d_bp/u15/T3M-HI2.Z80 similarity index 100% rename from Source/Images/hd0/s0/u15/T3M-HI2.Z80 rename to Source/Images/d_bp/u15/T3M-HI2.Z80 diff --git a/Source/Images/hd0/s0/u15/T3T-24-1.Z80 b/Source/Images/d_bp/u15/T3T-24-1.Z80 similarity index 100% rename from Source/Images/hd0/s0/u15/T3T-24-1.Z80 rename to Source/Images/d_bp/u15/T3T-24-1.Z80 diff --git a/Source/Images/hd0/s0/u15/TCCHECK.COM b/Source/Images/d_bp/u15/TCCHECK.COM similarity index 100% rename from Source/Images/hd0/s0/u15/TCCHECK.COM rename to Source/Images/d_bp/u15/TCCHECK.COM diff --git a/Source/Images/hd0/s0/u15/TCCHECK.MAC b/Source/Images/d_bp/u15/TCCHECK.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/TCCHECK.MAC rename to Source/Images/d_bp/u15/TCCHECK.MAC diff --git a/Source/Images/hd0/s0/u15/TCMAKE.COM b/Source/Images/d_bp/u15/TCMAKE.COM similarity index 100% rename from Source/Images/hd0/s0/u15/TCMAKE.COM rename to Source/Images/d_bp/u15/TCMAKE.COM diff --git a/Source/Images/hd0/s0/u15/TCMAKE.MAC b/Source/Images/d_bp/u15/TCMAKE.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/TCMAKE.MAC rename to Source/Images/d_bp/u15/TCMAKE.MAC diff --git a/Source/Images/hd0/s0/u15/TCSEL32.COM b/Source/Images/d_bp/u15/TCSEL32.COM similarity index 100% rename from Source/Images/hd0/s0/u15/TCSEL32.COM rename to Source/Images/d_bp/u15/TCSEL32.COM diff --git a/Source/Images/hd0/s0/u15/TCSELECT.COM b/Source/Images/d_bp/u15/TCSELECT.COM similarity index 100% rename from Source/Images/hd0/s0/u15/TCSELECT.COM rename to Source/Images/d_bp/u15/TCSELECT.COM diff --git a/Source/Images/hd0/s0/u15/TCSELECT.MAC b/Source/Images/d_bp/u15/TCSELECT.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/TCSELECT.MAC rename to Source/Images/d_bp/u15/TCSELECT.MAC diff --git a/Source/Images/hd0/s0/u15/TCSRC14.COM b/Source/Images/d_bp/u15/TCSRC14.COM similarity index 100% rename from Source/Images/hd0/s0/u15/TCSRC14.COM rename to Source/Images/d_bp/u15/TCSRC14.COM diff --git a/Source/Images/hd0/s0/u15/TCVIEW.COM b/Source/Images/d_bp/u15/TCVIEW.COM similarity index 100% rename from Source/Images/hd0/s0/u15/TCVIEW.COM rename to Source/Images/d_bp/u15/TCVIEW.COM diff --git a/Source/Images/hd0/s0/u15/TCVIEW20.COM b/Source/Images/d_bp/u15/TCVIEW20.COM similarity index 100% rename from Source/Images/hd0/s0/u15/TCVIEW20.COM rename to Source/Images/d_bp/u15/TCVIEW20.COM diff --git a/Source/Images/hd0/s0/u15/UNERASE.COM b/Source/Images/d_bp/u15/UNERASE.COM similarity index 100% rename from Source/Images/hd0/s0/u15/UNERASE.COM rename to Source/Images/d_bp/u15/UNERASE.COM diff --git a/Source/Images/hd0/s0/u15/UNERASE.MAC b/Source/Images/d_bp/u15/UNERASE.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/UNERASE.MAC rename to Source/Images/d_bp/u15/UNERASE.MAC diff --git a/Source/Images/hd0/s0/u15/VF-BOX11.LBR b/Source/Images/d_bp/u15/VF-BOX11.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/VF-BOX11.LBR rename to Source/Images/d_bp/u15/VF-BOX11.LBR diff --git a/Source/Images/hd0/s0/u15/VF41.IQF b/Source/Images/d_bp/u15/VF41.IQF similarity index 100% rename from Source/Images/hd0/s0/u15/VF41.IQF rename to Source/Images/d_bp/u15/VF41.IQF diff --git a/Source/Images/hd0/s0/u15/VF41H.LBR b/Source/Images/d_bp/u15/VF41H.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/VF41H.LBR rename to Source/Images/d_bp/u15/VF41H.LBR diff --git a/Source/Images/hd0/s0/u15/VF42A.LBR b/Source/Images/d_bp/u15/VF42A.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/VF42A.LBR rename to Source/Images/d_bp/u15/VF42A.LBR diff --git a/Source/Images/hd0/s0/u15/VFILER.COM b/Source/Images/d_bp/u15/VFILER.COM similarity index 100% rename from Source/Images/hd0/s0/u15/VFILER.COM rename to Source/Images/d_bp/u15/VFILER.COM diff --git a/Source/Images/hd0/s0/u15/VFILER.HLP b/Source/Images/d_bp/u15/VFILER.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/VFILER.HLP rename to Source/Images/d_bp/u15/VFILER.HLP diff --git a/Source/Images/hd0/s0/u15/VFILER.MAC b/Source/Images/d_bp/u15/VFILER.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/VFILER.MAC rename to Source/Images/d_bp/u15/VFILER.MAC diff --git a/Source/Images/hd0/s0/u15/VFILR4-1.LBR b/Source/Images/d_bp/u15/VFILR4-1.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/VFILR4-1.LBR rename to Source/Images/d_bp/u15/VFILR4-1.LBR diff --git a/Source/Images/hd0/s0/u15/VFILR4-2.LBR b/Source/Images/d_bp/u15/VFILR4-2.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/VFILR4-2.LBR rename to Source/Images/d_bp/u15/VFILR4-2.LBR diff --git a/Source/Images/hd0/s0/u15/VMENU.COM b/Source/Images/d_bp/u15/VMENU.COM similarity index 100% rename from Source/Images/hd0/s0/u15/VMENU.COM rename to Source/Images/d_bp/u15/VMENU.COM diff --git a/Source/Images/hd0/s0/u15/VMENU.HLP b/Source/Images/d_bp/u15/VMENU.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/VMENU.HLP rename to Source/Images/d_bp/u15/VMENU.HLP diff --git a/Source/Images/hd0/s0/u15/VMENU.MAC b/Source/Images/d_bp/u15/VMENU.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/VMENU.MAC rename to Source/Images/d_bp/u15/VMENU.MAC diff --git a/Source/Images/hd0/s0/u15/VMENU24.LBR b/Source/Images/d_bp/u15/VMENU24.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/VMENU24.LBR rename to Source/Images/d_bp/u15/VMENU24.LBR diff --git a/Source/Images/hd0/s0/u15/VMENUCK.COM b/Source/Images/d_bp/u15/VMENUCK.COM similarity index 100% rename from Source/Images/hd0/s0/u15/VMENUCK.COM rename to Source/Images/d_bp/u15/VMENUCK.COM diff --git a/Source/Images/hd0/s0/u15/VMENUCK.MAC b/Source/Images/d_bp/u15/VMENUCK.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/VMENUCK.MAC rename to Source/Images/d_bp/u15/VMENUCK.MAC diff --git a/Source/Images/hd0/s0/u15/VMENUFUN.LBR b/Source/Images/d_bp/u15/VMENUFUN.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/VMENUFUN.LBR rename to Source/Images/d_bp/u15/VMENUFUN.LBR diff --git a/Source/Images/hd0/s0/u15/WHEEL.COM b/Source/Images/d_bp/u15/WHEEL.COM similarity index 100% rename from Source/Images/hd0/s0/u15/WHEEL.COM rename to Source/Images/d_bp/u15/WHEEL.COM diff --git a/Source/Images/hd0/s0/u15/WHEEL.MAC b/Source/Images/d_bp/u15/WHEEL.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/WHEEL.MAC rename to Source/Images/d_bp/u15/WHEEL.MAC diff --git a/Source/Images/hd0/s0/u15/WILDCAT.TXT b/Source/Images/d_bp/u15/WILDCAT.TXT similarity index 100% rename from Source/Images/hd0/s0/u15/WILDCAT.TXT rename to Source/Images/d_bp/u15/WILDCAT.TXT diff --git a/Source/Images/hd0/s0/u15/WYSE.DPG b/Source/Images/d_bp/u15/WYSE.DPG similarity index 100% rename from Source/Images/hd0/s0/u15/WYSE.DPG rename to Source/Images/d_bp/u15/WYSE.DPG diff --git a/Source/Images/hd0/s0/u15/XD.COM b/Source/Images/d_bp/u15/XD.COM similarity index 100% rename from Source/Images/hd0/s0/u15/XD.COM rename to Source/Images/d_bp/u15/XD.COM diff --git a/Source/Images/hd0/s0/u15/XD.MAC b/Source/Images/d_bp/u15/XD.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/XD.MAC rename to Source/Images/d_bp/u15/XD.MAC diff --git a/Source/Images/hd0/s0/u15/XDIR.COM b/Source/Images/d_bp/u15/XDIR.COM similarity index 100% rename from Source/Images/hd0/s0/u15/XDIR.COM rename to Source/Images/d_bp/u15/XDIR.COM diff --git a/Source/Images/hd0/s0/u15/XDIR.MAC b/Source/Images/d_bp/u15/XDIR.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/XDIR.MAC rename to Source/Images/d_bp/u15/XDIR.MAC diff --git a/Source/Images/hd0/s0/u15/Z3BASE1.LIB b/Source/Images/d_bp/u15/Z3BASE1.LIB similarity index 100% rename from Source/Images/hd0/s0/u15/Z3BASE1.LIB rename to Source/Images/d_bp/u15/Z3BASE1.LIB diff --git a/Source/Images/hd0/s0/u15/Z3BASE2.LIB b/Source/Images/d_bp/u15/Z3BASE2.LIB similarity index 100% rename from Source/Images/hd0/s0/u15/Z3BASE2.LIB rename to Source/Images/d_bp/u15/Z3BASE2.LIB diff --git a/Source/Images/hd0/s0/u15/Z3FILES.PH1 b/Source/Images/d_bp/u15/Z3FILES.PH1 similarity index 100% rename from Source/Images/hd0/s0/u15/Z3FILES.PH1 rename to Source/Images/d_bp/u15/Z3FILES.PH1 diff --git a/Source/Images/hd0/s0/u15/Z3FILES.PH2 b/Source/Images/d_bp/u15/Z3FILES.PH2 similarity index 100% rename from Source/Images/hd0/s0/u15/Z3FILES.PH2 rename to Source/Images/d_bp/u15/Z3FILES.PH2 diff --git a/Source/Images/hd0/s0/u15/Z3FILES.PQ2 b/Source/Images/d_bp/u15/Z3FILES.PQ2 similarity index 100% rename from Source/Images/hd0/s0/u15/Z3FILES.PQ2 rename to Source/Images/d_bp/u15/Z3FILES.PQ2 diff --git a/Source/Images/hd0/s0/u15/Z3FILES2.PH2 b/Source/Images/d_bp/u15/Z3FILES2.PH2 similarity index 100% rename from Source/Images/hd0/s0/u15/Z3FILES2.PH2 rename to Source/Images/d_bp/u15/Z3FILES2.PH2 diff --git a/Source/Images/hd0/s0/u15/Z3FILES2.PQ2 b/Source/Images/d_bp/u15/Z3FILES2.PQ2 similarity index 100% rename from Source/Images/hd0/s0/u15/Z3FILES2.PQ2 rename to Source/Images/d_bp/u15/Z3FILES2.PQ2 diff --git a/Source/Images/hd0/s0/u15/Z3HDR1.LIB b/Source/Images/d_bp/u15/Z3HDR1.LIB similarity index 100% rename from Source/Images/hd0/s0/u15/Z3HDR1.LIB rename to Source/Images/d_bp/u15/Z3HDR1.LIB diff --git a/Source/Images/hd0/s0/u15/Z3HDR2.LIB b/Source/Images/d_bp/u15/Z3HDR2.LIB similarity index 100% rename from Source/Images/hd0/s0/u15/Z3HDR2.LIB rename to Source/Images/d_bp/u15/Z3HDR2.LIB diff --git a/Source/Images/hd0/s0/u15/Z3LDRBUG.LBR b/Source/Images/d_bp/u15/Z3LDRBUG.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/Z3LDRBUG.LBR rename to Source/Images/d_bp/u15/Z3LDRBUG.LBR diff --git a/Source/Images/hd0/s0/u15/Z3LOC.COM b/Source/Images/d_bp/u15/Z3LOC.COM similarity index 100% rename from Source/Images/hd0/s0/u15/Z3LOC.COM rename to Source/Images/d_bp/u15/Z3LOC.COM diff --git a/Source/Images/hd0/s0/u15/Z3LOC.MAC b/Source/Images/d_bp/u15/Z3LOC.MAC similarity index 100% rename from Source/Images/hd0/s0/u15/Z3LOC.MAC rename to Source/Images/d_bp/u15/Z3LOC.MAC diff --git a/Source/Images/hd0/s0/u15/Z3LOC11.LBR b/Source/Images/d_bp/u15/Z3LOC11.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/Z3LOC11.LBR rename to Source/Images/d_bp/u15/Z3LOC11.LBR diff --git a/Source/Images/hd0/s0/u15/Z3RES.HLP b/Source/Images/d_bp/u15/Z3RES.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/Z3RES.HLP rename to Source/Images/d_bp/u15/Z3RES.HLP diff --git a/Source/Images/hd0/s0/u15/Z3TCAP.AQM b/Source/Images/d_bp/u15/Z3TCAP.AQM similarity index 100% rename from Source/Images/hd0/s0/u15/Z3TCAP.AQM rename to Source/Images/d_bp/u15/Z3TCAP.AQM diff --git a/Source/Images/hd0/s0/u15/Z3TCAP.ASM b/Source/Images/d_bp/u15/Z3TCAP.ASM similarity index 100% rename from Source/Images/hd0/s0/u15/Z3TCAP.ASM rename to Source/Images/d_bp/u15/Z3TCAP.ASM diff --git a/Source/Images/hd0/s0/u15/Z3TCAP.HLP b/Source/Images/d_bp/u15/Z3TCAP.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/Z3TCAP.HLP rename to Source/Images/d_bp/u15/Z3TCAP.HLP diff --git a/Source/Images/hd0/s0/u15/Z3TCAP.TCP b/Source/Images/d_bp/u15/Z3TCAP.TCP similarity index 100% rename from Source/Images/hd0/s0/u15/Z3TCAP.TCP rename to Source/Images/d_bp/u15/Z3TCAP.TCP diff --git a/Source/Images/hd0/s0/u15/Z3TCAP.TQP b/Source/Images/d_bp/u15/Z3TCAP.TQP similarity index 100% rename from Source/Images/hd0/s0/u15/Z3TCAP.TQP rename to Source/Images/d_bp/u15/Z3TCAP.TQP diff --git a/Source/Images/hd0/s0/u15/Z3TCAP22.LBR b/Source/Images/d_bp/u15/Z3TCAP22.LBR similarity index 100% rename from Source/Images/hd0/s0/u15/Z3TCAP22.LBR rename to Source/Images/d_bp/u15/Z3TCAP22.LBR diff --git a/Source/Images/hd0/s0/u15/Z3UTIL1.HLP b/Source/Images/d_bp/u15/Z3UTIL1.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/Z3UTIL1.HLP rename to Source/Images/d_bp/u15/Z3UTIL1.HLP diff --git a/Source/Images/hd0/s0/u15/Z3UTIL2.HLP b/Source/Images/d_bp/u15/Z3UTIL2.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/Z3UTIL2.HLP rename to Source/Images/d_bp/u15/Z3UTIL2.HLP diff --git a/Source/Images/hd0/s0/u15/Z3UTIL3.HLP b/Source/Images/d_bp/u15/Z3UTIL3.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/Z3UTIL3.HLP rename to Source/Images/d_bp/u15/Z3UTIL3.HLP diff --git a/Source/Images/hd0/s0/u15/ZCPR3.AQM b/Source/Images/d_bp/u15/ZCPR3.AQM similarity index 100% rename from Source/Images/hd0/s0/u15/ZCPR3.AQM rename to Source/Images/d_bp/u15/ZCPR3.AQM diff --git a/Source/Images/hd0/s0/u15/ZCPR3.ASM b/Source/Images/d_bp/u15/ZCPR3.ASM similarity index 100% rename from Source/Images/hd0/s0/u15/ZCPR3.ASM rename to Source/Images/d_bp/u15/ZCPR3.ASM diff --git a/Source/Images/hd0/s0/u15/ZCPR3.HLP b/Source/Images/d_bp/u15/ZCPR3.HLP similarity index 100% rename from Source/Images/hd0/s0/u15/ZCPR3.HLP rename to Source/Images/d_bp/u15/ZCPR3.HLP diff --git a/Source/Images/hd0/s0/u15/ZCPR3.INS b/Source/Images/d_bp/u15/ZCPR3.INS similarity index 100% rename from Source/Images/hd0/s0/u15/ZCPR3.INS rename to Source/Images/d_bp/u15/ZCPR3.INS diff --git a/Source/Images/hd0/s0/u15/ZCPR3.IQS b/Source/Images/d_bp/u15/ZCPR3.IQS similarity index 100% rename from Source/Images/hd0/s0/u15/ZCPR3.IQS rename to Source/Images/d_bp/u15/ZCPR3.IQS diff --git a/Source/Images/hd0/s0/u15/ZCPR3DIR.BQG b/Source/Images/d_bp/u15/ZCPR3DIR.BQG similarity index 100% rename from Source/Images/hd0/s0/u15/ZCPR3DIR.BQG rename to Source/Images/d_bp/u15/ZCPR3DIR.BQG diff --git a/Source/Images/hd0/s0/u15/ZCPR3DIR.BUG b/Source/Images/d_bp/u15/ZCPR3DIR.BUG similarity index 100% rename from Source/Images/hd0/s0/u15/ZCPR3DIR.BUG rename to Source/Images/d_bp/u15/ZCPR3DIR.BUG diff --git a/Source/Images/hd0/s0/u15/ZEX.AQM b/Source/Images/d_bp/u15/ZEX.AQM similarity index 100% rename from Source/Images/hd0/s0/u15/ZEX.AQM rename to Source/Images/d_bp/u15/ZEX.AQM diff --git a/Source/Images/hd0/s0/u15/ZEX.ASM b/Source/Images/d_bp/u15/ZEX.ASM similarity index 100% rename from Source/Images/hd0/s0/u15/ZEX.ASM rename to Source/Images/d_bp/u15/ZEX.ASM diff --git a/Source/Images/hd0/s0/u15/ZEX.COM b/Source/Images/d_bp/u15/ZEX.COM similarity index 100% rename from Source/Images/hd0/s0/u15/ZEX.COM rename to Source/Images/d_bp/u15/ZEX.COM diff --git a/Source/Images/hd0/s0/u15/ZEX.ZEX b/Source/Images/d_bp/u15/ZEX.ZEX similarity index 100% rename from Source/Images/hd0/s0/u15/ZEX.ZEX rename to Source/Images/d_bp/u15/ZEX.ZEX diff --git a/Source/Images/hd0/s0/u15/ZEX2.COM b/Source/Images/d_bp/u15/ZEX2.COM similarity index 100% rename from Source/Images/hd0/s0/u15/ZEX2.COM rename to Source/Images/d_bp/u15/ZEX2.COM diff --git a/Source/Images/hd0/s0/u15/ZEX31A.COM b/Source/Images/d_bp/u15/ZEX31A.COM similarity index 100% rename from Source/Images/hd0/s0/u15/ZEX31A.COM rename to Source/Images/d_bp/u15/ZEX31A.COM diff --git a/Source/Images/hd0/s0/u15/ZGOLF.COM b/Source/Images/d_bp/u15/ZGOLF.COM similarity index 100% rename from Source/Images/hd0/s0/u15/ZGOLF.COM rename to Source/Images/d_bp/u15/ZGOLF.COM diff --git a/Source/Images/hd0/s0/u15/ZMLIB.COM b/Source/Images/d_bp/u15/ZMLIB.COM similarity index 100% rename from Source/Images/hd0/s0/u15/ZMLIB.COM rename to Source/Images/d_bp/u15/ZMLIB.COM diff --git a/Source/Images/hd0/s0/u15/ZSYSTEM.INS b/Source/Images/d_bp/u15/ZSYSTEM.INS similarity index 100% rename from Source/Images/hd0/s0/u15/ZSYSTEM.INS rename to Source/Images/d_bp/u15/ZSYSTEM.INS diff --git a/Source/Images/hd0/s0/u15/xzrl.com b/Source/Images/d_bp/u15/xzrl.com similarity index 100% rename from Source/Images/hd0/s0/u15/xzrl.com rename to Source/Images/d_bp/u15/xzrl.com diff --git a/Source/Images/hd0/s0/u15/xzrl.doc b/Source/Images/d_bp/u15/xzrl.doc similarity index 100% rename from Source/Images/hd0/s0/u15/xzrl.doc rename to Source/Images/d_bp/u15/xzrl.doc diff --git a/Source/Images/d_cpm22.txt b/Source/Images/d_cpm22.txt new file mode 100644 index 00000000..49f112c3 --- /dev/null +++ b/Source/Images/d_cpm22.txt @@ -0,0 +1,17 @@ +# +# Add RomWBW utilities +# +../../Binary/Apps/*.com 0: +# +# Add Tune sample files +# +../../Binary/Apps/Tunes/*.pt? 3: +../../Binary/Apps/Tunes/*.mym 3: +# +# Add OS image +# +../CPM22/cpm_wbw.sys 0:cpm.sys +# +# Add Common Applications +# +Common/*.* 0: diff --git a/Source/Images/hd0/s0/u0/ASM.COM b/Source/Images/d_cpm22/u0/ASM.COM similarity index 100% rename from Source/Images/hd0/s0/u0/ASM.COM rename to Source/Images/d_cpm22/u0/ASM.COM diff --git a/Source/Images/hd0/s0/u0/CR.COM b/Source/Images/d_cpm22/u0/CR.COM similarity index 100% rename from Source/Images/hd0/s0/u0/CR.COM rename to Source/Images/d_cpm22/u0/CR.COM diff --git a/Source/Images/d_cpm22/u0/DDT.COM b/Source/Images/d_cpm22/u0/DDT.COM new file mode 100644 index 00000000..70e4ebfe Binary files /dev/null and b/Source/Images/d_cpm22/u0/DDT.COM differ diff --git a/Source/Images/hd0/s0/u0/DDTZ.DOC b/Source/Images/d_cpm22/u0/DDTZ.DOC similarity index 100% rename from Source/Images/hd0/s0/u0/DDTZ.DOC rename to Source/Images/d_cpm22/u0/DDTZ.DOC diff --git a/Source/Images/hd0/s0/u0/DIRX.COM b/Source/Images/d_cpm22/u0/DIRX.COM similarity index 100% rename from Source/Images/hd0/s0/u0/DIRX.COM rename to Source/Images/d_cpm22/u0/DIRX.COM diff --git a/Source/Images/hd0/s0/u0/DUMP.COM b/Source/Images/d_cpm22/u0/DUMP.COM similarity index 100% rename from Source/Images/hd0/s0/u0/DUMP.COM rename to Source/Images/d_cpm22/u0/DUMP.COM diff --git a/Source/Images/hd0/s0/u0/ED.COM b/Source/Images/d_cpm22/u0/ED.COM similarity index 100% rename from Source/Images/hd0/s0/u0/ED.COM rename to Source/Images/d_cpm22/u0/ED.COM diff --git a/Source/Images/hd0/s0/u0/LBREXT.COM b/Source/Images/d_cpm22/u0/LBREXT.COM similarity index 100% rename from Source/Images/hd0/s0/u0/LBREXT.COM rename to Source/Images/d_cpm22/u0/LBREXT.COM diff --git a/Source/Images/hd0/s0/u0/LIB.COM b/Source/Images/d_cpm22/u0/LIB.COM similarity index 100% rename from Source/Images/hd0/s0/u0/LIB.COM rename to Source/Images/d_cpm22/u0/LIB.COM diff --git a/Source/Images/hd0/s0/u0/LINK.COM b/Source/Images/d_cpm22/u0/LINK.COM similarity index 100% rename from Source/Images/hd0/s0/u0/LINK.COM rename to Source/Images/d_cpm22/u0/LINK.COM diff --git a/Source/Images/hd0/s0/u0/LOAD.COM b/Source/Images/d_cpm22/u0/LOAD.COM similarity index 100% rename from Source/Images/hd0/s0/u0/LOAD.COM rename to Source/Images/d_cpm22/u0/LOAD.COM diff --git a/Source/Images/hd0/s0/u0/MAC.COM b/Source/Images/d_cpm22/u0/MAC.COM similarity index 100% rename from Source/Images/hd0/s0/u0/MAC.COM rename to Source/Images/d_cpm22/u0/MAC.COM diff --git a/Source/Images/hd0/s0/u0/MBASIC.COM b/Source/Images/d_cpm22/u0/MBASIC.COM similarity index 100% rename from Source/Images/hd0/s0/u0/MBASIC.COM rename to Source/Images/d_cpm22/u0/MBASIC.COM diff --git a/Source/Images/hd0/s0/u0/PIP.COM b/Source/Images/d_cpm22/u0/PIP.COM similarity index 100% rename from Source/Images/hd0/s0/u0/PIP.COM rename to Source/Images/d_cpm22/u0/PIP.COM diff --git a/Source/Images/hd0/s1/u0/pmarc.com b/Source/Images/d_cpm22/u0/PMARC.COM similarity index 100% rename from Source/Images/hd0/s1/u0/pmarc.com rename to Source/Images/d_cpm22/u0/PMARC.COM diff --git a/Source/Images/hd0/s1/u0/pmext.com b/Source/Images/d_cpm22/u0/PMEXT.COM similarity index 100% rename from Source/Images/hd0/s1/u0/pmext.com rename to Source/Images/d_cpm22/u0/PMEXT.COM diff --git a/Source/Images/hd0/s0/u0/RMAC.COM b/Source/Images/d_cpm22/u0/RMAC.COM similarity index 100% rename from Source/Images/hd0/s0/u0/RMAC.COM rename to Source/Images/d_cpm22/u0/RMAC.COM diff --git a/Source/Images/hd0/s0/u0/STAT.COM b/Source/Images/d_cpm22/u0/STAT.COM similarity index 100% rename from Source/Images/hd0/s0/u0/STAT.COM rename to Source/Images/d_cpm22/u0/STAT.COM diff --git a/Source/Images/fd0/u0/SUBMIT.COM b/Source/Images/d_cpm22/u0/SUBMIT.COM similarity index 100% rename from Source/Images/fd0/u0/SUBMIT.COM rename to Source/Images/d_cpm22/u0/SUBMIT.COM diff --git a/Source/Images/hd0/s0/u0/UNCR.COM b/Source/Images/d_cpm22/u0/UNCR.COM similarity index 100% rename from Source/Images/hd0/s0/u0/UNCR.COM rename to Source/Images/d_cpm22/u0/UNCR.COM diff --git a/Source/Images/hd0/s0/u0/UNZIP.COM b/Source/Images/d_cpm22/u0/UNZIP.COM similarity index 100% rename from Source/Images/hd0/s0/u0/UNZIP.COM rename to Source/Images/d_cpm22/u0/UNZIP.COM diff --git a/Source/Images/hd0/s0/u0/XSUB.COM b/Source/Images/d_cpm22/u0/XSUB.COM similarity index 100% rename from Source/Images/hd0/s0/u0/XSUB.COM rename to Source/Images/d_cpm22/u0/XSUB.COM diff --git a/Source/Images/d_cpm22/u0/ZSID.COM b/Source/Images/d_cpm22/u0/ZSID.COM new file mode 100644 index 00000000..686b3775 Binary files /dev/null and b/Source/Images/d_cpm22/u0/ZSID.COM differ diff --git a/Source/Images/hd0/s0/u1/SAMPKEY.DOC b/Source/Images/d_cpm22/u1/SAMPKEY.DOC similarity index 100% rename from Source/Images/hd0/s0/u1/SAMPKEY.DOC rename to Source/Images/d_cpm22/u1/SAMPKEY.DOC diff --git a/Source/Images/hd0/s0/u1/SAMPKEY.ZDK b/Source/Images/d_cpm22/u1/SAMPKEY.ZDK similarity index 100% rename from Source/Images/hd0/s0/u1/SAMPKEY.ZDK rename to Source/Images/d_cpm22/u1/SAMPKEY.ZDK diff --git a/Source/Images/hd0/s0/u1/SAMPKEY.ZDT b/Source/Images/d_cpm22/u1/SAMPKEY.ZDT similarity index 100% rename from Source/Images/hd0/s0/u1/SAMPKEY.ZDT rename to Source/Images/d_cpm22/u1/SAMPKEY.ZDT diff --git a/Source/Images/hd0/s0/u1/ZDE10.DOC b/Source/Images/d_cpm22/u1/ZDE10.DOC similarity index 100% rename from Source/Images/hd0/s0/u1/ZDE10.DOC rename to Source/Images/d_cpm22/u1/ZDE10.DOC diff --git a/Source/Images/hd0/s0/u1/ZDE10.FOR b/Source/Images/d_cpm22/u1/ZDE10.FOR similarity index 100% rename from Source/Images/hd0/s0/u1/ZDE10.FOR rename to Source/Images/d_cpm22/u1/ZDE10.FOR diff --git a/Source/Images/hd0/s0/u1/ZDE10.NEW b/Source/Images/d_cpm22/u1/ZDE10.NEW similarity index 100% rename from Source/Images/hd0/s0/u1/ZDE10.NEW rename to Source/Images/d_cpm22/u1/ZDE10.NEW diff --git a/Source/Images/hd0/s0/u1/ZDE10.QRF b/Source/Images/d_cpm22/u1/ZDE10.QRF similarity index 100% rename from Source/Images/hd0/s0/u1/ZDE10.QRF rename to Source/Images/d_cpm22/u1/ZDE10.QRF diff --git a/Source/Images/hd0/s0/u1/ZDE10.TOC b/Source/Images/d_cpm22/u1/ZDE10.TOC similarity index 100% rename from Source/Images/hd0/s0/u1/ZDE10.TOC rename to Source/Images/d_cpm22/u1/ZDE10.TOC diff --git a/Source/Images/hd0/s0/u1/ZDE13.FOR b/Source/Images/d_cpm22/u1/ZDE13.FOR similarity index 100% rename from Source/Images/hd0/s0/u1/ZDE13.FOR rename to Source/Images/d_cpm22/u1/ZDE13.FOR diff --git a/Source/Images/hd0/s0/u1/ZDE13.NEW b/Source/Images/d_cpm22/u1/ZDE13.NEW similarity index 100% rename from Source/Images/hd0/s0/u1/ZDE13.NEW rename to Source/Images/d_cpm22/u1/ZDE13.NEW diff --git a/Source/Images/hd0/s0/u1/ZDE16.COM b/Source/Images/d_cpm22/u1/ZDE16.COM similarity index 100% rename from Source/Images/hd0/s0/u1/ZDE16.COM rename to Source/Images/d_cpm22/u1/ZDE16.COM diff --git a/Source/Images/hd0/s0/u1/ZDE16.DIR b/Source/Images/d_cpm22/u1/ZDE16.DIR similarity index 100% rename from Source/Images/hd0/s0/u1/ZDE16.DIR rename to Source/Images/d_cpm22/u1/ZDE16.DIR diff --git a/Source/Images/hd0/s0/u1/ZDE16.FIX b/Source/Images/d_cpm22/u1/ZDE16.FIX similarity index 100% rename from Source/Images/hd0/s0/u1/ZDE16.FIX rename to Source/Images/d_cpm22/u1/ZDE16.FIX diff --git a/Source/Images/hd0/s0/u1/ZDE16.FOR b/Source/Images/d_cpm22/u1/ZDE16.FOR similarity index 100% rename from Source/Images/hd0/s0/u1/ZDE16.FOR rename to Source/Images/d_cpm22/u1/ZDE16.FOR diff --git a/Source/Images/hd0/s0/u1/ZDE16.NEW b/Source/Images/d_cpm22/u1/ZDE16.NEW similarity index 100% rename from Source/Images/hd0/s0/u1/ZDE16.NEW rename to Source/Images/d_cpm22/u1/ZDE16.NEW diff --git a/Source/Images/d_cpm22/u1/ZDE16A.COM b/Source/Images/d_cpm22/u1/ZDE16A.COM new file mode 100644 index 00000000..3e9fdcc0 Binary files /dev/null and b/Source/Images/d_cpm22/u1/ZDE16A.COM differ diff --git a/Source/Images/d_cpm22/u1/ZDE16A.PAT b/Source/Images/d_cpm22/u1/ZDE16A.PAT new file mode 100644 index 00000000..e4520856 --- /dev/null +++ b/Source/Images/d_cpm22/u1/ZDE16A.PAT @@ -0,0 +1,102 @@ +; This patch file modifies the officially-distributed .COM file +; for ZDE Ver 1.6 (copyright by Carson Wilson) to: +; - Correct a bug which did not preserve create times when +; editing files > 1 extent. +; - Use an apparently 'dead' byte in the configuration area as +; a configuration flag to allow disabling the 'Auto-Indent' +; feature which was always 'on' in ZDE1.6. +; +; With the second change, you may configure the 'Auto-Indent' +; feature to be active (as distributed) or disabled (as this patch +; is configured) by altering the DB at label 'AIDflt' in the +; second part of this patch file below. +; +; Assemble this file to a .HEX file (example uses ZMAC) as: +; +; ZMAC ZDE16A.PAT /H +; +; then overlay the resulting ZDE16.HEX onto ZDE16.COM with MYLOAD +; (or equivalent) as: +; +; MYLOAD ZDE.COM=ZDE.COM,ZDE16.HEX +; +; The resulting ZDE.COM will be identified as 'ZDE 1.6a' in the +; text identification string near the beginning of the .COM file. +; +; Harold F. Bower, 18 July 2001. +; +; CP/M Standard Equates +; +BDOS EQU 0005H +FCB EQU 005CH +DMA EQU 0080H +TPA EQU 0100H +; +SDMA EQU 26 ; CP/M Function to set DMA Address +; +; Needed locations within ZDE 1.6 +; +Fill EQU TPA+0F8BH ; For Date Patch +TimBuf EQU TPA+3B3FH ; " " " +; +VTFlg EQU TPA+3ADAH ; For Auto-Ins Patch +HCRFlg EQU TPA+3AE3H ; " " " " +LfMarg EQU TPA+3AFDH ; " " " " +; +; ----------- Begin Patch File ----------- +; +; --- Fix Create Time Stamp Preservation Error --- + + ORG TPA+0029H + ; was: + DB 'a, (C)' ; DB ', Copr.' + ORG TPA+2461H + ; was: + LD (FCB+13),A ; CALL ClUsrF +; + ORG TPA+2F10H + ; was: + LD B,4 ; CALL ClUsrF + CALL ClUsrF ; LD DE,TimBuf + LD DE,TimBuf ; LD C,SDMA + CALL SetDMA ; CALL BDOS +; + ORG TPA+30AAH + ; was: + LD DE,DMA ; LD C,SDMA +SetDMA: LD C,SDMA ; LD DE,DMA +; + ORG TPA+30B4H + ; was: +ClUsrF: XOR A ; XOR A + EX DE,HL ; LD (FCB+13),A + JP Fill ; RET +; +; --- Usurp Config Flag for Auto-Insert use, sense on startup --- +; + ORG TPA+0057H + ; was: 0FFH +AIDflt: DB 00H ; Set Desired default (0=Off, FF=On) +; + ORG TPA+262AH + ; was: + LD (LfMarg),HL ; LD HL,0101H + XOR A ; LD (LfMarg),HL + LD (VTFlg),A ; XOR A + LD (HCRFlg),A ; LD (VTFlg),A + NOP ; LD (HCRFlg),A + LD A,(AIDflt) ; DEC A +; + ORG TPA+2711H + ; was: + NOP ; LD A,(0157H) {Unknown Use} + NOP ; OR A + NOP ; JP Z,Error2 + NOP + NOP + NOP + NOP +; +;------------ End of Patch File ------------ + END + \ No newline at end of file diff --git a/Source/Images/hd0/s0/u1/ZDENST16.COM b/Source/Images/d_cpm22/u1/ZDENST16.COM similarity index 100% rename from Source/Images/hd0/s0/u1/ZDENST16.COM rename to Source/Images/d_cpm22/u1/ZDENST16.COM diff --git a/Source/Images/hd0/s0/u1/ZDEPROP.DOC b/Source/Images/d_cpm22/u1/ZDEPROP.DOC similarity index 100% rename from Source/Images/hd0/s0/u1/ZDEPROP.DOC rename to Source/Images/d_cpm22/u1/ZDEPROP.DOC diff --git a/Source/Images/hd0/s0/u1/ZDEPROP.Z80 b/Source/Images/d_cpm22/u1/ZDEPROP.Z80 similarity index 100% rename from Source/Images/hd0/s0/u1/ZDEPROP.Z80 rename to Source/Images/d_cpm22/u1/ZDEPROP.Z80 diff --git a/Source/Images/hd0/s0/u1/ZDKCOM13.COM b/Source/Images/d_cpm22/u1/ZDKCOM13.COM similarity index 100% rename from Source/Images/hd0/s0/u1/ZDKCOM13.COM rename to Source/Images/d_cpm22/u1/ZDKCOM13.COM diff --git a/Source/Images/hd0/s0/u1/ZDKCOM13.DOC b/Source/Images/d_cpm22/u1/ZDKCOM13.DOC similarity index 100% rename from Source/Images/hd0/s0/u1/ZDKCOM13.DOC rename to Source/Images/d_cpm22/u1/ZDKCOM13.DOC diff --git a/Source/Images/d_cpm3.txt b/Source/Images/d_cpm3.txt new file mode 100644 index 00000000..3ac5e227 --- /dev/null +++ b/Source/Images/d_cpm3.txt @@ -0,0 +1,33 @@ +# +# Add files from CPM3 build +# +../CPM3/cpmldr.com 0: +../CPM3/cpmldr.sys 0: +../CPM3/ccp.com 0: +../CPM3/gencpm.com 0: +../CPM3/genres.dat 0: +../CPM3/genbnk.dat 0: +../CPM3/bios3.spr 0: +../CPM3/bnkbios3.spr 0: +../CPM3/bdos3.spr 0: +../CPM3/bnkbdos3.spr 0: +../CPM3/resbdos3.spr 0: +../CPM3/cpm3res.sys 0: +../CPM3/cpm3bnk.sys 0: +../CPM3/gencpm.dat 0: +../CPM3/cpm3.sys 0: +../CPM3/readme.1st 0: +../CPM3/cpm3fix.pat 0: +# +# Add RomWBW utilities +# +../../Binary/Apps/*.com 0: +# +# Add Tune sample files +# +../../Binary/Apps/Tunes/*.pt? 3: +../../Binary/Apps/Tunes/*.mym 3: +# +# Add Common Applications +# +Common/*.* 0: diff --git a/Source/Images/d_cpm3/u0/DATE.COM b/Source/Images/d_cpm3/u0/DATE.COM new file mode 100644 index 00000000..67a84b2d Binary files /dev/null and b/Source/Images/d_cpm3/u0/DATE.COM differ diff --git a/Source/Images/d_cpm3/u0/DEVICE.COM b/Source/Images/d_cpm3/u0/DEVICE.COM new file mode 100644 index 00000000..78075a3f Binary files /dev/null and b/Source/Images/d_cpm3/u0/DEVICE.COM differ diff --git a/Source/Images/d_cpm3/u0/DIR.COM b/Source/Images/d_cpm3/u0/DIR.COM new file mode 100644 index 00000000..16ead13b Binary files /dev/null and b/Source/Images/d_cpm3/u0/DIR.COM differ diff --git a/Source/Images/d_cpm3/u0/DUMP.COM b/Source/Images/d_cpm3/u0/DUMP.COM new file mode 100644 index 00000000..97c10c03 Binary files /dev/null and b/Source/Images/d_cpm3/u0/DUMP.COM differ diff --git a/Source/Images/d_cpm3/u0/ED.COM b/Source/Images/d_cpm3/u0/ED.COM new file mode 100644 index 00000000..203eafd7 Binary files /dev/null and b/Source/Images/d_cpm3/u0/ED.COM differ diff --git a/Source/Images/d_cpm3/u0/ERASE.COM b/Source/Images/d_cpm3/u0/ERASE.COM new file mode 100644 index 00000000..cdc3ea60 Binary files /dev/null and b/Source/Images/d_cpm3/u0/ERASE.COM differ diff --git a/Source/Images/d_cpm3/u0/GENCOM.COM b/Source/Images/d_cpm3/u0/GENCOM.COM new file mode 100644 index 00000000..d0816425 Binary files /dev/null and b/Source/Images/d_cpm3/u0/GENCOM.COM differ diff --git a/Source/Images/d_cpm3/u0/GET.COM b/Source/Images/d_cpm3/u0/GET.COM new file mode 100644 index 00000000..7f12456e Binary files /dev/null and b/Source/Images/d_cpm3/u0/GET.COM differ diff --git a/Source/Images/d_cpm3/u0/HELP.COM b/Source/Images/d_cpm3/u0/HELP.COM new file mode 100644 index 00000000..378cf03b Binary files /dev/null and b/Source/Images/d_cpm3/u0/HELP.COM differ diff --git a/Source/Images/d_cpm3/u0/HELP.HLP b/Source/Images/d_cpm3/u0/HELP.HLP new file mode 100644 index 00000000..428c767a Binary files /dev/null and b/Source/Images/d_cpm3/u0/HELP.HLP differ diff --git a/Source/Images/d_cpm3/u0/HEXCOM.CPM b/Source/Images/d_cpm3/u0/HEXCOM.CPM new file mode 100644 index 00000000..9fdc1292 Binary files /dev/null and b/Source/Images/d_cpm3/u0/HEXCOM.CPM differ diff --git a/Source/Images/d_cpm3/u0/INITDIR.COM b/Source/Images/d_cpm3/u0/INITDIR.COM new file mode 100644 index 00000000..03cce335 Binary files /dev/null and b/Source/Images/d_cpm3/u0/INITDIR.COM differ diff --git a/Source/Images/d_cpm3/u0/PATCH.COM b/Source/Images/d_cpm3/u0/PATCH.COM new file mode 100644 index 00000000..548b266e Binary files /dev/null and b/Source/Images/d_cpm3/u0/PATCH.COM differ diff --git a/Source/Images/d_cpm3/u0/PIP.COM b/Source/Images/d_cpm3/u0/PIP.COM new file mode 100644 index 00000000..e1321a2a Binary files /dev/null and b/Source/Images/d_cpm3/u0/PIP.COM differ diff --git a/Source/Images/d_cpm3/u0/PUT.COM b/Source/Images/d_cpm3/u0/PUT.COM new file mode 100644 index 00000000..c9d35243 Binary files /dev/null and b/Source/Images/d_cpm3/u0/PUT.COM differ diff --git a/Source/Images/d_cpm3/u0/RENAME.COM b/Source/Images/d_cpm3/u0/RENAME.COM new file mode 100644 index 00000000..8582175f Binary files /dev/null and b/Source/Images/d_cpm3/u0/RENAME.COM differ diff --git a/Source/Images/d_cpm3/u0/ROMWBW.TXT b/Source/Images/d_cpm3/u0/ROMWBW.TXT new file mode 100644 index 00000000..3e6be3be --- /dev/null +++ b/Source/Images/d_cpm3/u0/ROMWBW.TXT @@ -0,0 +1,21 @@ +This is a generic CP/M 3 adaptation for RomWBW. + +There are two ways to launch CP/M 3. First, you can run the command +CPMLDR from a CP/M 2.2 or Z-System command line. Alternatively, you +boot directly into CP/M 3 by choosing the CP/M 3 disk from the RomWBW +loader prompt. The CP/M 3 disk must be bootable in this case. + +With the following exceptions, the files in this directory came from +the CP/M 3 binary distribution on "The Unofficial CP/M Web site" at +http://www.cpm.z80.de/binary.html. + +As documented in the "README.1ST" file, the included files have been +patched with all applicable DRI patches per CPM3FIX.PAT. + +In addition, the following have been added: + +- INITDIR.COM was not included. The copy included is the original + DRI distribution, with both patches installed. + +- ZSID.COM is the original DRI ZSID distribution, but patched to use + RST 6 instead of RST 7 to avoid conflicting with mode 1 interrupts. \ No newline at end of file diff --git a/Source/Images/d_cpm3/u0/SAVE.COM b/Source/Images/d_cpm3/u0/SAVE.COM new file mode 100644 index 00000000..03ceb989 Binary files /dev/null and b/Source/Images/d_cpm3/u0/SAVE.COM differ diff --git a/Source/Images/d_cpm3/u0/SET.COM b/Source/Images/d_cpm3/u0/SET.COM new file mode 100644 index 00000000..e4c374dc Binary files /dev/null and b/Source/Images/d_cpm3/u0/SET.COM differ diff --git a/Source/Images/d_cpm3/u0/SETDEF.COM b/Source/Images/d_cpm3/u0/SETDEF.COM new file mode 100644 index 00000000..005ac7ad Binary files /dev/null and b/Source/Images/d_cpm3/u0/SETDEF.COM differ diff --git a/Source/Images/d_cpm3/u0/SHOW.COM b/Source/Images/d_cpm3/u0/SHOW.COM new file mode 100644 index 00000000..5f2458ec Binary files /dev/null and b/Source/Images/d_cpm3/u0/SHOW.COM differ diff --git a/Source/Images/d_cpm3/u0/SUBMIT.COM b/Source/Images/d_cpm3/u0/SUBMIT.COM new file mode 100644 index 00000000..e0473c7c Binary files /dev/null and b/Source/Images/d_cpm3/u0/SUBMIT.COM differ diff --git a/Source/Images/d_cpm3/u0/TYPE.COM b/Source/Images/d_cpm3/u0/TYPE.COM new file mode 100644 index 00000000..17e695f2 Binary files /dev/null and b/Source/Images/d_cpm3/u0/TYPE.COM differ diff --git a/Source/Images/d_cpm3/u0/ZSID.COM b/Source/Images/d_cpm3/u0/ZSID.COM new file mode 100644 index 00000000..686b3775 Binary files /dev/null and b/Source/Images/d_cpm3/u0/ZSID.COM differ diff --git a/Source/Images/d_nzcom.txt b/Source/Images/d_nzcom.txt new file mode 100644 index 00000000..cb131317 --- /dev/null +++ b/Source/Images/d_nzcom.txt @@ -0,0 +1,18 @@ +# +# Add RomWBW utilities +# +../../Binary/Apps/*.com 0: +# +# Add Tune sample files +# +../../Binary/Apps/Tunes/*.pt? 3: +../../Binary/Apps/Tunes/*.mym 3: +# +# Add OS images +# +../CPM22/cpm_wbw.sys 0:cpm.sys +../ZSDOS/zsys_wbw.sys 0:zsys.sys +# +# Add Common Applications +# +Common/*.* 0: diff --git a/Source/Images/d_nzcom/u0/!(C)1988 b/Source/Images/d_nzcom/u0/!(C)1988 new file mode 100644 index 00000000..14367066 Binary files /dev/null and b/Source/Images/d_nzcom/u0/!(C)1988 differ diff --git a/Source/Images/d_nzcom/u0/!NZ-COM b/Source/Images/d_nzcom/u0/!NZ-COM new file mode 100644 index 00000000..e69de29b diff --git a/Source/Images/d_nzcom/u0/!VERS--1.2H b/Source/Images/d_nzcom/u0/!VERS--1.2H new file mode 100644 index 00000000..e69de29b diff --git a/Source/Images/d_nzcom/u0/ALIAS.CMD b/Source/Images/d_nzcom/u0/ALIAS.CMD new file mode 100644 index 00000000..936006c3 Binary files /dev/null and b/Source/Images/d_nzcom/u0/ALIAS.CMD differ diff --git a/Source/Images/d_nzcom/u0/ARUNZ.COM b/Source/Images/d_nzcom/u0/ARUNZ.COM new file mode 100644 index 00000000..041b87c4 Binary files /dev/null and b/Source/Images/d_nzcom/u0/ARUNZ.COM differ diff --git a/Source/Images/d_nzcom/u0/BGZRDS19.LBR b/Source/Images/d_nzcom/u0/BGZRDS19.LBR new file mode 100644 index 00000000..5849efa9 Binary files /dev/null and b/Source/Images/d_nzcom/u0/BGZRDS19.LBR differ diff --git a/Source/Images/d_nzcom/u0/CLEDINST.COM b/Source/Images/d_nzcom/u0/CLEDINST.COM new file mode 100644 index 00000000..c26a3cf0 Binary files /dev/null and b/Source/Images/d_nzcom/u0/CLEDINST.COM differ diff --git a/Source/Images/d_nzcom/u0/CLEDSAVE.COM b/Source/Images/d_nzcom/u0/CLEDSAVE.COM new file mode 100644 index 00000000..dde04bc1 Binary files /dev/null and b/Source/Images/d_nzcom/u0/CLEDSAVE.COM differ diff --git a/Source/Images/d_nzcom/u0/CONFIG.LBR b/Source/Images/d_nzcom/u0/CONFIG.LBR new file mode 100644 index 00000000..40d87ae7 Binary files /dev/null and b/Source/Images/d_nzcom/u0/CONFIG.LBR differ diff --git a/Source/Images/d_nzcom/u0/COPY.COM b/Source/Images/d_nzcom/u0/COPY.COM new file mode 100644 index 00000000..734953d9 Binary files /dev/null and b/Source/Images/d_nzcom/u0/COPY.COM differ diff --git a/Source/Images/d_nzcom/u0/CPSET.COM b/Source/Images/d_nzcom/u0/CPSET.COM new file mode 100644 index 00000000..54462ce0 Binary files /dev/null and b/Source/Images/d_nzcom/u0/CPSET.COM differ diff --git a/Source/Images/d_nzcom/u0/CRUNCH.COM b/Source/Images/d_nzcom/u0/CRUNCH.COM new file mode 100644 index 00000000..ac17854a Binary files /dev/null and b/Source/Images/d_nzcom/u0/CRUNCH.COM differ diff --git a/Source/Images/d_nzcom/u0/DOCFILES.LBR b/Source/Images/d_nzcom/u0/DOCFILES.LBR new file mode 100644 index 00000000..c96cd424 Binary files /dev/null and b/Source/Images/d_nzcom/u0/DOCFILES.LBR differ diff --git a/Source/Images/d_nzcom/u0/EDITNDR.COM b/Source/Images/d_nzcom/u0/EDITNDR.COM new file mode 100644 index 00000000..149cb98c Binary files /dev/null and b/Source/Images/d_nzcom/u0/EDITNDR.COM differ diff --git a/Source/Images/d_nzcom/u0/FCP.LBR b/Source/Images/d_nzcom/u0/FCP.LBR new file mode 100644 index 00000000..8eb74a42 Binary files /dev/null and b/Source/Images/d_nzcom/u0/FCP.LBR differ diff --git a/Source/Images/d_nzcom/u0/FF.COM b/Source/Images/d_nzcom/u0/FF.COM new file mode 100644 index 00000000..b68b2add Binary files /dev/null and b/Source/Images/d_nzcom/u0/FF.COM differ diff --git a/Source/Images/d_nzcom/u0/HELP.COM b/Source/Images/d_nzcom/u0/HELP.COM new file mode 100644 index 00000000..58b4d6fc Binary files /dev/null and b/Source/Images/d_nzcom/u0/HELP.COM differ diff --git a/Source/Images/d_nzcom/u0/HLPFILES.LBR b/Source/Images/d_nzcom/u0/HLPFILES.LBR new file mode 100644 index 00000000..32200cdd Binary files /dev/null and b/Source/Images/d_nzcom/u0/HLPFILES.LBR differ diff --git a/Source/Images/d_nzcom/u0/IF.COM b/Source/Images/d_nzcom/u0/IF.COM new file mode 100644 index 00000000..c51cd0bd Binary files /dev/null and b/Source/Images/d_nzcom/u0/IF.COM differ diff --git a/Source/Images/d_nzcom/u0/JETLDR.COM b/Source/Images/d_nzcom/u0/JETLDR.COM new file mode 100644 index 00000000..c33c0738 Binary files /dev/null and b/Source/Images/d_nzcom/u0/JETLDR.COM differ diff --git a/Source/Images/d_nzcom/u0/LBREXT.COM b/Source/Images/d_nzcom/u0/LBREXT.COM new file mode 100644 index 00000000..591922b7 Binary files /dev/null and b/Source/Images/d_nzcom/u0/LBREXT.COM differ diff --git a/Source/Images/d_nzcom/u0/LBRHELP.COM b/Source/Images/d_nzcom/u0/LBRHELP.COM new file mode 100644 index 00000000..eb1ef6ee Binary files /dev/null and b/Source/Images/d_nzcom/u0/LBRHELP.COM differ diff --git a/Source/Images/d_nzcom/u0/LDIR.COM b/Source/Images/d_nzcom/u0/LDIR.COM new file mode 100644 index 00000000..d72eeef7 Binary files /dev/null and b/Source/Images/d_nzcom/u0/LDIR.COM differ diff --git a/Source/Images/d_nzcom/u0/LPUT.COM b/Source/Images/d_nzcom/u0/LPUT.COM new file mode 100644 index 00000000..72716963 Binary files /dev/null and b/Source/Images/d_nzcom/u0/LPUT.COM differ diff --git a/Source/Images/d_nzcom/u0/LSH-HELP.COM b/Source/Images/d_nzcom/u0/LSH-HELP.COM new file mode 100644 index 00000000..0103795b Binary files /dev/null and b/Source/Images/d_nzcom/u0/LSH-HELP.COM differ diff --git a/Source/Images/d_nzcom/u0/LSH.COM b/Source/Images/d_nzcom/u0/LSH.COM new file mode 100644 index 00000000..6ec314f7 Binary files /dev/null and b/Source/Images/d_nzcom/u0/LSH.COM differ diff --git a/Source/Images/d_nzcom/u0/LSH.WZ b/Source/Images/d_nzcom/u0/LSH.WZ new file mode 100644 index 00000000..690f9918 Binary files /dev/null and b/Source/Images/d_nzcom/u0/LSH.WZ differ diff --git a/Source/Images/d_nzcom/u0/LSHINST.COM b/Source/Images/d_nzcom/u0/LSHINST.COM new file mode 100644 index 00000000..a9beb351 Binary files /dev/null and b/Source/Images/d_nzcom/u0/LSHINST.COM differ diff --git a/Source/Images/d_nzcom/u0/LX.COM b/Source/Images/d_nzcom/u0/LX.COM new file mode 100644 index 00000000..d424f9fb Binary files /dev/null and b/Source/Images/d_nzcom/u0/LX.COM differ diff --git a/Source/Images/d_nzcom/u0/MKZCM.COM b/Source/Images/d_nzcom/u0/MKZCM.COM new file mode 100644 index 00000000..ad0ce7b0 Binary files /dev/null and b/Source/Images/d_nzcom/u0/MKZCM.COM differ diff --git a/Source/Images/d_nzcom/u0/NAME.COM b/Source/Images/d_nzcom/u0/NAME.COM new file mode 100644 index 00000000..d3a8cdf5 Binary files /dev/null and b/Source/Images/d_nzcom/u0/NAME.COM differ diff --git a/Source/Images/d_nzcom/u0/NZ-DBASE.INF b/Source/Images/d_nzcom/u0/NZ-DBASE.INF new file mode 100644 index 00000000..72f21691 Binary files /dev/null and b/Source/Images/d_nzcom/u0/NZ-DBASE.INF differ diff --git a/Source/Images/d_nzcom/u0/NZBLITZ.COM b/Source/Images/d_nzcom/u0/NZBLITZ.COM new file mode 100644 index 00000000..03552792 Binary files /dev/null and b/Source/Images/d_nzcom/u0/NZBLITZ.COM differ diff --git a/Source/Images/d_nzcom/u0/NZBLTZ14.CFG b/Source/Images/d_nzcom/u0/NZBLTZ14.CFG new file mode 100644 index 00000000..5ffb8751 Binary files /dev/null and b/Source/Images/d_nzcom/u0/NZBLTZ14.CFG differ diff --git a/Source/Images/d_nzcom/u0/NZBLTZ14.HZP b/Source/Images/d_nzcom/u0/NZBLTZ14.HZP new file mode 100644 index 00000000..86d8590c Binary files /dev/null and b/Source/Images/d_nzcom/u0/NZBLTZ14.HZP differ diff --git a/Source/Images/d_nzcom/u0/NZCOM.COM b/Source/Images/d_nzcom/u0/NZCOM.COM new file mode 100644 index 00000000..b80c4d3a Binary files /dev/null and b/Source/Images/d_nzcom/u0/NZCOM.COM differ diff --git a/Source/Images/d_nzcom/u0/NZCOM.LBR b/Source/Images/d_nzcom/u0/NZCOM.LBR new file mode 100644 index 00000000..bf432b41 Binary files /dev/null and b/Source/Images/d_nzcom/u0/NZCOM.LBR differ diff --git a/Source/Images/d_nzcom/u0/NZCPR.LBR b/Source/Images/d_nzcom/u0/NZCPR.LBR new file mode 100644 index 00000000..1f51b0f6 Binary files /dev/null and b/Source/Images/d_nzcom/u0/NZCPR.LBR differ diff --git a/Source/Images/d_nzcom/u0/PATH.COM b/Source/Images/d_nzcom/u0/PATH.COM new file mode 100644 index 00000000..1245c1ad Binary files /dev/null and b/Source/Images/d_nzcom/u0/PATH.COM differ diff --git a/Source/Images/hd0/s2/u0/PUBLIC.COM b/Source/Images/d_nzcom/u0/PUBLIC.COM similarity index 100% rename from Source/Images/hd0/s2/u0/PUBLIC.COM rename to Source/Images/d_nzcom/u0/PUBLIC.COM diff --git a/Source/Images/d_nzcom/u0/PWD.COM b/Source/Images/d_nzcom/u0/PWD.COM new file mode 100644 index 00000000..dda7ce75 Binary files /dev/null and b/Source/Images/d_nzcom/u0/PWD.COM differ diff --git a/Source/Images/d_nzcom/u0/RCP.LBR b/Source/Images/d_nzcom/u0/RCP.LBR new file mode 100644 index 00000000..fdc452f6 Binary files /dev/null and b/Source/Images/d_nzcom/u0/RCP.LBR differ diff --git a/Source/Images/d_nzcom/u0/RELEASE.NOT b/Source/Images/d_nzcom/u0/RELEASE.NOT new file mode 100644 index 00000000..6b006422 --- /dev/null +++ b/Source/Images/d_nzcom/u0/RELEASE.NOT @@ -0,0 +1,266 @@ + + RELEASE.NOT - UPDATE INFORMATION ON NZCOM + + +Please understand that unlike purely commercial enterprises, Z-System is +mainly the work of enthusiasts. As such, Z-System never truly reaches +completion; each new development is more of a plateau upon which further +innovation occurs. Though we have done our best, the printed documentation +inevitably lags behind the most recent enhancements. Try to consult as +much material as possible about a given command before proceeding; if there +is a help or document file pertaining to the command, it supercedes printed +instructions, especially with regard to such matters as syntax or technical +specifications. + + +Notes of September 12, 1991 +=========================== + + Release 1.2H involves a significant updating of the support utilities that +we distribute as a courtesy with NZCOM. We suggest that any Z-System user who +is not in regular contact with a Z-Node consider taking advantage of the Z- +System Software Update Service (ZSUS). Here are some of the important changes +with this release. + +ZCNFG.COM, CONFIG.LBR: + Al Hawley has introduced a powerful and convenient method for + configuring programs. ZCNFG works either with individual CFG files or + with CFG files stored in the CONFIG.LBR library. For example, try + running the command "ZCNFG ZLT". ZCNFG will automatically extract + ZLT15.CFG from CONFIG.LBR. + +NZBLITZ.COM, NZBLTZ14.HZP, NZBLTZ14.CFG: + These programs can be used to coldboot an NZCOM system very rapidly + with the complete system, including error handlers, shells, and TCAPs + already in place. Run "HELP NZBLTZ14" to learn more about it. Some + of its features can be tailored by running "ZCNFG NZBLITZ" with the + CFG file in the same directory. + +LBREXT.COM: + This replaces LGET for extracting member files from LBR library files. + +HELP.COM, LBRHELP.COM, HLPFILES.LBR: + HELP (actually HELPC14) is an improved version of the help utility. + It can work with normal help files (HLP) and crunched help files + (HZP). LBRHELP can work with normal or crunched help files that are + stored in a library (which is where we have put all the help files + distributed with NZCOM and Z3PLUS). + +DOCFILES.LBR: + Documentation and help files have been collected into an LBR file. + +ZLT.COM: + This is a full Z-System replacement for LT, and it handles the latest + LZH-compressed files. + +COPY.COM: + This is the version of COPY from the ZSDOS/ZDDOS release. Enter "COPY + //" for syntax information. It replaces a dangerously defective copy + program provided with earlier releases. + +LSH.COM, LSH.WZ, LSH-HELP.COM, LSHINST.COM, ZERR.COM: + These are the latest LSH command history shell and command-line editor + and the associated error handler (fixed-log versions). These + completely replace EASE. + +CLEDINST.COM, CLEDSAVE.COM: + A transient history shell like LSH can be slow on floppy systems with + sluggish disk drives, even if the files have been placed in optimal + locations. Some of the RCP modules supplied no include an RCP- + resident command-line editor called CLED. Its features can be + configured using CLEDINST.COM, and the history can be saved to a file + using CLEDSAVE.COM. + +TCAP.LBR, XTCAP.COM: + A number of programs now require a terminal capabilities descriptor + (TCAP) with extended functions. The standard TCAPs loaded with + TCSELECT do not have these functions. TCAP.LBR is a collection of + extended TCAPs for some terminals. XTCAP.COM is a program that can + add the most important extensions to a standard TCAP (it is a quick- + and-dirty fix until the full set of TCAPs is updated). + +VIEW.COM: + This is Bridger Mitchell's file viewing utility. It is very powerful + (but it requires an extended TCAP). + +NAME.COM: + This program can quickly add or remove a name for a single directory. + +TCJ.INF: + TCJ has a new publisher (one of our own Z-Node sysops), and this file + tells how to take out a subscription (which all Z-System users + absolutely should do!). + +ZFILEB38.LZT: + This is the BRIEF listing of all the support programs currently + available for use with Z-System. There is another file which includes + descriptions of all the programs, but it would fill up an entire + diskette! + + +Notes of November 5, 1989 +========================= + + 1. NEW FILES: NZCPR.LBR, FCP.LBR, RCP.LBR. + 2. TCSELECT PROBLEM. + 3. ERA.COM, REN.COM, SAVE.COM, ETC. + 4. LSH REPLACES EASE. + 5. NZBLITZ IS HERE! + 6. MINOR UPDATE NOTES. + 7. TO NEW Z-SYSTEM USERS. + 8. ALERT ABOUT NZCPM.COM AND NZCOM.CCP + 9. CHANGE IN MKZCM DEFAULT AND STANDARD RCP + 10. NZBIO+.ZRL + +1. NEW FILES: NZCPR.LBR, FCP.LBR, RCP.LBR. + +The main NZCOM.LBR now contains only the standard configurations of the +CPR, FCP, and RCP modules to minimize disk space requirements. Alternative +versions of these modules are now supplied in separate libraries. Modules +can be loaded directly from these libraries, or individual files can be +extracted and put into NZCOM.LBR to replace the default files. Each +library has a brief DOC file describing the modules (one of which is the +default version included in NZCOM.LBR). + + +2. TCSELECT PROBLEM. + +A problem has been discovered with the operation of TCSELECT as described +in the manual. TCSELECT is a Z-System program and does not function +reliably under CP/M (on some systems it causes a crash). Fortunately there +is a simple fix: reverse the order of the instructions in the manual. Boot +up NZ-COM before attempting to create MYTERM.Z3T, and run TCSELECT only +after NZ-COM is running. Remember that you need both TCSELECT.COM and +Z3TCAP.TCP to generate your .Z3T file. The entries in the Z3TCAP library +for the Xerox computers has been patched to correct a long-standing error. + + +3. ERA.COM, REN.COM, SAVE.COM, ETC. + +The standard configuration of Z-System does not include resident commands +for REN, DIR, or SAVE. These are provided instead as transient programs +(COM files), and you should copy any that you need to your working disk. +Several other functions that are often resident (but not always) are also +provided as COM files. Many of these are type-4 programs (see the manual). +We recommend using SDZ.COM as your standard directory display utility. It +is far more functional than the resident DIR commands in either Z-System or +CP/M, and it is designed to work properly under both. + + +4. LSH REPLACES EASE. + +EASE has been replaced by a newer, more versatile, and well-behaved program +named LSH (Log SHell). Like EASE, LSH allows you to edit command lines +using WordStar-like control. Consult LSH.WZ before use for general +information and/or run HELPLSH while running LSH for a display of LSH's +capabilities. LSHINST installs and customizes LSH to taste. Error +handling is now taken care of by ZERR.COM, a separate program. Our great +thanks to Rob Friefeld for writing these superb Z-System tools and for +allowing us to include them with NZ-COM. + + +5. NZBLITZ IS HERE! + +NZBLITZ is now included with NZ-COM. True to its name, NZBLITZ loads NZCOM +(and ZSDOS/ZDDOS, if present) in a flash, making it extremely helpful in +saving and reloading a given system configuration. Once you have configured +your system as wanted, log to drive A0: and type NZBLITZ NZLOAD. From now +on, your startup to Z-System is MUCH faster; just type "NZLOAD". Be +forewarned that NZBLITZ saves EVERYTHING as currently active, so be sure you +are at the directory (usually A0:) you wish the system to start in, and be +sure all active shells, flow states, terminal definitions, and so on are the +desired ones before proceeding. See NZBLITZ.NZT for more information. + + +6. MINOR UPDATE NOTES. + +- The RCP WHLQ command no longer used. "WHL" alone displays the current + wheel status. As before, "WHL password" turns the wheel byte on. "WHL + xxx" now turns the wheel byte OFF if xxx is something other than the + correct password. + +- ARUNZ is now a type-4 program which loads at the highest possible + memory location so as to save lower memory for immediate re-execution + with GO. See ARUNZ09R.DZC and TCJ31.MZG (as well as your NZ-COM or + Z3PLUS manual) for more on the amazing ARUNZ. + +- VLU is no longer included, as it was not reliable. Please use LDIR, + LGET, LPUT, and LT instead to manipulate library files. + +- At this writing, we are sorry to note that both Z-Node Central and the + Lillipute Z-Nodes mentioned in Chapter 7 of your manual are out of + service. Z-Nodes 2 and 3 are still going strong, however, as are the + many others listed in ZNODES.LST. Z-Node 2 is the new Z-Node Central. + We cannot recommend highly enough that you get a modem and investigate at + least one Z-Node as a source of inspiration and support. + +- Version 5.0 of the ZEX batch processor replaces earlier versions. ZEX + now runs under both NZ-COM and Z3PLUS and is a very powerful means of + customizing programs and commands. The ZEX.RSX file mentioned in the + manual is no longer required. See ZEX50.DZC for help. + + +7. TO NEW Z-SYSTEM USERS. + +- All files whose middle filetype character is "Z" (e.g., SAMPLE.DZC) are + "crunched" files which must be uncompressed with LT.COM or UNCRUNCH.COM + before use. + +- As outlined in section 4.3.2 of your manual, most Z-System programs + have built-in help; type the name of any program followed by "//" for a + short description if you are confused. + +- Finally, it is not at all necessary to master every nuance of the system + before it becomes useful. Please don't try to devour all of Z-System at + once. Instead, we suggest that you decide what aspects of the system you + will find most helpful and try to master one or two of those before + exploring further. We think you will find that if you choose wisely, + learning one aspect fully not only makes that aspect of your computing + world easier, it also equips you with the skills and confidence with + which to surmount other aspects of Z-System more confidently. + + +8. ALERT ABOUT NZCPM.COM AND NZCOM.CCP + +Some users have been tripped up by the way NZCOM handles the generation of +an NZCPM.COM file: it writes such a file only if one does not already exist. +This speeds up loading of the system. However, should you ever make any +change to your base CP/M system (such as installing ZSDOS/ZDDOS or a new +version of the BIOS), you should be sure to erase the NZCPM.COM file so that +NZCOM will create a new one the next time it is run. You should also be +sure to use NZCOM and not NZBLITZ to load the system the first time after +such a change. + +You should also be aware that warmboots of the NZ-COM Z-System are performed +by loading the NZCOM.CCP file containing the image of the current command +processor. This means that you must not change the diskette in the A: drive +unless you have copied the **CURRENT** NZCOM.CCP file (if you only use one +system configuration, the current version will not change) to the diskette +you are going to place in the A: drive. + + +9. CHANGE IN MKZCM DEFAULT AND STANDARD RCP + +Carson Wilson and Rob Friefeld have made some major changes in the RCP code +(see the DOC file in RCP.LBR). The standard RCP module is now 18 records +long instead of 16. MKZCM.COM has been patched to provide this value as a +default. + + +10. NZBIO+.ZRL + +The BIOS in some computers uses the Z80 index and alternate registers +without +saving them on the stack and restoring them. These computers include the +TeleVideo 80x machines, the Oneac On!, the Zorba, and a number of other +machines. Some programs (JETLDR and EDITNDR, for example) will not function +properly under these conditions. The file NZBIO+.ZRL in NZCOM.LBR is a +special version of the NZ-COM virtual BIOS that protects the Zilog registers +across all BIOS calls. To use this BIOS, you much use MKZCM to allocate 4 +records to the BIOS. If you observe strange behavior on your system with +NZ-COM, you might want to try using this special BIOS. + + + + - End of RELEASE.NOT - + \ No newline at end of file diff --git a/Source/Images/d_nzcom/u0/SAINST.COM b/Source/Images/d_nzcom/u0/SAINST.COM new file mode 100644 index 00000000..1e6db48f Binary files /dev/null and b/Source/Images/d_nzcom/u0/SAINST.COM differ diff --git a/Source/Images/d_nzcom/u0/SALIAS.COM b/Source/Images/d_nzcom/u0/SALIAS.COM new file mode 100644 index 00000000..9b85e041 Binary files /dev/null and b/Source/Images/d_nzcom/u0/SALIAS.COM differ diff --git a/Source/Images/d_nzcom/u0/SAVENDR.COM b/Source/Images/d_nzcom/u0/SAVENDR.COM new file mode 100644 index 00000000..bf8d1125 Binary files /dev/null and b/Source/Images/d_nzcom/u0/SAVENDR.COM differ diff --git a/Source/Images/hd0/s2/u0/SDZ.COM b/Source/Images/d_nzcom/u0/SDZ.COM similarity index 100% rename from Source/Images/hd0/s2/u0/SDZ.COM rename to Source/Images/d_nzcom/u0/SDZ.COM diff --git a/Source/Images/d_nzcom/u0/SHOW.COM b/Source/Images/d_nzcom/u0/SHOW.COM new file mode 100644 index 00000000..b22ce699 Binary files /dev/null and b/Source/Images/d_nzcom/u0/SHOW.COM differ diff --git a/Source/Images/hd0/s2/u0/SUB.COM b/Source/Images/d_nzcom/u0/SUB.COM similarity index 100% rename from Source/Images/hd0/s2/u0/SUB.COM rename to Source/Images/d_nzcom/u0/SUB.COM diff --git a/Source/Images/hd0/s0/u0/SUBMIT.COM b/Source/Images/d_nzcom/u0/SUBMIT.COM similarity index 100% rename from Source/Images/hd0/s0/u0/SUBMIT.COM rename to Source/Images/d_nzcom/u0/SUBMIT.COM diff --git a/Source/Images/d_nzcom/u0/TCAP.LBR b/Source/Images/d_nzcom/u0/TCAP.LBR new file mode 100644 index 00000000..9a3fd9c5 Binary files /dev/null and b/Source/Images/d_nzcom/u0/TCAP.LBR differ diff --git a/Source/Images/d_nzcom/u0/TCJ.INF b/Source/Images/d_nzcom/u0/TCJ.INF new file mode 100644 index 00000000..7d9322c1 --- /dev/null +++ b/Source/Images/d_nzcom/u0/TCJ.INF @@ -0,0 +1,31 @@ + + Information About Subscriptions to + + The Computer Journal + + +The subscription rates for TCJ as of September 1, 1991, are as follows: + + 1 year 2 years + ------ ------- + U.S. $18 $32 + Foreign (surface mail) $24 $44 + Foreign (air mail) $38 $72 + +There are six issues per year. To place a subscription, contact the +new publisher (as of July 1992): + + The Computer Journal + P.O. Box 535 + Lincoln, CA 95658 + 916-645-1670 (answering machine and FAX) + +You may order a trial subscription. Just place an order. If you decide +that TCJ is not for you, then just mark the invoice "cancel" and send it +back. + +Payments for TCJ must normally be in the form of a money order or a check +drawn on a U.S. bank in U.S. funds or a postal money order in U.S. funds. +It is expected that MasterCard and VISA will again be acceptable in the +future. + \ No newline at end of file diff --git a/Source/Images/d_nzcom/u0/TCJ25.WZ b/Source/Images/d_nzcom/u0/TCJ25.WZ new file mode 100644 index 00000000..ad1a14e6 Binary files /dev/null and b/Source/Images/d_nzcom/u0/TCJ25.WZ differ diff --git a/Source/Images/d_nzcom/u0/TCJ26.WZ b/Source/Images/d_nzcom/u0/TCJ26.WZ new file mode 100644 index 00000000..97410f76 Binary files /dev/null and b/Source/Images/d_nzcom/u0/TCJ26.WZ differ diff --git a/Source/Images/d_nzcom/u0/TCJ27.WZ b/Source/Images/d_nzcom/u0/TCJ27.WZ new file mode 100644 index 00000000..35905c13 Binary files /dev/null and b/Source/Images/d_nzcom/u0/TCJ27.WZ differ diff --git a/Source/Images/d_nzcom/u0/TCJ28.WZ b/Source/Images/d_nzcom/u0/TCJ28.WZ new file mode 100644 index 00000000..ba0bdd5a Binary files /dev/null and b/Source/Images/d_nzcom/u0/TCJ28.WZ differ diff --git a/Source/Images/d_nzcom/u0/TCJ29.WZ b/Source/Images/d_nzcom/u0/TCJ29.WZ new file mode 100644 index 00000000..116420dd Binary files /dev/null and b/Source/Images/d_nzcom/u0/TCJ29.WZ differ diff --git a/Source/Images/d_nzcom/u0/TCJ30.WZ b/Source/Images/d_nzcom/u0/TCJ30.WZ new file mode 100644 index 00000000..dca4d430 Binary files /dev/null and b/Source/Images/d_nzcom/u0/TCJ30.WZ differ diff --git a/Source/Images/d_nzcom/u0/TCJ31UPD.WZ b/Source/Images/d_nzcom/u0/TCJ31UPD.WZ new file mode 100644 index 00000000..f55b7042 Binary files /dev/null and b/Source/Images/d_nzcom/u0/TCJ31UPD.WZ differ diff --git a/Source/Images/d_nzcom/u0/TCJ32.WZ b/Source/Images/d_nzcom/u0/TCJ32.WZ new file mode 100644 index 00000000..87a3caac Binary files /dev/null and b/Source/Images/d_nzcom/u0/TCJ32.WZ differ diff --git a/Source/Images/d_nzcom/u0/TCJ33UPD.WZ b/Source/Images/d_nzcom/u0/TCJ33UPD.WZ new file mode 100644 index 00000000..782a9e35 Binary files /dev/null and b/Source/Images/d_nzcom/u0/TCJ33UPD.WZ differ diff --git a/Source/Images/d_nzcom/u0/TCSELECT.COM b/Source/Images/d_nzcom/u0/TCSELECT.COM new file mode 100644 index 00000000..4b29b83e Binary files /dev/null and b/Source/Images/d_nzcom/u0/TCSELECT.COM differ diff --git a/Source/Images/d_nzcom/u0/TY3ERA.COM b/Source/Images/d_nzcom/u0/TY3ERA.COM new file mode 100644 index 00000000..ac18c870 Binary files /dev/null and b/Source/Images/d_nzcom/u0/TY3ERA.COM differ diff --git a/Source/Images/d_nzcom/u0/TY3REN.COM b/Source/Images/d_nzcom/u0/TY3REN.COM new file mode 100644 index 00000000..e5746c57 Binary files /dev/null and b/Source/Images/d_nzcom/u0/TY3REN.COM differ diff --git a/Source/Images/d_nzcom/u0/TY4ERA.COM b/Source/Images/d_nzcom/u0/TY4ERA.COM new file mode 100644 index 00000000..77dac7fd Binary files /dev/null and b/Source/Images/d_nzcom/u0/TY4ERA.COM differ diff --git a/Source/Images/hd0/s2/u0/REN.COM b/Source/Images/d_nzcom/u0/TY4REN.COM similarity index 100% rename from Source/Images/hd0/s2/u0/REN.COM rename to Source/Images/d_nzcom/u0/TY4REN.COM diff --git a/Source/Images/d_nzcom/u0/TY4SAVE.COM b/Source/Images/d_nzcom/u0/TY4SAVE.COM new file mode 100644 index 00000000..59f9d77f Binary files /dev/null and b/Source/Images/d_nzcom/u0/TY4SAVE.COM differ diff --git a/Source/Images/hd0/s2/u0/SP.COM b/Source/Images/d_nzcom/u0/TY4SP.COM similarity index 100% rename from Source/Images/hd0/s2/u0/SP.COM rename to Source/Images/d_nzcom/u0/TY4SP.COM diff --git a/Source/Images/d_nzcom/u0/UNCRUNCH.COM b/Source/Images/d_nzcom/u0/UNCRUNCH.COM new file mode 100644 index 00000000..5ffb68ef Binary files /dev/null and b/Source/Images/d_nzcom/u0/UNCRUNCH.COM differ diff --git a/Source/Images/d_nzcom/u0/VIEW.COM b/Source/Images/d_nzcom/u0/VIEW.COM new file mode 100644 index 00000000..c812f75e Binary files /dev/null and b/Source/Images/d_nzcom/u0/VIEW.COM differ diff --git a/Source/Images/d_nzcom/u0/XTCAP.COM b/Source/Images/d_nzcom/u0/XTCAP.COM new file mode 100644 index 00000000..06b26f08 Binary files /dev/null and b/Source/Images/d_nzcom/u0/XTCAP.COM differ diff --git a/Source/Images/d_nzcom/u0/Z3LOC.COM b/Source/Images/d_nzcom/u0/Z3LOC.COM new file mode 100644 index 00000000..fab1359c Binary files /dev/null and b/Source/Images/d_nzcom/u0/Z3LOC.COM differ diff --git a/Source/Images/d_nzcom/u0/Z3TCAP.TCP b/Source/Images/d_nzcom/u0/Z3TCAP.TCP new file mode 100644 index 00000000..07adc281 Binary files /dev/null and b/Source/Images/d_nzcom/u0/Z3TCAP.TCP differ diff --git a/Source/Images/d_nzcom/u0/ZCNFG.COM b/Source/Images/d_nzcom/u0/ZCNFG.COM new file mode 100644 index 00000000..b88a2d0c Binary files /dev/null and b/Source/Images/d_nzcom/u0/ZCNFG.COM differ diff --git a/Source/Images/d_nzcom/u0/ZERR.COM b/Source/Images/d_nzcom/u0/ZERR.COM new file mode 100644 index 00000000..4565e7a7 Binary files /dev/null and b/Source/Images/d_nzcom/u0/ZERR.COM differ diff --git a/Source/Images/d_nzcom/u0/ZEX.COM b/Source/Images/d_nzcom/u0/ZEX.COM new file mode 100644 index 00000000..cd46405d Binary files /dev/null and b/Source/Images/d_nzcom/u0/ZEX.COM differ diff --git a/Source/Images/d_nzcom/u0/ZF-DIM.COM b/Source/Images/d_nzcom/u0/ZF-DIM.COM new file mode 100644 index 00000000..1b1332ce Binary files /dev/null and b/Source/Images/d_nzcom/u0/ZF-DIM.COM differ diff --git a/Source/Images/d_nzcom/u0/ZF-REV.COM b/Source/Images/d_nzcom/u0/ZF-REV.COM new file mode 100644 index 00000000..b0cc279b Binary files /dev/null and b/Source/Images/d_nzcom/u0/ZF-REV.COM differ diff --git a/Source/Images/d_nzcom/u0/ZFILEB38.LZT b/Source/Images/d_nzcom/u0/ZFILEB38.LZT new file mode 100644 index 00000000..6c2c5399 Binary files /dev/null and b/Source/Images/d_nzcom/u0/ZFILEB38.LZT differ diff --git a/Source/Images/d_nzcom/u0/ZFILER.CMD b/Source/Images/d_nzcom/u0/ZFILER.CMD new file mode 100644 index 00000000..3fddad5c --- /dev/null +++ b/Source/Images/d_nzcom/u0/ZFILER.CMD @@ -0,0 +1,28 @@ + Z System Distribution ZFILER.CMD, 11 Oct 89 by Carson Wilson +0 ! $"Enter ZFILER macro script: " +E ! echo f%>ull file spec:%< $p;echo f%>ile directory:%< $d$u:;echo f%>ile name.....:%< $n;echo f%>ile type.....:%< $t +K ! $d$u:;$!crunch $f $"Destination directory: ";$h: +L ! $!if eq $t lbr;ldir $p;else;echo f%>ile %<$f%> is not a library;fi +T ! $!lt $p +U ! $d$u:;uncr $f;$h: +X ! if ~eq $t com;echo n%>ot a % file;else;$d$u:;:$n $" Command Tail: ";$h:;fi +Z ! $d$u:;$" Command to perform on file: " $f $" Tail: ";$h: +# + SAMPLE ZFILER COMMAND MACROS FOR USE WITH NZCOM AND Z3PLUS + +macros: 0. on-line macro + E. Echo data about file name + K. Krunch the file + L. display directory of Library + T. Type the file + U. Uncrunch the file + X. eXecute the file + Z. perform command on file + +ZFILER parameters for use with macro '0' + + $! ZEX 'GO' $P DU:FN.FT $D DRIVE + $".." PROMPT $F FN.FT $U USER + $'..' PROMPT $N FN $H HOME DU + $T FT + \ No newline at end of file diff --git a/Source/Images/d_nzcom/u0/ZHELPERS.LZT b/Source/Images/d_nzcom/u0/ZHELPERS.LZT new file mode 100644 index 00000000..976755a3 Binary files /dev/null and b/Source/Images/d_nzcom/u0/ZHELPERS.LZT differ diff --git a/Source/Images/d_nzcom/u0/ZLT.COM b/Source/Images/d_nzcom/u0/ZLT.COM new file mode 100644 index 00000000..57d04c5e Binary files /dev/null and b/Source/Images/d_nzcom/u0/ZLT.COM differ diff --git a/Source/Images/d_nzcom/u0/ZNODES66.LZT b/Source/Images/d_nzcom/u0/ZNODES66.LZT new file mode 100644 index 00000000..0f529921 Binary files /dev/null and b/Source/Images/d_nzcom/u0/ZNODES66.LZT differ diff --git a/Source/Images/hd0/s0/u0/zsdos.zrl b/Source/Images/d_nzcom/u0/ZSDOS.ZRL similarity index 100% rename from Source/Images/hd0/s0/u0/zsdos.zrl rename to Source/Images/d_nzcom/u0/ZSDOS.ZRL diff --git a/Source/Images/d_nzcom/u0/ZSYSTEM.IZF b/Source/Images/d_nzcom/u0/ZSYSTEM.IZF new file mode 100644 index 00000000..87fb58ee Binary files /dev/null and b/Source/Images/d_nzcom/u0/ZSYSTEM.IZF differ diff --git a/Source/Images/hd0/s0/u2/ANAGRAM.COM b/Source/Images/d_ws4/u0/ANAGRAM.COM similarity index 100% rename from Source/Images/hd0/s0/u2/ANAGRAM.COM rename to Source/Images/d_ws4/u0/ANAGRAM.COM diff --git a/Source/Images/hd0/s0/u2/CHAPTER1.DOC b/Source/Images/d_ws4/u0/CHAPTER1.DOC similarity index 100% rename from Source/Images/hd0/s0/u2/CHAPTER1.DOC rename to Source/Images/d_ws4/u0/CHAPTER1.DOC diff --git a/Source/Images/hd0/s0/u2/CHAPTER2.DOC b/Source/Images/d_ws4/u0/CHAPTER2.DOC similarity index 100% rename from Source/Images/hd0/s0/u2/CHAPTER2.DOC rename to Source/Images/d_ws4/u0/CHAPTER2.DOC diff --git a/Source/Images/hd0/s0/u2/CHAPTER3.DOC b/Source/Images/d_ws4/u0/CHAPTER3.DOC similarity index 100% rename from Source/Images/hd0/s0/u2/CHAPTER3.DOC rename to Source/Images/d_ws4/u0/CHAPTER3.DOC diff --git a/Source/Images/hd0/s0/u2/DIARY.DOC b/Source/Images/d_ws4/u0/DIARY.DOC similarity index 100% rename from Source/Images/hd0/s0/u2/DIARY.DOC rename to Source/Images/d_ws4/u0/DIARY.DOC diff --git a/Source/Images/hd0/s0/u2/DICTSORT.COM b/Source/Images/d_ws4/u0/DICTSORT.COM similarity index 100% rename from Source/Images/hd0/s0/u2/DICTSORT.COM rename to Source/Images/d_ws4/u0/DICTSORT.COM diff --git a/Source/Images/hd0/s0/u2/FIND.COM b/Source/Images/d_ws4/u0/FIND.COM similarity index 100% rename from Source/Images/hd0/s0/u2/FIND.COM rename to Source/Images/d_ws4/u0/FIND.COM diff --git a/Source/Images/hd0/s0/u2/HOMONYMS.TXT b/Source/Images/d_ws4/u0/HOMONYMS.TXT similarity index 100% rename from Source/Images/hd0/s0/u2/HOMONYMS.TXT rename to Source/Images/d_ws4/u0/HOMONYMS.TXT diff --git a/Source/Images/hd0/s0/u2/HYEXCEPT.TXT b/Source/Images/d_ws4/u0/HYEXCEPT.TXT similarity index 100% rename from Source/Images/hd0/s0/u2/HYEXCEPT.TXT rename to Source/Images/d_ws4/u0/HYEXCEPT.TXT diff --git a/Source/Images/hd0/s0/u2/HYPHEN.COM b/Source/Images/d_ws4/u0/HYPHEN.COM similarity index 100% rename from Source/Images/hd0/s0/u2/HYPHEN.COM rename to Source/Images/d_ws4/u0/HYPHEN.COM diff --git a/Source/Images/hd0/s0/u2/LOOKUP.COM b/Source/Images/d_ws4/u0/LOOKUP.COM similarity index 100% rename from Source/Images/hd0/s0/u2/LOOKUP.COM rename to Source/Images/d_ws4/u0/LOOKUP.COM diff --git a/Source/Images/hd0/s0/u2/MAINDICT.CMP b/Source/Images/d_ws4/u0/MAINDICT.CMP similarity index 100% rename from Source/Images/hd0/s0/u2/MAINDICT.CMP rename to Source/Images/d_ws4/u0/MAINDICT.CMP diff --git a/Source/Images/hd0/s0/u2/MARKFIX.COM b/Source/Images/d_ws4/u0/MARKFIX.COM similarity index 100% rename from Source/Images/hd0/s0/u2/MARKFIX.COM rename to Source/Images/d_ws4/u0/MARKFIX.COM diff --git a/Source/Images/hd0/s0/u2/MOVEPRN.COM b/Source/Images/d_ws4/u0/MOVEPRN.COM similarity index 100% rename from Source/Images/hd0/s0/u2/MOVEPRN.COM rename to Source/Images/d_ws4/u0/MOVEPRN.COM diff --git a/Source/Images/hd0/s0/u2/PATCH.LST b/Source/Images/d_ws4/u0/PATCH.LST similarity index 100% rename from Source/Images/hd0/s0/u2/PATCH.LST rename to Source/Images/d_ws4/u0/PATCH.LST diff --git a/Source/Images/hd0/s0/u2/PRINT.TST b/Source/Images/d_ws4/u0/PRINT.TST similarity index 100% rename from Source/Images/hd0/s0/u2/PRINT.TST rename to Source/Images/d_ws4/u0/PRINT.TST diff --git a/Source/Images/hd0/s0/u2/READ.ME b/Source/Images/d_ws4/u0/READ.ME similarity index 100% rename from Source/Images/hd0/s0/u2/READ.ME rename to Source/Images/d_ws4/u0/READ.ME diff --git a/Source/Images/hd0/s0/u2/README b/Source/Images/d_ws4/u0/README similarity index 100% rename from Source/Images/hd0/s0/u2/README rename to Source/Images/d_ws4/u0/README diff --git a/Source/Images/hd0/s0/u2/REVIEW.COM b/Source/Images/d_ws4/u0/REVIEW.COM similarity index 100% rename from Source/Images/hd0/s0/u2/REVIEW.COM rename to Source/Images/d_ws4/u0/REVIEW.COM diff --git a/Source/Images/hd0/s0/u2/RULER.DOC b/Source/Images/d_ws4/u0/RULER.DOC similarity index 100% rename from Source/Images/hd0/s0/u2/RULER.DOC rename to Source/Images/d_ws4/u0/RULER.DOC diff --git a/Source/Images/hd0/s0/u2/SAMPLE1.DOC b/Source/Images/d_ws4/u0/SAMPLE1.DOC similarity index 100% rename from Source/Images/hd0/s0/u2/SAMPLE1.DOC rename to Source/Images/d_ws4/u0/SAMPLE1.DOC diff --git a/Source/Images/hd0/s0/u2/SAMPLE2.DOC b/Source/Images/d_ws4/u0/SAMPLE2.DOC similarity index 100% rename from Source/Images/hd0/s0/u2/SAMPLE2.DOC rename to Source/Images/d_ws4/u0/SAMPLE2.DOC diff --git a/Source/Images/hd0/s0/u2/SAMPLE3.DOC b/Source/Images/d_ws4/u0/SAMPLE3.DOC similarity index 100% rename from Source/Images/hd0/s0/u2/SAMPLE3.DOC rename to Source/Images/d_ws4/u0/SAMPLE3.DOC diff --git a/Source/Images/hd0/s0/u2/SPELL.COM b/Source/Images/d_ws4/u0/SPELL.COM similarity index 100% rename from Source/Images/hd0/s0/u2/SPELL.COM rename to Source/Images/d_ws4/u0/SPELL.COM diff --git a/Source/Images/hd0/s0/u2/TABLE.DOC b/Source/Images/d_ws4/u0/TABLE.DOC similarity index 100% rename from Source/Images/hd0/s0/u2/TABLE.DOC rename to Source/Images/d_ws4/u0/TABLE.DOC diff --git a/Source/Images/hd0/s0/u2/TEXT.DOC b/Source/Images/d_ws4/u0/TEXT.DOC similarity index 100% rename from Source/Images/hd0/s0/u2/TEXT.DOC rename to Source/Images/d_ws4/u0/TEXT.DOC diff --git a/Source/Images/hd0/s0/u2/TW.COM b/Source/Images/d_ws4/u0/TW.COM similarity index 100% rename from Source/Images/hd0/s0/u2/TW.COM rename to Source/Images/d_ws4/u0/TW.COM diff --git a/Source/Images/hd0/s0/u2/WC.COM b/Source/Images/d_ws4/u0/WC.COM similarity index 100% rename from Source/Images/hd0/s0/u2/WC.COM rename to Source/Images/d_ws4/u0/WC.COM diff --git a/Source/Images/hd0/s0/u2/WINSTALL.COM b/Source/Images/d_ws4/u0/WINSTALL.COM similarity index 100% rename from Source/Images/hd0/s0/u2/WINSTALL.COM rename to Source/Images/d_ws4/u0/WINSTALL.COM diff --git a/Source/Images/hd0/s0/u2/WORDFREQ.COM b/Source/Images/d_ws4/u0/WORDFREQ.COM similarity index 100% rename from Source/Images/hd0/s0/u2/WORDFREQ.COM rename to Source/Images/d_ws4/u0/WORDFREQ.COM diff --git a/Source/Images/hd0/s0/u2/WS.COM b/Source/Images/d_ws4/u0/WS.COM similarity index 100% rename from Source/Images/hd0/s0/u2/WS.COM rename to Source/Images/d_ws4/u0/WS.COM diff --git a/Source/Images/hd0/s0/u2/WS.OVR b/Source/Images/d_ws4/u0/WS.OVR similarity index 100% rename from Source/Images/hd0/s0/u2/WS.OVR rename to Source/Images/d_ws4/u0/WS.OVR diff --git a/Source/Images/hd0/s0/u2/WSCHANGE.COM b/Source/Images/d_ws4/u0/WSCHANGE.COM similarity index 100% rename from Source/Images/hd0/s0/u2/WSCHANGE.COM rename to Source/Images/d_ws4/u0/WSCHANGE.COM diff --git a/Source/Images/hd0/s0/u2/WSCHANGE.OVR b/Source/Images/d_ws4/u0/WSCHANGE.OVR similarity index 100% rename from Source/Images/hd0/s0/u2/WSCHANGE.OVR rename to Source/Images/d_ws4/u0/WSCHANGE.OVR diff --git a/Source/Images/hd0/s0/u2/WSCHHELP.OVR b/Source/Images/d_ws4/u0/WSCHHELP.OVR similarity index 100% rename from Source/Images/hd0/s0/u2/WSCHHELP.OVR rename to Source/Images/d_ws4/u0/WSCHHELP.OVR diff --git a/Source/Images/fd0/u0/WSHELP.OVR b/Source/Images/d_ws4/u0/WSHELP.OVR similarity index 100% rename from Source/Images/fd0/u0/WSHELP.OVR rename to Source/Images/d_ws4/u0/WSHELP.OVR diff --git a/Source/Images/hd0/s0/u2/WSINDEX.XCL b/Source/Images/d_ws4/u0/WSINDEX.XCL similarity index 100% rename from Source/Images/hd0/s0/u2/WSINDEX.XCL rename to Source/Images/d_ws4/u0/WSINDEX.XCL diff --git a/Source/Images/hd0/s0/u2/WSMSGS.OVR b/Source/Images/d_ws4/u0/WSMSGS.OVR similarity index 100% rename from Source/Images/hd0/s0/u2/WSMSGS.OVR rename to Source/Images/d_ws4/u0/WSMSGS.OVR diff --git a/Source/Images/hd0/s0/u2/WSPRINT.OVR b/Source/Images/d_ws4/u0/WSPRINT.OVR similarity index 100% rename from Source/Images/hd0/s0/u2/WSPRINT.OVR rename to Source/Images/d_ws4/u0/WSPRINT.OVR diff --git a/Source/Images/hd0/s0/u2/WSSHORT.OVR b/Source/Images/d_ws4/u0/WSSHORT.OVR similarity index 100% rename from Source/Images/hd0/s0/u2/WSSHORT.OVR rename to Source/Images/d_ws4/u0/WSSHORT.OVR diff --git a/Source/Images/d_zpm3.txt b/Source/Images/d_zpm3.txt new file mode 100644 index 00000000..c330d32c --- /dev/null +++ b/Source/Images/d_zpm3.txt @@ -0,0 +1,32 @@ +# +# Add files from ZPM3 build +# +../ZPM3/zpmldr.com 0: +../ZPM3/zpmldr.sys 0: +../CPM3/cpmldr.com 0: +../CPM3/cpmldr.sys 0: +../ZPM3/autotog.com 15: +../ZPM3/clrhist.com 15: +../ZPM3/setz3.com 15: +../ZPM3/cpm3.sys 0: +../ZPM3/zccp.com 0: +../ZPM3/zinstal.zpm 0: +../ZPM3/startzpm.com 0: +../ZPM3/makedos.com 0: +../ZPM3/gencpm.dat 0: +../ZPM3/bnkbios3.spr 0: +../ZPM3/bnkbdos3.spr 0: +../ZPM3/resbdos3.spr 0: +# +# Add RomWBW utilities +# +../../Binary/Apps/*.com 15: +# +# Add Tune sample files +# +../../Binary/Apps/Tunes/*.pt? 3: +../../Binary/Apps/Tunes/*.mym 3: +# +# Add Common Applications +# +Common/*.* 15: diff --git a/Source/Images/d_zpm3/u0/HELP.HLP b/Source/Images/d_zpm3/u0/HELP.HLP new file mode 100644 index 00000000..428c767a Binary files /dev/null and b/Source/Images/d_zpm3/u0/HELP.HLP differ diff --git a/Source/Images/d_zpm3/u0/ROMWBW.TXT b/Source/Images/d_zpm3/u0/ROMWBW.TXT new file mode 100644 index 00000000..45528bd5 --- /dev/null +++ b/Source/Images/d_zpm3/u0/ROMWBW.TXT @@ -0,0 +1,47 @@ +This is a generic ZPM3 adaptation for RomWBW. + +There are two ways to launch ZPM3. First, you can run the command +CPMLDR from a CP/M 2.2 or Z-System command line. Alternatively, you +boot directly into ZPM3 by choosing the ZPM3 disk from the RomWBW +loader prompt. The ZPM3 disk must be bootable in this case. + +You may notice that there is a ZPMLDR application on the hard disk +image. This application is equivalent to CPMLDR, but it has some +issues with the number of drives that RomWBW supports. So, as +indicated above, use CPMLDR to launch ZPM3. + +I have not found a way to make ZPM3 start up with any drive other +than A: as the system drive. So, during the load process, the boot +drive and drive A: are "swapped" so that the boot drive is always +drive A:. Use the ASSIGN command after starting ZPM3 to see how the +drives get mapped. + +Per ZPM3 standard, files are distributed across different user areas +depending on their usage. Normal applications are in user 15. Help +files in user 10. Configuration files in user 14. + +In addition to the applications provided in the ZPM3 distributio, the +normal CP/M 3 files are included in user area 15. A few typical ZCPR +utility programs are also included in user area 15: + + - ALIAS + - ARUNZ + - ERASE + - HELPC (named ZHELP) + - LBREXT + - SALIAS + - SETPATH + - VERRROR + - VLU + - ZCNFG + - ERASE (named ZERASE) + - ZEX + - ZFILER + - ZP + - SHOW (named ZSHOW) + - ZXD + +It is a bit confusing, but the ZPM3 system file is called CPM3.SYS. +This is the ZPM3 default configuration and I guess it is done this +way to maximize compatibility with CP/M 3. You will notice that the +startup banner will indicate ZPM3. \ No newline at end of file diff --git a/Source/Images/d_zpm3/u10/ALIAS.HLP b/Source/Images/d_zpm3/u10/ALIAS.HLP new file mode 100644 index 00000000..c255ce8e --- /dev/null +++ b/Source/Images/d_zpm3/u10/ALIAS.HLP @@ -0,0 +1,167 @@ +; + + ALIAÓ 1.5 + + The ZCPR3 ALIAS Facility + + A - The ALIAS Command + + E - ALIAS Examples + + S - Summary of ALIAS Variables + +:A + +Command: ALIAS + + Syntax:‚ ALIAÓ [[dir:]cmndname] + +Function: + + Thå ALIAÓ facilitù ió thå scripô expansioî utilitù oæ ZCPR3® +Aî  Aliaó  ió  á  COÍ filå createä bù  thå  ALIAÓ  prograí  whicè +containó  onå  oò morå commandó (separateä bù semicolons©  tï  bå +placeä  iî thå Commanä Linå Buffer® Wheî thå Aliaó  ió  invoked¬ +parameteró  froí thå commanä linå arå implanteä intï  thå  scripô +containeä withiî thå Alias¬ anä thå resultinç ne÷ commanä linå ió +placeä intï thå Commanä Linå Buffeò anä executed. + + +Options: + None + +Comments: + + ZCPR³  MUSÔ  bå implementeä witè aî  Externaì  Commanä  Linå +Buffeò iî ordeò foò ALIAÓ tï work. + + Thå  scripô oæ thå internaì commanä linå supportó  parameteò +passinç  iî á manneò similaò tï ZEØ anä SUB® Thå  variableó  $n¬ +wherå  °  <½  î  <½ 9¬ maù bå placeä intï  thå  script¬  anä  thå +correspondinç  parameteró wilì bå substituteä foò  thå  indicateä +variables® Thå variablå $° ió thå namå oæ thå Aliaó itself® Thå +variablå $ª ió thå entirå taiì oæ thå commanä line. + + + Ne÷  variables¬ $Ä anä $U¬ arå available® $Ä  expandó  intï +thå letteò oæ thå disë whicè waó loggeä iî aô thå timå thå  Aliaó +waó  expandeä (thå homå disk)¬ anä $Õ expandó intï á  numbeò  (iî +ASCIÉ  chars© representinç thå useò areá whicè waó loggeä  iî  aô +thå timå thå Aliaó waó expandeä (thå homå user). + + Thå  ZCPR³ Systeí filå nameó arå availablå tï thå  Aliaó  aó +thå  variableó  $Fî foò NAME.TYP¬ $Nî foò NAMÅ anä $Tî  foò  TYP¬ +wherå  ° <½ î <½ 4® $F° referó tï thå prototypå SH.VAÒ  filenamå +whilå $F± referó tï NAME.TYÐ oæ Systeí Filå 1¬ $N± referó tï NAMÅ +oæ Systeí Filå 1¬ etc® Notå thaô thå SETFILÅ commanä ió useä  tï +definå thå contentó oæ thå Systeí filå names. + + '$$§  expandó intï á singlå '$'® Iî fact¬ anù  unrecognizeä +variablå afteò á '$§ wilì expanä tï itself® + + + Á  ne÷  ALIAÓ  variablå ió introduceä aô  Versioî  1.5¬  thå +Pointer®   Iô  wilì  returî  thå contentó  oæ  anù  knowî  memorù +locatioî (pluó aî optionaì offset)® Iô takeó thå forí.. + $.ADDR[+OFF][[.OFF[+OFF]][.OFF[+OFF]] + + Foò example¬ oî mù NZCOÍ system.. + +$.± (0001H© D50³ Bioó Warí booô entry +$.1-³ (0001H© D50° Beginninç oæ Bios +$.¶ (0006H© C70¶ BDOÓ entry + +$.10¹ (0109H© E78° Z3ENÖ address +$.109.2² (Z3ENV+22H© E88° Z3MSÇ buffeò address +$.109.22+¶ Z3MSG+¶ E88¶ Prograí Erroò Flag +$.109.1¸ (Z3ENV+18H© E90° Z3CÌ address +$.109.18.° (Z3CL© E9xø NXTCHÒ pointer + + + Summary of Alias Variables + + $0 - Name of Alias + $n - Parameter from Command Line (1 <= n <= 9) + + $* - Tail of Command Line (everything after the verb) + + $D - Currenô Drivå Letter + $U - Currenô Useò Number + + $F° ­ Prototypå SH.VAÒ Shelì Variablå File + $Fn - FILENAME.TYP of System File n (1 <= n <= 4) + $Nn - FILENAME of System File n + $Tî ­ TYÐ oæ Systeí Filå n + + $.addò ­ Ne÷ Pointeò variable + + $$ - The character '$' + + + + +Error Messages:‚ (onlù one) + + "Ovfl¢  meanó  thaô  eitheò á particulaò  commanä  taiì  haó +expandeä  beyonä  12¶ characteró anä woulä overflo÷ TBUFÆ  iæ  iô +werå  ruî  oò  thå  expandeä commanä  lines¬  combineä  witè  thå +remaindeò oæ thå contentó oæ thå commanä linå buffer¬ ió toï lonç +tï fiô iî thå commanä linå buffer. + +:S + + Summary of Alias Variables + + $0 - Name of Alias + $n - Parameter from Command Line (1 <= n <= 9) + + $* - Tail of Command Line (everything after the verb) + + $D - Home Disk + $U - Home User + + $F° ­ SH.VAÒ prototypå Shelì Variablå filename + $Fn - FILENAME.TYP of System File n (1 <= n <= 4) + $Nn - FILENAME of System File n + $Tî ­ TYÐ oæ Systeí Filå n + + $.addò ­ Ne÷ Pointeò variable + + $$ - The character '$' + +:E + + Examples of Aliases + +Case 1: + + Thå useò ió constantlù issuinç thå followinç commandó iî thå +ordeò indicated: + + ASM myfile.BBZ + LOAD myfile + + He can generalize it with the following Alias script: + + ASM $1.BBZ;LOAD $1 + + If this Alias is named MYASM.COM, then typing + +"MYASM test" will be equivalent to "ASM test.BBZ;LOAD test" + +Case 2: + + Samå aó Caså ± excepô tï thå Currenô Drive¬ noô  necessarilù +drivå B: + + ASÍ $1.$D$DZ;LOAÄ $1 + +Caså 3: + + Imaginå  yoõ havå changeä diskó iî thå drivå à anä yoõ  wanô +tï displaù itó directorù anä spacå available® Yoõ woulä normallù +executå ^à tï warí boot¬ theî maybå XDIÒ Cº foò thå display®   Aî +aliaó caî reducå thió tï á singlå characteò command¬ X. + + JUMÐ 0;XDIÒ C: + \ No newline at end of file diff --git a/Source/Images/d_zpm3/u10/HP-RPN.HLP b/Source/Images/d_zpm3/u10/HP-RPN.HLP new file mode 100644 index 00000000..3bc6530d --- /dev/null +++ b/Source/Images/d_zpm3/u10/HP-RPN.HLP @@ -0,0 +1,202 @@ +; + ‚ +  --- H P - R P N ---  +   + + RPN Notation for the HP Calculator + + + R -->  RPN Notation  + S -->  Stack Operations  + F -->  Stack Functions  + N -->  Numeric Functions  + H -->  HP Calculator  + Z -->  ZP  +:R +RPΠNotatioî + +Reverså Polisè Notatioî (RPN)¬ alsï calleä postfiø notation¬ ió á simplå +buô powerfuì stacë orienteä notatioî commonlù useä iî Hewlett-Packarä +calculators® Yoõ firsô enteò thå twï numberó yoõ wisè tï operatå on¬ usinç +thå 3«        1² +     9-3=¶     ¹ minuó ³           93­         ¶ +     9*3=2·    ¹ timeó ³           93ª        2· +     9/3=³     ¹ divideä bù ³      93¯         ³ +     9^3=72¹   ¹ tï thå 3rä poweò  93Þ       72¹ +:S +Stacë + +RPÎ ió baseä oî thå stack® Á numbeò ió entereä ontï thå stacë bù keyinç iî +thå digits¬ endinç witè ® Eacè subsequenô numbeò keyeä iî wilì pusè +thå existinç contentó oæ thå stacë onå leveì higher. + +Thå structurå oæ HP'ó ´ leveì stacë ió showî belowº + +               Ô -¾ 000°      Stacë registeò ´ +               Ú -¾ 000°      Stacë registeò ³ +               Ù -¾ 000°      Stacë registeò ² +               -------------------------------------------­ +               Ø -¾ 000°      Stacë registeò ± <­ Displayeä + +HÐ alwayó displayó thå contentó oæ thå Ø register® Wheî thå stacë ió +lifted¬ thå contentó oæ eacè stacë registeò arå moveä uð onå level¬ thå +contentó oæ registeò Ø arå copieä intï Ù anä thå contentó oæ registeò T¬ Šthå topmosô register¬ arå lost. + +Wheî thå stacë ió dropped¬ thå contentó oæ eacè stacë registeò arå moveä +dowî onå leveì anä thå contentó oæ thå Ô registeò arå copieä intï registeò +Z® Thå resulô oæ thå calculatoò operatioî ió placeä iî registeò X. + +Bù enterinç á numbeò ´ timeó tï copù iô intï eacè stacë register¬ thaô +numbeò becomeó á 'constant§ iî thå Ù registeò foò subsequenô chaiî +operations® Eacè subsequenô operatioî wilì droð thå stack¬ restorinç thå +samå 'constant§ valuå tï thå Ù registeò foò thå nexô operation. +:F + Stacë Functionó + +HÐ haó severaì functionó thaô affecô onlù thå stack® Iî eacè oæ theså +examples¬ eacè stacë registeò startó ouô loadeä witè thå numbeò oæ thaô +register. + +Clearinç thå Stacë (^X) + +Control-Ø clearó thå entirå stack® Thå valueó containeä iî thå LAST-Ø anä +memorù registeró arå noô affected® HÐ startó witè á cleaò stacë wheî +loadeä froí disk. + +               Ô -¾ ´         Ô -¾ ° +               Ú -¾ ³         Ú -¾ ° +               Ù -¾ ²         Ù -¾ ° +               ------------------------------------­ +               Ø -¾ ±         Ø -¾ °    <­ Displayeä +                    Keyº ^Ø             (Cleaò stack© + Enterinç á numbeò () + +¬ useä aó thå ENTEÒ key¬ terminateó entrù oæ á numbeò anä placeó iô oî +thå stack® Iô alsï separateó twï numberó entereä onå afteò thå other® © oò dowî (<) + +Wheî thå stacë ió rolleä up¬ thå stacë ió lifteä onå leveì anä thå previouó +valuå oæ Ô ió moveä dowî tï X® Wheî thå stacë ió rolleä down¬ thå stacë ió +droppeä onå leveì anä thå previouó valuå oæ Ø ió moveä uð tï T® Nï valueó +arå lost. + +               Ô -¾ ´         Ô -¾ ³    Valuå froí Ô moveä +               Ú -¾ ³         Ú -¾ ²         dowî tï Ø +               Ù -¾ ²         Ù -¾ ± +               ------------------------------------­ +               Ø -¾ ±         Ø -¾ ´    <­ Displayeä +                    Keyº ¾              (Rolì Up© + +               Ô -¾ ´         Ô -¾ ±    Valuå froí Ø moveä +               Ú -¾ ³         Ú -¾ ´         uð tï Ô +               Ù -¾ ²         Ù -¾ ³ +               ------------------------------------­ +               Ø -¾ ±         Ø -¾ ²    <­ Displayeä +                    Keyº ¼              (Rolì Down© + Exchangå contentó oæ Ø anä Ù (=) + +Thå contentó oæ registeró Ø anä Ù arå exchanged® Nï otheò registeró arå +affected® Thió functioî ió usefuì wheî yoõ neeä tï reverså thå ordeò oæ thå +operandó beforå yoõ perforí aî operatioî sensitivå tï thå ordeò oæ thå +operands¬ sucè aó subtraction¬ division¬ oò exponentiation. + +               Ù -¾ ²         Ù -¾ ± +               ------------------------------------­ +               Ø -¾ ±         Ø -¾ ²    <­ Displayeä +                    Keyº ½              (X<>Y© + Lasô Ø (L) + +Wheî á numeriã functioî ió executed¬ á copù oæ thå lasô valuå iî thå Ø +registeò beforå thå functioî ió executeä ió saveä iî thå Last-Ø register® +Thaô valuå caî bå restoreä tï thå Ø registeò usinç thå 'L§ key® Thå stacë +ió lifted¬ movinç thå currenô contentó oæ thå Ø registeò tï thå Ù register¬ +etc® Thå valuå iî thå Last-Ø registeò ió noô affected. + +               Ô -¾ ³         Ô -¾ ³         Ô -¾ ³ +               Ú -¾ ²         Ú -¾ ³         Ú -¾ ² +               Ù -¾ ±         Ù -¾ ²         Ù -¾ ¹ +               ------------------------------------------------­ +     Last-Ø -¾ Ø -¾ ¸         Ø -¾ ¹         Ø -¾ ¸ <­ Displayeä +                    Keyº «         Keyº Ì           (Last-X© +                         (1+8=9© +:N +Numeriã Functionó + +Wheî yoõ wanô tï keù iî twï numbers¬ onå afteò thå other¬ yoõ uså thå  Overview  + H -->  Invoking HP  + D -->  Display Modes  + S -->  Stack  + E -->  Entering Numbers  + F -->  Math Functions  + M -->  Memory Registers  + N -->  RPN Notation  + Z -->  ZP  +:O +Overview + +HÐ ió á simplå anä verù usefuì programmer'ó integeò calculatoò modeleä +afteò á Hewlett-Packarä calculatoò witè RPÎ notation® + +HÐ caî operatå iî anù oæ fouò displaù modesº hexadecimal¬ decimal¬ binary¬ +anä character¬ makinç iô verù usefuì foò quicklù convertinç integeò numberó +froí onå baså tï another® Iô caî dï integeò addition¬ subtraction¬ +multiplication¬ division¬ exponentiatioî anä bitwiså logicaì ANÄ anä OÒ +operations® Iô haó á ´ leveì stacë anä ¶ memorù storagå registers. + +Alì HÐ internaì storagå anä arithmetiã ió unsigneä 1¶ bit® Thuó 6553· +becomeó 00001¬ -± becomeó 65535¬ anä sï on® Multiplicatioî anä +exponentiatioî overflo÷ anä divisioî bù ° wilì generatå errors® Iî binarù +anä characteò modes¬ thå displaù showó onlù thå lowesô ¸ bitó oæ 16¬ sï 8- +biô overflo÷ doeó noô usuallù causå aî error. + +Iæ yoõ arå noô familiaò witè RPÎ notation¬ see the section on RPN notation, +whicè alsï haó morå detaiì oî ho÷ HÐ useó itó stacë durinç stacë anä +numeriã operations. +:H +Invoking HP + +Uså ^à tï invokå HP® Iô wilì displaù itó singlå operatinç linå anä waiô +foò youò input: + +   H¾ 0000 + +Thå 'H>§ indicateó hexadecimaì displaù modå (thå defaulô displaù mode)¬ anä +'0000§ ió thå fouò digiô heø calculatoò displaù oæ registeò X¬ whicè ió thå +registeò alwayó displayed® Thå HÐ stacë anä memorù arå initializeä tï zerï +wheî ZP ió firsô loadeä into memory. + + Exiting HP  + +Uså ^à tï exiô HÐ anä returî tï ZP® Wheî yoõ uså ^à tï reruî HP¬ yoõ wilì Šreturî tï iô jusô wherå yoõ lefô off® +:D +Display Modes + +HÐ caî operatå iî anù oæ fouò displaù modesº hexadecimal¬ decimal¬ binary¬ +anä character¬ makinç iô verù usefuì foò quicklù convertinç integeò numberó +froí onå baså tï another® Tï changå displaù modes¬ enteò thå escapå H® Iî heø mode¬ numberó displaù +aó fouò heø digitó froí 0000-FFFF. + +Decimaì Displaù Mode + +Tï selecô thå decimaì displaù mode¬ enteò D® Iî decimaì mode¬ numberó +displaù aó fivå decimaì digitó froí 00000-65535. + +Binarù Displaù Mode + +Tï selecô thå binarù displaù mode¬ enteò B® Iî binarù mode¬ thå leasô +significanô bytå oæ thå numbeò displayó aó ¸ binarù digits from +00000000-11111111. + Character Display Mode + +Tï selecô thå characteò displaù mode¬ enteò C® Iî characteò mode¬ thå +leasô significanô · bitó oæ thå numbeò displaù aó aî ASCIÉ character® +Controì characteró arå indicateä bù á '^§ prefix® Foò example¬ 03È wilì +displaù aó '^C§ anä DEÌ (7FH)¬ wilì displaù aó '^?'. + +Alì non-commanä characteró caî bå directlù entereä aó datá iî thå characteò +displaù mode® Characteró thaô havå commanä functionó sucè aó 'L'¬ '/'¬ +'='¬ ^C¬ Y)® Nï otheò registeró arå affected® Thió +functioî ió usefuì wheî yoõ neeä tï reverså thå ordeò oæ operandó beforå +performinç á functioî sensitivå tï operanä ordeò sucè aó subtraction¬ +divisoî oò exponentiation. + Last-X + +Wheî á numeriã functioî ió executed¬ á copù oæ thå lasô valuå iî thå Ø +registeò beforå thå functioî ió executeä ió saveä iî thå Last-Ø register® +Thaô valuå caî bå restoreä tï thå Ø registeò usinç thå 'L§ (oò 'l'© key® +Thå stacë ió lifted¬ pushinç thå currenô contentó oæ thå Ø registeò tï thå +Ù register¬ etc¬ unlesó stacë lifô haó beeî disableä bù thå § (oò thå unshifteä equivalenô '.'© tï +rolì iô uð onå leveì anä '<§ (oò ','© tï rolì iô dowî onå level® Wheî thå +stacë ió rolleä up¬ thå contentó oæ alì stacë registeró arå moveä uð onå +leveì anä thå valuå iî thå toð stacë registeò ió moveä dowî tï thå Ø +register® Wheî thå stacë ió rolleä down¬ thå contentó oæ alì stacë +registeró arå moveä dowî onå leveì anä thå valuå iî thå Ø registeò ió moveä +uð tï thå toð stacë register® Nï stacë valueó arå losô aó á resulô oæ +stacë rolì operations. +:E +Entering Numbers + +Invokå HÐ anä trù enterinç thå followinç sequencå oæ keys: + + ^X Ä ± ., + +aó welì aó thå controì characteró ^X¬ ^à anä s§ insteaä oæ 's'¬ +sincå enterinç 's§ initiateó á storå tï á memorù register. +:F +Math Functions + +HP'ó integeò numeriã functionó anä thå keyó tï invokå theí are: + + + Addition, X=Y+Ø +     - Subtraction, X=Y-Ø +     * Multiplication, X=Y*Ø +     ^ Exponentiation, X=Y^Ø (Ù tï thå Xtè power© +     / Integer quotient, X=INT(Y/X© + - Remainder Register R=X*(Y/X-INT(Y/X)© +     & Bitwise AND, X=Y&X +     | Bitwise OR, X=Y|X +     ~ Negation [2's complement], X=~X + +Negatioî affectó onlù thå Ø register® Thå otheò operationó uså thå numberó +iî thå firsô twï registeró Ø anä Ù aó operands¬ anä displaù thå resulô iî +thå Ø register¬ droppinç thå stack. + +Wheî aî integeò divisioî ió performed¬ yoõ arå ofteî interesteä iî botè thå +quotienô anä thå remainder® Thå remaindeò ió saveä iî thå speciaì reserveä +memorù registeò Ò durinç thå divisioî operation® Memorù registeò Ò maù bå +accesseä jusô likå anù oæ thå regulaò memorù registeró 0-5® Iô maù alsï bå +useä tï storå numberó likå anù otheò register¬ buô anù contentó wilì bå +overwritteî bù thå remaindeò durinç integeò divisioî operations. + +Thå consolå belì wilì rinç iæ aî undefineä keù ió presseä aó aî operator® +Iô wilì alsï rinç anä thå currenô multiplicatioî (*)¬ divisioî (/© oò +exponentiatioî (^© functioî wilì noô operatå iæ 16-biô overflo÷ occuró oò +iæ divisioî bù ° ió attempted® Thå stacë ió lefô unchanged® Uså thå +backspacå keù tï deletå thå offendinç operanä iæ yoõ wisè tï trù again. +:M +Memory Storage Registers + +Therå arå ¶ memorù storagå registeró numbereä 0-µ thaô maù bå useä tï storå +constantó oò intermediatå valueó durinç calculations¬ foò recalleä aó +needeä later® Thå contentó oæ memorù registeró arå noô affecteä bù thå +cleaò stacë (^X© operation® Eitheò uppeò oò loweò caså letteró maù bå useä +tï specifù memorù registeò Storå oò Recalì functions. + +Storing a Number + +Thå numbeò iî thå Ø registeò maù bå storeä iî anù oæ thå memorù registeró +usinç thå commanä 'S§ followeä bù thå numbeò oæ thå desireä memorù +register® Thå previouó contentó oæ thå memorù registeò wilì bå +overwritten¬ buô thå numbeò iî thå Ø registeò wilì noô bå affected® Foò +example¬ tï storå thå numbeò iî registeò Ø iî memorù registeò 2¬ enteò +'S2'® + Recalling a Number + +Numberó maù bå recalleä froí á memorù registeò tï registeò Ø usinç thå 'R§ +commanä followeä bù thå numbeò oæ thå memorù register® Thå stacë wilì bå +lifted¬ pushinç thå previouó contentó oæ registeò Ø intï registeò Y¬ buô +thå numbeò iî thå memorù registeò wilì noô bå affected® Foò example¬ tï +recalì memorù registeò ° tï registeò X¬ enteò 'R0'. + +Remainder Register + +Aî additionaì speciaì memorù registeò Ò ió useä bù thå divisioî operatioî +tï storå thå remainder® Iô maù alsï bå useä tï storå anä recalì numbers¬ +buô anù contentó wilì bå overwritteî durinç á divisioî operation® Thå +commanä 'RR§ (Recalì Remainder© afteò á divisioî operatioî wilì storå á +copù oæ thå remaindeò iî registeò Ø anä pusè thå quotienô tï registeò Y® +Aî exchangå (=© operatioî wilì swað theí iæ desired® +:N:HP-RPN.HLP +:Z:ZP.HLP + \ No newline at end of file diff --git a/Source/Images/d_zpm3/u10/IF.HLP b/Source/Images/d_zpm3/u10/IF.HLP new file mode 100644 index 00000000..f4a18dd9 --- /dev/null +++ b/Source/Images/d_zpm3/u10/IF.HLP @@ -0,0 +1,172 @@ +; + IF.COM  + + Author: Jay Sage + Version: 1.6 (includes type 4 for Z34) + Last Update: 4/28/89 + + + 1- Overview 4- Error Messages + 2- Syntax 5- Examples of use + 3- Options + +:1 +Overview of IF.COM + + IF is the transient counterpart of the FCP-resident IF command. IF.COM +tests the indicated condition to see if it is TRUE and, if so, sets the Flow +State to TRUE (allowing the following commands to execute). If the condition +is FALSE, the Flow State is set to FALSE (allowing only Flow Commands to +execute). + + IF.COM is invoked if the current FCP has the IF.COM facility enabled. If +this is the case, whenever an IF command is issued, the FCP will load IF.COM +from the ROOT directory into memory and execute it. The command tail is +passed to IF.COM, and IF.COM acts as a conventional COM file. + + Under NZFCP distributed with NZ-COM and ZCPR vs. 3.4, IF.COM is invoked +only if the indicated condition is NOT included in the condition set +available with the resident FCP. IF.COM may be invoked directly if its +invocation is preceeded by a colon, i.e. :IF .. etc. +:2 + Syntax: (1) IF ARG1=ARG2 + (2) IF REGISTER# value + (3) IF CONDITION ARGUMENTS + + In form (1), the arguments are tested for literal equality and must match +character for character. No space is permitted preceeding or following the +equal sign. A more flexible comparison is provided by using form (3) with the +comparison conditions, i.e. IF EQ ARG1 ARG2 or IF = ARG1 ARG2. This form +permits the use of the condition set EQ, NE, GT, GE, LT, LE as well as their +symbolic equivalents =, <> or ><, >, >=, <, <=. + + In all forms which permit or require the use of numeric values such as form +(2) or the VALUE and REG conditions, numeric entry may be specified as octal, +binary or hexadecimal by appending the letters o, b or h to the number. + + In form (3), only the first two letters of the condition are signifigant. + + A '~' may be used to negate all conditions in forms (2) and (3). +:3 + Options: + +T - Flow State set to unconditionally to TRUE + +F - Flow State set unconditionally to FALSE Š +AMBIG fnme - Flow state set TRUE if file name is ambiguous + +ARCHIVE fnme - Flow state set TRUE if archive attribute set + +BG - Flow state set TRUE if BackGrounder present + +COMPR fnme - Flow state set TRUE if file is squeezed or Crunched + +DS - Flow state set TRUE if DateStamper present + +EMPTY afn,... - if any file in indicated list is EMPTY (size is 0k), + Flow State is set to TRUE + +ERROR [value] - if ZCPR3 error flag is set, Flow State is set to TRUE. If + value is present, flow state is set TRUE only if error + number matches value. + Options (continued): + +EXIST afn,... - if all files in the list exist, Flow State is set to TRUE + +INPUT [text] - user input is enabled, and if user strikes T, Y, , or + , Flow State is set to TRUE. Text appears as a prompt and + may use '^' to generate control characters, %< to display + upper case, %> to display lower case and ctl-a/ctl-b to + enable/disable screen standout mode. + +LIST tail - Flow state set TRUE if tail contains items separated by commas + +NULL afn - if no 'afn' (field is blank), then Flow State is set to TRUE + +PAUSE n [txt} - Like INPUT above except that Flow State is automatically + set to True if user does not respond within n seconds. + +REG m op n - Flow state set TRUE if expression is true. M is a register + number, n is a test value and op is a member of the operator + set =, <>, ><, >, >=, <, <=. All can be negated with '~'. + +RO fnme - Flow state set TRUE if file Read Only attribute set + Options (continued): + +SHELL [name] - Flow state set TRUE if a shell is on shell stack. If name is + present, it is compared to the name on the top of the shell + stack and the flow state set TRUE if they match. Name may + ambiguous. + +SYS fnme - Flow state set TRUE if file Sys attribute set + +TAG fnme n - Flow state set TRUE if attribute n set in file name. + +TCAP [string] - if a Z3TCAP is installed, Flow State is set to TRUE. If + string is present it is compared to the TCAP ID string (up + to the length of the former) and the flow state set TRUE if + they match. String may contain '?' wildcards. + +VALUE m op n - Flow state set TRUE if expression is true. M and n are values + and op is an operater from the set =, <>, ><, >, >=, <, <=. + All can be negated with '~'. + +WHEEL - if WHEEL byte set, Flow State is set to TRUE + Options (continued): + +ZEX - Flow state set TRUE if ZEX is running + +reg# value - if reg (0-31) has indicated value (0-255), Flow State is + set to TRUE + +afn1=afn2 - if two afns are identical in name, Flow State is set to TRUE + + A leading tilde (~) char before a condition negates the effect of the +condition. If the condition is FALSE, the Flow State is set to TRUE. For +each condition, only the first two chars are significant (eg. NU for NULL). + +Examples: "IF ~T" is the same as "IF F" + "IF ~NULL arg" is TRUE if 'arg' is non-blank + "IF ~EXIST afn,..." is TRUE if any 'afn' in the + list does not exist. +:4 +Error Message: + +IF.COM generates several error messages relating to improper entry of +condition arguments. These messages are self-explanatory. See the help for +IF.COM Options for correct syntax for each condition. + +The message 'IF overflow' indicates a nesting beyond the permitted 8 levels. +:5 +Examples of Use: + + a. IF NULL $1 + - if the indicated parameter (from within a SUBMIT or ZEX command file) + is not provided, set the Flow State to TRUE + + b. IF ~EXIST ZEX.ASM,ZEX.ZEX + - if any one of these files does not exist, Flow State is set to TRUE + + c. IF EXIST ZEX.ASM,ZEX.ZEX + - if any one of these files does not exist, Flow State is set to FALSE + (i.e., all files must exist for a TRUE Flow State) + + d. IF NEC=$1 + - if the first passed parameter is the same as the file name "NEC.", + then the Flow State is Set to TRUE + + e. IF 5 7h + - if Register 5 = 7 hexidecimal, the Flow State is Set to TRUE + + Examples (continued): + + f. IF LT $1 FEE + - if the first passed parameter preceeds 'FEE' in alphabetical order, + the flow state is set to TRUE. + + +============================================================================== + + Z-System HELP file on 'flow-control' commands (IF, IF.COM, IFSTAT.COM, +ELSE, FI, XIF, etc.). + \ No newline at end of file diff --git a/Source/Images/d_zpm3/u10/MENU.HLP b/Source/Images/d_zpm3/u10/MENU.HLP new file mode 100644 index 00000000..d4d59aa5 --- /dev/null +++ b/Source/Images/d_zpm3/u10/MENU.HLP @@ -0,0 +1,23 @@ +; + ZHELP - Online Documentation for Z3 Tools + + A - Alias (The ZCPR3 ALIAS Facility) + I - IF (Conditional Command Processor) + V - VLU (Visual Library Utility) + F - ZFiler (Point and Shoot Shell) + Z - ZPatch (File/Disk/Memory Record Patcher) + +ZHELP can also be used to access help files directly by +entering ZHELP followed by the name of the help file. + +The Z3 help files are stored in user area 10. You can view the +directory of this user area for a listing of help topics available. + +To access help for the standard CP/M Plus programs, use the +command HELP. + +:A:ALIAS.HLP +:I:IF.HLP +:V:VLU.HLP +:F:ZFILER.HLP +:Z:ZP.HLP \ No newline at end of file diff --git a/Source/Images/d_zpm3/u10/VLU.HLP b/Source/Images/d_zpm3/u10/VLU.HLP new file mode 100644 index 00000000..cd46ce89 --- /dev/null +++ b/Source/Images/d_zpm3/u10/VLU.HLP @@ -0,0 +1,499 @@ +:VLU106 +Copyright 1987, 1988 Esoteric Software Michal Carson + + +This program is distributed through ZSIG. + + + + +VLU is a ZCPR3-specific library utility with crunch, uncrunch, and +unsqueeze capabilities. It will allow the user to view, +uncompress, or extract library members or to view, uncompress +(uncrunch or unsqueeze, VLU will decide), or crunch disk files. +VLU will build libraries from disk files, crunching the files as +they are added. Date and time stamping is supported. + +The utility will display a list of disk files on the screen along +with a cursor and other information. Placing the cursor next to +the name of a file with the extension .LBR and pressing 'O' for +Open will display the contents of the library and shift the cursor +into that directory. + + +What is a Library? + + +The Library file format was developed to overcome two persistent +limitations of CP/M disk storage capacity: limited directory +entries and large blocking. + +Although CP/M writes files in units of 128 bytes (sectors), most +manufacturers have used larger blocks for storage on disk. On a +Kaypro with double-sided disks, for instance, all files are stored +in 2K blocks; even a file one sector in length will occupy 2K on +the disk. Because the Kaypro disk format allows only 64 directory +entries (files) on one disk, 64 of these 1-sector files (8K) will +fill a 390K disk. + +In a library, a one sector file occupies one sector (plus 32 bytes +for internal directory information). The library itself occupies +only one directory entry on the disk. If 64 one-sector files are +placed into a library, the library will occupy 10K. + +This is obviously an extreme example but illustrates the +usefulness of libraries. Other reasons exist; for instance, +libraries keep related files together. VLU will make files within +libraries more accessible. + + +What is an ARC? Which is better? + + +An ARC is another popular format for storing files in reduced space. +ARCs store files sequentially each preceded by its own header of +information which may be 18 to 29 bytes. LBRs use a 32 byte +directory entry at the beginning of the LBR; the directory +indicates where the matching file may be found. + +The information stored in an ARC file header is similar to the +information stored in an LBR directory. Both formats store the +file name, exact file length, the last modification date and time, +and a CRC-16 for integrity. LBRs also store the file's creation +date and time. + +There are many arguments in favor of each format. To the casual +user, these arguments are not important and either format will +serve for any purpose--long-term or short, organizational, +archival, or for transfer. Neither format is an effective storage +format for sequential access devices such as tape. Anyone who +insists on the absolute superiority of one format over the other +is behaving parochially. + +VLU is the best effort to date in supporting the complete +Novosielski LBR definition. A like effort expended on behalf of +the ARC format might make it similarly useful. + + +In this Library + + +Two executable versions are distributed: VLUxxx-R.COM is for +machines using reverse video as highlighting. VLUxxx-D.COM is for +machines using dim video as highlighting. VLU.FOR is a short +description of the utility; VLU.HLP is full documentation +compatible with the ZCPR3 HELP text display utility. VLU.UPD +contains a trail of the changes in VLU since its initial release. + + +The Help display + + +Pressing "/" or "?" will evoke a display summarizing the commands +available to the user. These commands change according to Wheel +status and directory choice (e.g., Delete is not available in the +library and is never available to non-wheels). Pressing either of +these keys a second time will clear the display. + + +Invoking VLU + + +Use VLU as an extension to a ZCPR3 shell, such as ZFiler, invoked +through a macro. To install it, one need only open the ZFILER.CMD +file with a word processor and enter a line of the form "1 vlu". +Thereafter, positioning ZFiler's cursor beside a library file and +pressing "1" (possibly preceded by [esc]) will bring up VLU with +the library already open. A better thought is to assign this +macro to the "O" key--"O $d1$u1:;vlu" in ZFILER.CMD--so that [esc] +"O" will open the indicated library. + +Some aspects of VLU were designed to make it an extension of +tools like ZFiler. Most of these tools use the ZCPR system files +to store the name of the current file (the file the cursor was +pointing to on exit). VLU checks these filenames and interprets +the current file (sys file 2) as a library. On starting up, VLU +will try to open a library file of this name in the current +directory. + +This allows the user to invoke VLU from the "Z" prompt of ZFiler +or through a macro, pass no parameters, yet open the current file +if it is a library (or shares the same name as a library though it +may have a different extension). If a different library is opened +before returning to ZFiler, VLU will modify the filename in +system file 2 (and thus cause ZFiler to believe it was pointing to +this library when it exited). + +As a command file, VLU can, of course, be invoked from the system +command prompt. If a library name is entered on the command line, +that name will take priority over any name currently in system +file 2 and the library will be opened if it exists. Even from +ZFiler's "Z" prompt, with the pointer on ABC.LBR, entering the +command "VLU DEF" will open DEF.LBR and place that name into +system file 2. + +If the invocation is followed by a DU: specification or by the +name of a ZCPR named directory (DIR:), VLU will display the +filenames in that directory instead of the default directory. +That is, "VLU TOOLS:" will display the contents of the TOOLS: +directory. + +VLU can be re-entered with the GO command. + + +Changing libraries + + +When a library is opened, when VLU is invoked with a library name +in the command line, or when a new directory is logged in with a +library open, the VLU cursor will point into the library +directory. Pressing the [esc] key will shift the cursor back into +the disk directory. Opening a different library is as simple as +positioning the cursor in front of the name and pressing "O". + + +Cursor motion + + +Moving the cursor (pointer) in VLU is effected using the WordStar +cursor motion controls. Ctrl-E moves the cursor up one line, ctrl- +X moves it down. Ctrl-S moves the cursor left one file, ctrl-D +moves it right. Ctrl-F brings a new directory onscreen if there +are more files in the directory (as if the display were moving +right by a directory), ctrl-A moves back toward the beginning of +the file list. Ctrl-F will wrap around from the final directory +to the original. + +The arrow keys will also function if they are properly defined in +the users Environment descriptor. The symbols "+" and "-" (and +"=", the usual non-shifted counterpart of "+") have the same +effect on the directories as ctrl-F and ctrl-A, respectively. + +The [esc] key shifts directories: from the library to the disk, +from the disk to library. In most instances the cursor will +"remember" where it was when it last pointed into that directory. + + +Extracting and Uncompressing library members + + +Positioning the cursor beside the name of a member file within a +library and pressing "E" for Extract will cause the file to be +copied from the library to the disk with no change in form. If it +is crunched or squeezed within the library, it will be crunched or +squeezed on the disk. + +Pressing "U" for Uncompress will cause a crunched file to be +uncrunched onto the disk, a squeezed file to be unsqueezed onto the +disk, or an uncompressed file to be extracted sic. + + +Changing directories + + +Library members may be extracted to a disk and/or user area other +than the one which contains the library. Open the library first, +then use the Login command to move to the destination directory +and perform the extraction or decompression. The move is +accomplished by pressing "L" (cursor position makes no difference +with this command) and answering the prompt "Directory ". The +user may enter a DU: specification (e.g., A0:, B14:, F:) or the +name of a ZCPR3 named directory to be found in the current system +(e.g., MODEM:, DUNGEON:). The colon is not necessary in any case +and an invalid directory or denied access will relog the current +area. No entry at all will also relog the current area to +facilitate disk changes. + + +Tagging files + + +The "T" command will tag an untagged file and the "Y" command will +remove the tag from a tagged file. The tag appears as a hash (#) +immediately following the filename. After a group operation +(actually, during the group operation) hard tags represented by +the hash are replaced by soft tags which appear as an apostrophe +('). Soft tags may be transformed back into hard tags by the +retag command: "#". + +Wildcard tagging is accomplished by pressing "W" and providing a +wildcard file specification in response to the prompt. All files +in the directory, starting from the beginning, which match the +file spec will be tagged. + +Group Tag ("GT") is equivalent to a *.* wildcard tag. + +It may be convenient at times to untag all files. This can be +accomplished by relogging the directory with the Login command. +Certain file operations which affect the disk directory (anything +that adds or deletes files) will also end by relogging the +directory and any remaining tags will be erased. Group operations +obviously prevent this relogging until all files have been +operated upon. + + +Group operations + + +"G" will evoke the Group prompt. From this prompt, several +commands are available to wheels. Only the View and Tag commands +are available to non-wheels. If the wheel byte is not zero, the +prompt will indicate by the first letter of each command that +View, Tag, Uncompress, crunch, and either Extract or Delete and +Build are available. Any selection is carried out on all tagged +files. The Group manager will check for ctrl-C between each file +and abort if it is seen. + + +Viewing files and library members + + +Crunched, squeezed, and uncompressed files may be viewed by +pressing "V" at the VLU prompt. The screen will clear and the +file's name will appear at the top of the screen. Just below the +filename, the file's datestamps, if present, will be shown. These +will be taken from the disk in the case of ordinary text files, +from the library directory if the file is a member, or from the +internal datestamp of a compressed file. The display will stop +with the prompt "[strike any key]" after, in general, 22 lines. +The number of lines (the size of a screen page) is taken from the +ZCPR3 Environment descriptor. The number provided there, by +convention, is two lines less than the screen height. This should +give the viewer two lines of overlap with each page. If this is +unsatisfactory, the user's Environment descriptor can be adjusted. + +At any time during the viewing, pressing ctrl-S will pause the +display. Pressing ctrl-C will abort the operation. Under Group +control, ctrl-C will abort the entire operation and no more files +will be presented; the user will be returned to the file directory +display. Unscreened files will remain tagged. Ctrl-X may be used +to skip to the next file under Group execution; it has the same +effect as ctrl-C if only one file is being viewed; id est, we +abort. + +At the "[strike any key]" prompt, pressing the period will bring +one more logical line to the screen. + +Ctrl-Z will suppress the "[strike any key]" prompt and the file +will scroll continuously. The viewer is then left with ctrl-S to +stop the display (and any key except ctrl-X and ctrl-C will +restart it). Another Ctrl-Z will restore the paging mode and +stop the screen immediately. This is handy for long files when +the interesting text is near the end. + +Certain file extensions are proscripted (e.g., COM, LBR, ARK, SYS, +RCP, REL, PRL, etc.) and VLU will give the message "may not type" +followed by the file name. Crunched files of proscripted types +may have a "stamp" of interest to the user. If a proscripted file +is crunched, VLU will give the filename as it appears and attempt +to extract the original filename and any stamp contained in the +first record of the file. Thus, the datestamps of COM and REL +files may be inspected using the View command. + + +Crunching disk files + + +The crunching faculty of VLU is incidental. Because of the Group +operations, it is quite useful but, ab ovo, crunching files on +disk has been secondary to the project of crunching files into +libraries. Selecting "K" at the command prompt will cause VLU to +crunch the file pointed to by its cursor. + +VLU will first prompt for a "stamp." By convention, this is a +message of up to 40 characters enclosed in square brackets. The +message is imbedded in the first record of the crunched file to be +displayed when the file is either uncrunched or viewed. Often, +the message reports the date of the crunching, the author or +origin of the file, or its expanded size. + +VLU will allow the entry of up to 38 characters. Two characters +are reserved so that VLU can insert the square brackets if the +user omits one or both. VLU will remove any trailing spaces from +the message; if the user wishes the spaces to appear, the user +must enter at least the final closing bracket. + +VLU will also imbed the current datestamp of the file, if present, +in the form established by CRUNCH23D. To use the datestamp in +place of a text stamp simply type a return at the "Enter stamp" +prompt. + +The crunching operation will be aborted if the file is already +compressed (either crunched or squeezed) or is of a proscripted +filetype. Proscripted filetypes are ARC, ARK, BAD, and, if the +target is a library, FOR and CIM (vide infra). + +VLU will allow the user to crunch a member out of a library onto +the disk. There seems no harm in such an operation (and as little +use). + + +File size report + + +The size of a file in kilobytes and sectors (records) is reported +when the user presses either "F" or carriage return. If the file +is a library member, size is reported to the nearest higher 1K. +If the file is on disk, size is reported according to the blocking +factor of the disk. + + +Renaming files + + +To rename a library member or a disk file, position the cursor +beside the file and press "R". Enter the new name at the prompt. + + +Deleting files + + +To delete a library member or a disk file, position the cursor +beside the file and press "D". The user will be prompted to +confirm that the file is to be deleted. Under Group Delete, the +first prompt applies to all tagged files. Answering Yes to this +prompt will delete all files with no further prompts. Answering +No will abort the operation entirely (no files deleted). +Answering Verify will cause a prompt to appear for each file in +the group. + + +Building libraries + + +In order for VLU to build a library, all files to be included +must be located in the same directory. The library may, if +necessary, be built in a foreign directory. + +As distributed, VLU will crunch all files as they are added to the +library (unless the extension is unacceptable as previously +stated). See the configuration section for possible attenuation +of this practice. + +VLU does have limits governing the maximum size library it may +build or deal with. Those limits will vary with the size of the +current TPA. Most users will never approach VLU's limits, but for +curiosity's sake, a TPA of 58K is sufficient to manipulate a +library of over 1200 members. + +To initiate the construction, tag the files which are to be +included and then type "GB" for Group and Build. VLU will prompt +for the name and directory of the "new library" and the number of +entries to allow. A carriage return at the "entries" prompt will +build a library with the minimum directory size, only large enough +to accommodate the currently tagged files. If you specify the +number of members (say, 40), VLU will build a library large enough +to hold at least this many members and will report the actual +number of possible entries ("initializing 43 entries"). + +Rather than count the number of members you have tagged and add to +that the number of members you expect to add in the future, you +may simply enter "+40" to have VLU make the calculation. VLU will +build a library large enough to hold the presently tagged files +and 40 more. If no files are currently tagged, VLU will build an +empty library large enough to hold at least 40 members. + +VLU will then initialize the library directory area. The +directory list (onscreen) will be reset to the first screen; the +cursor will move to the first tagged file and a message will +display that the file is being crunched or added as appropriate. +If any files are to be crunched, the "stamp" prompt will appear +and the stamp thus entered will be in effect for all files +crunched into the library. + +Under this operation, as under the Wildcard and other Group +operations, the directory list onscreen will shift through all +files searching for tags. This will be more noticeable in larger +directories (which require more than one display) and during the +Building of libraries (because of the pause to close the library). + +The datestamps for file creation date/time and file modification +date/time will be copied into the library directory if they are +available on the disk. They may also be imbedded in crunched +members (if no stamp is entered). The current time/date will be +placed in the library directory to indicate when the library was +originally constructed. + + +Adding files to a Library + + +Adding files to an existing library is a simple as constructing +the library in the first place and the files will be compressed +according to the same rules. Date/time stamps will be preserved. +The current date/time will be written into the library directory +to indicate the library's last modification. + +Files may be added individually or tagged and added in a Group +operation. The library to which they will be added must be open +and must have unused directory entries (deleted entries will not +be overwritten). + + +Configuration + + +Several configuration bytes are located in the first sector of the +VLU command file where they may be easily patched. For the exact +location and function of these bytes, see the accompanying file, +VLUxxxC.Z80. + + +Option menu + + +The Option menu, accessed by typing "M" at the command prompt, +will toggle many of the configuration bytes. This allows +temporary assignment of the options. + + +Security + + +The Login command of VLU will use the Z33 parser when it is +available, thus behaving exactly as the operating system would in +allowing admittance to directory areas. When Z33 is not available, +VLU allows immediate entry to any du: or dir: within the MAXDSK +and MAXUSR limits of the environment. Dir: forms outside these +limits are also accepted; passwords, if present, are enforced. + +Commands Delete and Rename and any commands that would create new +files are available only while the wheel byte is set. + + +Contributors + + +VLU directly incorporates the work of many "better makers." My +thanks to all of them as much and more for the opportunity to read +and learn from their code as well as for their library releases. +Among them, the following: + +Richard Conn, Howard Goldstein, Jay Sage, Al Dunsmuir et al. for +SYSLIB, Z3LIB, Z33LIB and VLIB--and ZCPR, itself. + +C.B. Falconer and Steven Greenberg for USQREL, UNCREL, CRN, and +BUFFERS. + +Steven Cohen for M24. Carson Wilson for DATEHL and Z80DOS. +Bridger Mitchell for FRESET and CRUNCH23D. + +Martin Murray for NULU, the standard for these efforts. And Gary +Novosielski for the library format. + + +Note from the programmer + + +VLU, its source code, and its documentation are copyright 1987, +1988 by Esoteric Software. VLU is distributed for non-commercial +use only; any commercial use or any re-distribution in association +with a commercial product or package requires the prior written +permission of the author. + +I may be reached on Z-nodes 2 and 3--Al Hawley's Ladera and Jay +Sage's Newton Centre; both are available via PC-Pursuit. I log +into these nodes approximately semi-weekly. If it is important to +reach me sooner, my home Z-node is #58, operated by Kent Mason in +Oklahoma City (405/943-8638). + \ No newline at end of file diff --git a/Source/Images/d_zpm3/u10/ZFHIST.HLP b/Source/Images/d_zpm3/u10/ZFHIST.HLP new file mode 100644 index 00000000..28494857 --- /dev/null +++ b/Source/Images/d_zpm3/u10/ZFHIST.HLP @@ -0,0 +1,442 @@ +: + ZFILER HISTORY  + + Version 1.1 Jan 9, 1993      Jay Sage  + + 1. Texô  messageó  werå  gathereä  intï á blocë  iî  ordeò  tï  supporô +        languagå  overlayó  aó  explaineä  iî mù  articlå  iî  Thå  Computeò +        Journal®  Englisè  anä Germaî versionó arå supplieä witè thå initiaì +        release. + + 2. The CFG configuration file for use with ZCNFG was extensively + overhauled. + + 3. The source code is now being released. + Versioî 1.0ñ Jaî 9¬ 199² Roâ Friefelä ¦ Jaù Sage + + 1. Addeä  optioî  tï filteò VIE× anä PRINÔ output®  Iæ ON¬  thå  filteò +        removeó thå higè bit¬  theî printó onlù alphanumerics¬  CR¬  anä LF® +        Iæ  assemblù  optioî EXPTA iæ false¬  TABÓ arå  alsï  output®  Thå +        filteò settinç maù bå toggleä froí thå Optionó command. + + 2. ZFILEÒ  caî  remembeò alì thå filå tagó wheî iô returnó  afteò  á  Ú +        commanä  oò macrï run®  Thå filå lisô ió writteî tï á temporarù disë +        filå (ZFILER.TAG¬  iî á configurablå directory)¬  anä  automaticallù +        reaä  back®  Thió featurå caî bå turneä off¬  iæ desired¬  froí  thå +        Optionó command. + + 3. Grouð  Tag/Untaç anä Wilä Tag/Untaç maù bå separatelù seô tï worë oî +        thå entirå ring¬ oò jusô froí thå filå pointer® Grouð Reverså alwayó +        workó oî thå entirå ring®  Toggleó arå oî Optionó list® Lisô macroó +        no÷ sofô taç fileó jusô likå regulaò grouð macros. + + 4. Thå  Optionó lisô ió no÷ 1µ itemó long®  Theså flagó werå previouslù +        saveä witè thå filå mask¬  sï onlù 1± itemó coulä bå reliablù saved® +        Theù  arå no÷ bit-encodeä iî thå lasô twï byteó oæ thå  shelì  stacë +        entry®  (Thå  NOÔ  USESTË  assemblù optioî haó noô beeî  recodeä  tï +        reflecô thió change® Itó limitó werå lonç agï exceeded.) + + 5. Z33OPÔ arå flaç byteó copieä froí ZCPÒ tï á locaì buffer¬  useä  foò +        controllinç  passworä  checking®  Previouó versioî waó checkinç  thå +        wronç  bits®  Sincå thå locaì buffeò ió loadeä aô  runtime¬  iô  waó +        moveä tï DSEG. +  Version 1.0p           May 16, 1991                    Rob Friefeld  + + ---   BUGS FIXED: + 1. Copù  tï existinç r/ï filå failed®  Thå destinatioî attributeó  werå +        neveò  checkeä iæ "Seô Desô Att¢ optioî waó false® + 2. Oî  disë full¬  partiallù copieä filå ió closeä beforå  deletioî  sï +        thaô disë spacå reporô ió no÷ correct. + 3. Iæ  thå ZEØ batcè filå ió locateä iî á specifiã DÕ noô oî thå  path¬ +        ZEØ  won'ô  finä it®  Althougè thå MACROCMDº  patcè poinô  coulä  bå +        altereä tï "ZEØ DU:ZFILER"¬  ZÆ wilì no÷ dï thió automaticallù afteò +        creatinç thå .ZEØ file® Aó á result¬ thå MACROCMÄ entrù MUSÔ uså á ³ +        letteò  commanä  namå  iæ yoõ patcè iô tï ruî somethinç  otheò  thaî +        'ZEX'. + + ---    ALTERATIONS: + 1. DINIT called on exit. + 2. Installatioî optioî availablå tï eraså finisheä ZFILER.ZEØ oî  shelì +        run®  Yoõ  maù  wanô  tï seô thió flaç afteò  debugginç  youò  macrï +        commanä file. + 3. Clear screen on exit (EXITCLS) is config option now. + 4. Grouð  Taç anä Grouð Untaç operatå froí thå filå pointeò tï thå  enä +        oæ thå list¬  ratheò thaî oî thå entirå list® (Thió ió á restoratioî +        oæ somethinç ZÆ useä tï do.© + + ---    NEW: + 1. ZF10P.CFÇ configuratioî filå foò uså witè ZCNFG.COM® Handleó alì thå +        configuratioî excepô macrï strinç installation. + + 2. Grouð Macrï putó taggeä filå counô intï (configurable© useò registeò +        #µ  beforå  running®  Thå macrï caî theî dowî counô  anä  takå  somå +        additionaì  actioî  afteò  iô  haó ruî oî  eacè  taggeä  file®  Thå +        intendeä uså foò thió ió tï creatå librarieó oæ cruncheä fileó  witè +        onå macrï command® Foò example¬ thå script + + 1 ! $d$u:;$!crunch $f M1:;reg m5;if reg 5= 0;$"Library DU ":; +     lput $"Library Name: " M1:*.* +1;era M1:*.?Z?;fi;$h: + +        cruncheó  taggeä fileó tï á fixeä directorù M1º  (oî thå RAÍ  disk)¬ +        makeó  á  librarù  ouô oæ theí (leavinç rooí foò ± morå  file©  witè +        inpuô   foò  librarù  namå  anä  location¬   theî  eraseó  alì   thå +        "crunchettes". + +        Foò  consistency¬  singlå  macroó puô á "1¢ counô iî thå  useò  reg® +        Therå  ió nï codå tï enforcå thå 0..¹ useò registeò range¬  iî  caså +        yoõ wanô tï uså 18..3± (10-1µ arå reserved¬  16-1· arå useä bù otheò +        programs). + +        REÇ  musô  bå á Type³ oò Type´ (oò RCP© foò thå ZEØ GÏ "$!¢  tï  ruî +        CRUNCÈ repeatedly. +  Version 1.0o September 21, 1989 Carson Wilson  + + --- Implementeä lisô capabilitù foò grouð macros®  Iæ á ZFILER.CMÄ scripô +       beginó  witè  á commá (beforå oò afteò thå leadinç  "!¢  shelì  pauså +       parameter)¬  ZFILEÒ  wilì alwayó expanä thå macrï directlù tï  ZCPR'ó +       multiplå commanä line®  Iî thå caså oæ á grouð macro¬  $P¬ $F¬ anä $Ô +       parameteró  arå expandeä tï á lisô oæ alì taggeä files¬  separateä bù +       thå characteò followinç thå leadinç comma® Foò example¬ thå script + +         ± ¬ !echï thå taggeä fileó arå $F + +       displayó  "THÅ TAGGEÄ FILEÓ ARÅ "¬  followeä bù á lisô oæ thå  taggeä +       fileó separateä bù ¢ ¢ oî thå consolå (assuminç ECHÏ ió á valiä  ZCPÒ +       command)®  ZFILEÒ  theî  promptó  foò á  keypresó  beforå  returning® +       Pleaså  notå thaô thió macrï bufferó thå entirå lisô oæ taggeä  fileó +       tï CP/M'ó commanä linå buffer¬ whicè ió onlù 12µ byteó iî length® Thå +       abovå commanä thereforå normallù causeó á "Commanä toï long¢ erroò iæ +       morå  thaî abouô ¸ fileó arå tagged¬  dependinç oî thå lengthó oæ thå +       taggeä names. + + --- Madå  thå  escapå characteò anä thå characteró tï quotå witè iô  wheî +       writinç á grouð macrï commanä tï ZEØ oò SUBMIÔ fileó installablå (seå +       ZFCNFN.Z8°  foò  details)®  Escapå characteò ió  currentlù  "$"¬  anä +       quoteä characteró arå currentlù "$"¬ "|"¬ anä "<"¬ aó requireä bù ZEØ +       versioî 5. + + --- Iô  ió no÷ possiblå tï answeò ZFILER'ó "Logiî [DIR][:MASKÝ  ¢  prompô +       witè  ".¢  (samå aó ":"© tï loç iî alì fileó iî  thå  currenô +       directory®  Previouslù  thió loggeä iî alì fileó aô useò zerï oæ  thå +       currenô  drive®  Thå  Logiî commanä no÷ alsï seemó tï  accepô  ".afn¢ +       insteaä oæ ":afn". + + --- Shelì biô seô whilå waitinç foò useò commands¬ theî immediatelù reseô +       wheî á commanä keù ió received®  Thió shoulä allo÷ Bridgeò Mitchell'ó +       AÔ  commanä  scheduleò  RSØ tï interrupô ZFILEÒ aó iô woulä  thå  CCÐ +       whilå ZFILEÒ ió promptinç foò commands. + + --- Fixeä buç whicè preventeä anù Grouð commandó froí operatinç wheî  thå +       configuratioî bytå MACFLAÇ waó patcheä tï false. + + --- Removeä codå whicè attempteä tï uså thå ZCPÒ Environmenô tï determinå +       ho÷  manù  lineó oæ texô tï displaù witè thå Ö command¬  aó thió  waó +       onlù partiallù implemented¬ resultinç iî difficultù oî terminaló witè +       oveò 2´ rows®  Texô lineó no÷ hard-codeä tï 22¬ sï ZFILEÒ shoulä worë +       oî anù terminaì witè 2´ oò morå screeî rows. + + --- Fixeä  smalì  buç  whicè  causeä  "Publiã  Filå  Error¢  messagå   tï +       disappear. + + --- Control-É ió no÷ aî aliaó foò control-Ê (jumð tï filå again)¬ foò uså +       witè  terminaì definitionó (TCAPs© whicè definå control-Ê aó aî arro÷ +       key. + + --- Ideá  foò futurå versionsº  iô woulä bå verù difficulô tï allo÷  filå +       rinç  sortinç  baseä oî filå dateó (woulä neeä tï storå  datå  stampó +       witè ring¬ requirinç majoò rewrite)® Iô woulä bå relativelù simplå tï +       allo÷  thå Logiî anä startuð commandó tï accepô anä matcè fileó tï  á +       datespec¬ though... +  Version 1.0n March 20, 1989 Carson Wilson  + + --- Addeä $ª scripô parameteò tï cleaò thå screeî during¬ anä rebuilä iô +       afteò á scripô prompt® Usagå ió $"$*..text..¢ Prompô texô appearó aô +       thå toð oæ á cleareä screen. + + --- Iî  anticipatioî  oæ  thå  ZEØ 4.0x¬  '$§ anä '|§ arå  no÷  thå  onlù +       characteró  whicè arå quoteä wheî ZFILEÒ buildó thå ZFILER.ZEØ  file® +       Iæ  theù  appeaò iî á ZFILER.CMÄ scripô theså characteró  arå  quoteä +       witè  á  leadinç  dollaò sigî wheî ZFILER.ZEØ  ió  built®  Alì  otheò +       characteró  arå passeä "aó is¢ tï thå ZEØ file®  Thió meanó thaô  yoõ +       cannoô uså ZEØ directiveó sucè aó $1¬  |crlf|¬  etc® dynamically¬ buô +       thió waó neveò thå intentioî oæ thå interfacå betweeî ZFILEÒ anä ZEØ +       - thå  ideá  waó simplù tï providå multiplå executionó oæ  á  ZFILEÒ +       commanä script. + + --- For efficiency ZFILER no longer resets disks under Z3PLUS. + Version 1.0m January 24, 1989 Carson Wilson + + --- Copù anä Movå commandó no÷ worë properlù witè Z3PLUS¬ anä thå currenô +       12-houò timå showó aô thå toð oæ thå screeî undeò Z3PLUS. + + --- Patè  fileó  conflictó  arå nï  longeò  reporteä  undeò  ZSDOS®  Thió +       produceä  problemó  wheî useró trieä tï backuð directorieó alonç  thå +       path® ZSDOÓ publiã conflictó arå stilì reported. + + --- Grouð Movå anä Grouð Copù erroò messageó arå no÷ sustaineä untiì  thå +       useò strikeó á key¬ ratheò thaî disappearinç immediately. + + --- Promptó iî scriptó maù no÷ contaiî controì characteró aó "^x¢ wherå ø +       ió anù characteò froí À tï z. + + --- Thå dollaò sigî ió no÷ aî "escapå character¢ foò scripô prompts¬  anä +       caî bå useä tï includå thå $¬ ^¬ "¬ anä § characteró iî prompts. + + Examples: + Script Result + $"Enter name or $^C: " Enter name or ^C: + $"Type $"//$" for help: " Type "//" for help: + + --- Movå  anä  Grouð Movå no÷ uså thå optioî menõ selectionó wheî  movinç +       fileó insteaä oæ alwayó copyinç filå attributes. + + --- Cleaned up help screen. + + --- Tï  savå  debugginç  timå  É havå changeä  thå  defaulô  optioî  menõ +       settingó tï mù preferences. +  Version 1.0l September 13, 1988 Carson Wilson  + + --- Bugó iî thå Grouð Copy¬  Grouð Move¬  anä Grouð Deletå commandó whicè +       occurreä wheî thå useò aborteä certaiî promptó witè á control-à havå +       beeî corrected. + + --- Á  buç whicè resulteä iî erratiã behavioò iæ ZFILER.CMÄ waó noô founä +       waó fixed. + + --- Thå unsQueezå commanä waó removeä tï reducå prograí size®  Sincå  fe÷ +       ne÷ fileó arå squeezeä anä UNCRUNCÈ no÷ supportó unsqueezinç iô seemó +       preferablå  tï exporô thió functioî bù callinç UNCRUNCÈ witè á ZFILEÒ +       macro®  Aî  additionaì  benefiô oæ thió techniquå ió  thaô  á  singlå +       commanä no÷ maù bå useä foò botè uncrunchinç anä unsqueezing® ZFILEÒ +       wilì  stilì  displaù  anä  prinô  squeezeä  files® + + --- ZFILEÒ  no÷ performó consistentlù wheî invokeä witè ZCPR'ó GÏ oò JUMÐ +       commands®  Previouó  versionó  faileä wheî morå thaî onå Ú  oò  macrï +       commanä waó useä afteò invokinç ZFILEÒ witè GO/JUMP. + + --- (DateStampeò  versionó  only)®  Iæ á DateStampeò (BDOÓ  functioî  12© +       real-timå clocë ió available¬  thå currenô 12-houò timå ió printeä tï +       thå lefô oæ thå "ZFILER¢ namå aô thå toð oæ thå screen. + + --- Iæ  runninç  undeò ZSDOÓ oò ZDDOS¬  disë resetó arå skippeä  foò  alì +       commandó  excepô Space®  Thió considerablù increaseó  performancå  oî +       floppù disë systems. + + --- Thå  environmenô addresó ió loadeä froí locatioî 10¹ aô ruî timå  foò +       compatibilitù witè ZCPÒ 3.° systems. + + --- Thå  performancå  oæ thå Delete¬  Copy¬  Move¬  Archive¬  anä  Renamå +       commandó haó beeî improveä bù minimizinç disë accesses. + + --- Thå  Spacå  commanä  no÷  resetó  individuaì  drives®  Thió  ió  morå +       efficienô anä makeó thå Ó commanä practicaì witè virtuaì drives. + + --- Thå  Spacå  anä Filesizå commandó no÷ worë  properlù  afteò  printinç +       files. + + --- ZFILEÒ  no÷  checkó  foò anä reportó Publiã oò  Patè  conflictó  wheî +       Copying¬  Renaming¬  Moving¬  anä  Archivinç  fileó undeò  ZSDOS®  Iî +       addition¬  fileó  createä  witè Copy/Move/Archivå arå alwayó  seô  tï +       Privatå (higè biô oæ f2=0© tï avoiä possiblå ZSDOÓ Publiã conflicts. + + --- Single file View no longer affects file tags. + + --- Single file Copy or Move no longer skip to the next file afterwards. + + --- (DateStampeò  versionó only)®  Iæ  available¬  Copy/Move/Archivå  uså +       ZSDOS'ó  morå  efficienô  Geô Stamð anä Seô Stamð functioî  calló  tï +       preservå  filå  datestampó  acrosó  copies®   Thå  oldeò  methoä   oæ +       datestampinç ió retaineä foò non-ZSDOÓ systems. + + --- Rename error messages no longer disappear before they can be read. +  Version 1.0k May 3, 1988 Jay Sage  + + --- Fixeä  somå  minoò  bugó iî thå codå (slipuð oæ usinç á  D  wherå  É +       intendeä á DÓ tï allocatå spacå foò longeò stringó tï bå patchable) +  Version 1.0j May 1, 1988 Jay Sage  + + --- Improveä  flexibilitù oæ grouð macrï operatioî bù puttinç threå itemó +       iî thå configuratioî page®  Thå strinç MACROCMÄ ió á  null-terminateä +       strinç witè thå grouð macrï commanä line®  Thå strinç MACLEADSTÒ ió á +       null-terminateä  strinç oæ characteró tï bå writteî aô thå  beginninç +       oæ  thå grouð macrï batcè filå (ZEØ oò SUBMIT)®  Thå strinç  MACROFC +       markó  thå  filå  controì  blocë foò thå filå witè  thå  grouð  macrï +       commands. + + --- Fixeä á buç iî thå expansioî oæ filå nameó iî macrï  parameters®  Thå +       codå  waó noô maskinç ouô filå attributes®  Iô happeneä thaô thió diä +       noô matteò excepô foò blankó witè attributeó set¬ buô no÷ thå codå ió +       correct. + + --- Therå  ió  aî additionaì configuratioî optioî (lasô  bytå  iî  CONFIÇ +       sectioî jusô beforå OPÔ string© thaô allowó filå nameó tï bå showî iî +       loweò case® Seô thå bytå tï FÆ foò uppeò case¬ ° foò loweò case. +  Version 1.0i April 14, 1988 Jay Sage  + + --- Thå  sourcå codå tï 1.0è waó losô iî á disë crash®  Thå featureó havå +       beeî recreated® Thå operatioî witè GÏ oò JUMÐ haó beeî improved. + + --- Thå  shelì stacë entrù no÷ includeó thå expliciô directorù wherå  thå +       filå waó founä iæ Z3³ oò lateò ió running. +  Version 1.0h September 16, 1987 Jay Sage  + + --- Fixeä buç iî thå codå foò logginç iî thå displayeä directorù wheî thå +       Ú  commanä ió executed®  Iô waó noô workinç correctlù undeò  standarä +       ZCPR3° oò BackGrounder. + + --- Addeä optionó tï controì ho÷ thå ZFILER.CMÄ macrï filå ió located®  Á +       choicå  oî thå optioî menõ allowó onå tï turî oî oò ofæ searchinç  oæ +       thå entirå patè (includinç currenô directory)® Iæ thaô optioî ió off¬ +       eitheò  thå  rooô directorù oò á specifieä directorù  wilì  bå  used¬ +       dependinç oî thå configuratioî oæ thå program. + + --- ZFILEÒ caî no÷ bå reexecuteä usinç thå GÏ command® É borroweä á tricë +       froí Aì Hawley®  Wheî invokeä originally¬ ZFILEÒ checkó thå namå useä +       tï invokå it®  Iæ thå namå ió 'GO'¬  theî á defaulô namå storeä iî aî +       internaì  buffeò ió used®  Otherwise¬  iæ thå namå ió copieä tï  thaô +       buffeò froí thå externaì FCB® É believå thió changå wilì allo÷ ZFILEÒ +       tï bå reconfigureä usinç thå 'O§ commanä anä theî cloneä usinç SAVE. + + --- Á  morå  extensivå  useò configuratioî filå haó beeî  provideä  whicè +       compriseó sectionó oæ ZFHDR.Z8° (thå definitionó file© anä ZFMAIN.Z8° +       (thå beginninç oæ thå maiî code)®  Aó before¬  reconfiguratioî caî bå +       carrieä  ouô bù editinç ZFCNFH.Z8° anä assemblinç iô tï á  HEØ  file® +       Theî "MLOAÄ ZFNEW=ZF0LD.COM,ZFCNFH¢ wilì instalì thå changes. +  Version 1.0g August 23, 1987 Carson Wilson  + + --- Reverså video¬ dim¬ oò '_§ arå no÷ useä oî thå statuó linå tï displaù +       alì currenô filå attributes® 'r§ read-onlù tagó werå interferinç witè +       thå tagginç commandó anä arå nï longeò used. + + --- Corrected bug preventing view/unsqueeze of squeezed system files. + + --- Correcteä  buç  causinç squeezeä read-onlù fileó tï bå unsqueezeä  aó +       systeí fileó wheî copyinç attributes. + + --- Made invalid command display show actual command. + + --- Removeä  ZFILEÒ  commanä prompô oî exiô tï  avoiä  confusioî  betweeî +       systeí anä ZFILER'ó 'Z§ command® + + --- Fixeä initiaì helð screeî froí commanä line. + + --- Added space before 'OK' after copy/unsqueeze. +  Version 1.0f June 12, 1987 Jay Sage  + + --- Addeä  rudimentarù grouð macrï capabilitù bù havinç ZFILEÒ  construcô +       anä invokå á ZEØ script. +  Version 1.0c April 27, 1987 Jay Sage  + + --- Madå changeó foò compatibilitù witè ZCPR33®  No÷ oî manuaì invocatioî +       ZFILEÒ returnó tï thå commanä processoò iæ ZEØ ió runninç sï thaô ZEØ +       caî continuå tï supplù commandó eveî iæ therå arå nï morå commandó oî +       thå currenô commanä line. +  Version 1.0b January 13, 1987 Jay Sage  + + --- Addeä DateStampeò support® No÷ oî filå copieó anä moves¬ thå timå anä +       datå stamð oæ thå sourcå filå ió carrieä tï thå destination. + + --- Modifieä handlinç oæ directorieó witè 'L§ (oò 'N'© command®  Thå codå +       no÷ respectó thå DUOË flaç iî thå environment® Iæ iô ió set¬ theî anù +       attempô  tï  loç iî á directorù ió checkeä againsô thå  maxdrivå  anä +       maxuseò  valueó iî thå environment®  Iæ thå requesô passeó thaô test¬ +       theî thå directorù ió loggeä iî eveî iæ iô haä beeî specifieä usinç á +       DIÒ  forí  anä  eveî iæ á passworä ió associateä  witè  it®  Iæ  thå +       requesteä  directorù ió beyonä thå maxdrive/maxuseò rangå oò iæ  DUOË +       ió noô set¬  theî thå nameä directorù registeò ió checked®  Passwordó +       arå  checkeä  aó  needeä  usinç  improveä  codå  (shorteò  anä   morå +       accurate). + + --- Addeä smalì biô oæ codå tï thå ZFILEÒ initializatioî tï changå thå JÐ +       opcodå  aô 100È tï REÔ sï thaô ZFILEÒ cannoô bå reruî usinç thå  "GO¢ +       commanä (thió haó baä consequences¬  sincå thå shelì stacë entrù theî +       woulä havå thå commanä "GO¢ storeä iî it!). +  Version 1.0a January 1, 1987 Jay Sage  + + --- IMPORTANÔ  CHANGEº  Tï  makå  ZFILEÒ  consistenô  witè  otheò  scripô +       processinç programs¬  thå defaulô parameteò designatioî characteò haó +       beeî changeä froí '%§ tï '$'® Thå parameteò '$'¬ aî alternatå foò 'P§ +       (completeä   Pointed-to-filå   specº   du:name.typ©   waó   thereforå +       eliminated® Theså changeó wilì requirå editinç oæ anù VFILER.CMÄ filå +       wheî convertinç tï ZFILER.CMÄ (thå macrï filå useä bù ZFILER). + + --- Modifieä thå waù thå shelì stacë entrù ió used® Iô no÷ keepó botè thå +       originaì directorù anä thå requesteä directorù oî thå stack® Thió haó +       twï majoò advantages® Bù keepinç thå originaì directorù oî thå stack¬ +       oî  exiô witè thå 'X§ commanä ZFILEÒ caî returî tï thå directorù froí +       whicè iô waó originallù invokeä nï matteò ho÷ manù otheò  directorieó +       havå beeî loggeä iî bù uså oæ thå 'Z§ commanä oò á macro® Keepinç thå +       requesteä (displayed© directorù oî thå shelì stacë avoidó thå probleí +       witè  á passwordeä directorù oæ havinç tï reenteò thå passworä  everù +       timå ZFILEÒ returneä froí á macrï oò 'Z§ operation. + + --- Addeä  featurå witè 'Z§ commanä tï allo÷ avoidancå oæ thå "Strikå anù +       Key¢  messagå oî returî tï ZFILER®  Iæ thå commanä linå entereä  witè +       thå  'Z§  commanä beginó witè á space¬  theî shelì  waitinç  wilì  bå +       disabled. + + --- Fixeä á buç causeä bù thå GÁ command®  Oncå thaô commanä waó used¬ aî +       archivinç  flaç  waó seô anä neveò cleared®  Á lateò Gà commanä  theî +       useä  thå group-archivå promptinç optionó insteaä oæ  thå  group-copù +       options® Thió flaç ió no÷ reseô beforå alì grouð operations. + + --- Addeä  optioî  oî  filå  copù anä unsqueezå functionó  tï  allo÷  thå +       destinatioî  filå  attributeó tï bå seô tï thoså oæ á  filå  thaô  ió +       beinç overwritten® Iæ therå ió nï sucè filå anä thå attributå settinç +       optioî ió engaged¬ theî thå sourcå filå attributeó wilì bå used. + + --- Changeä  codå foò filå viewinç sï thaô scrollinç wilì stoð aô thå enä +       oæ á filå (carriagå returî anä spacå wilì noô causå thå nexô filå  tï +       start)®  Onlù  control-Ø oò control-à wilì bå effectivå aô thå enä oæ +       file®  Alsï addeä control-Ú optioî tï gï straighô tï thå enä oæ  filå +       withouô paging. + + --- Changeä thå codå thaô allowó interruptioî oæ grouð operationó sï thaô +       á  carriagå  returî wilì noô causå aî aborô (manù useró  answeò  somå +       promptó  thaô  requirå nï returî witè á returî anä founä  thaô  grouð +       operationó  workeä onlù oî thå firsô file)®  Anù characteò otheò thaî +       carriagå returî wilì aborô masó operations. + + --- Madå iô possiblå tï geô thå macrï helð screeî bù pressinç thå  leadiî +       characteò  á  seconä timå iî responså tï thå 'Macro§ prompô (thió  ió +       mucè easieò anä morå naturaì thaî enterinç thå '#§ command). + + --- Fixeä  á  buç  iî thå macrï helð displaù  thaô  waó  echoinç  controì +       characteró tï thå screen®  Theså arå no÷ filtered®  Onå caî no÷ enteò +       ESà ESà tï seå thå macrï helð screeî anä á thirä ESà tï returî tï thå +       filå display. + + --- Addeä  ^Ê  commanä  tï jumð tï nexô filå thaô matcheó thå  lasô  masë +       specifieä  iî á "J¢ command®  Thió allowó onå tï jumð easilù  tï  thå +       seconä oò thirä filå thaô matcheó á simplå filå spec. + + --- Fixeä  buç  iî "M¢ anä "GM¢ commandó thaô alloweä sourcå filå  tï  bå +       deleteä, eveî wheî copù haä noô beeî performed. + + --- Addeä "GR¢ grouð reverså functioî tï reverså filå tags®  Taggeä fileó +       becomå untagged»  untaggeä fileó becomå tagged» anä soft-taggeä fileó +       remaiî soft-tagged. + + --- Cleaneä  uð  loç commanä ("L¢ oò "N")®  Removeä speciaì  handlinç  oæ +       answeò  "x¢  foò directorù namå (nï longeò needed© anä changeä  erroò +       handlinç code. + + --- Addeä  á configuratioî optioî tï automaticallù loç iî  thå  displayeä +       directorù foò thå "Z¢ commanä iæ thå useò numbeò ió lesó thaî 16®  Iæ +       thió  optioî ió configureä in¬  theî thå disë systeí ió reseô  beforå +       thå  commanä  linå  ió ruî (thió ió thå disadvantagå  oæ  usinç  thió +       option). + + --- Made rename command preserve all file attributes. + + --- Addeä  optionó  tï copù attributeó witè fileó anä tï alwayó  seô  thå +       archivå  attributå  iî  thå destinatioî file®  Iæ thå filå  ió  beinç +       moved¬  thå attributeó arå alwayó copieä anä thå archivå biô ió  lefô +       aó  iô was»  iæ thå filå ió beinç unsqueezed¬  thå attributeó arå noô +       copieä anä thå archivå biô ió seô accordinç tï itó optioî setting. + +--------------------------------------------------------------------------- + + See the VFHIST.DOC file in VFILER Version 4.1 for the history of the + development of VFILER. +  \ No newline at end of file diff --git a/Source/Images/d_zpm3/u10/ZFILER.HLP b/Source/Images/d_zpm3/u10/ZFILER.HLP new file mode 100644 index 00000000..029adac4 --- /dev/null +++ b/Source/Images/d_zpm3/u10/ZFILER.HLP @@ -0,0 +1,545 @@ +; ------------------------------------- + | | + | ZFILER, The Point-and-Shoot Shell | + | | + ------------------------------------- + + + Z >> Introduction to Z-System Shells + F >> What is ZFILER for? + + +I >> Invoking ZFILER  C >> ZFILER Commands  +D >> The ZFILER Display S >> Single-File Built-In Functions +O >> Thå Optioî Commanä G >> Built-In Group Commands +J >> Credits P >> Pointeò Commandó +H >> History of ZFILER N >> Otheò Non-Filå Commandó + + + M >> ZFILER's Script Macro Facility +:Z + Z-System Shells  + + Á  Z-Systeí shelì ió á prograí thaô takeó oveò thå user-inpuô  functioî +oæ  thå  commanä  processor®  Thå  waù  thió workó  ió  thaô  thå  Z-Systeí +environmenô  includeó á speciaì areá iî memorù calleä thå shelì stacë  wherå +shelì  commanä lineó caî bå kept®  Wheneveò thå ZCPR³ commanä processoò  ió +finisheä  processinç  alì thå commandó thaô havå beeî passeä tï  iô  iî  thå +commanä  linå buffeò (anotheò speciaì areá iî memory)¬  iô checkó thå  shelì +stack®  Onlù iæ nï commanä linå ió presenô therå doeó thå commanä processoò +itselæ  prompô thå useò foò thå nexô commanä line®  Iæ therå ió aî entrù iî +thå  shelì stack¬  theî thaô commanä linå ió ruî instead¬  anä thå  useò  nï +longeò seeó thå commanä processoò directly. + + Somå  shells¬  likå thå EASÅ oò LSÈ historù shell¬  whilå makinç á  biç +changå iî ho÷ thå systeí ió actually‚ running¬  makå relativelù littlå changå +iî  ho÷  iô appears‚ tï run®  Á commanä prompô ió stilì presented¬  anä  onå +enteró  commandó morå oò lesó aó usual®  Thå differencå ió thaô onå  haó  á +morå  capablå  editoò  aô one'ó disposal¬  anä thå commandó arå saveä  tï  á +historù filå froí whicè theù caî bå recalled¬  edited¬ anä ruî again® Aó wå +shalì see¬  thå ZFILEÒ shelì presentó thå useò witè á dramaticallù differenô +useò interface. +:F + Whaô ió ZFILEÒ For¿  + + Historically¬  ZFILEÒ  ió á descendanô iî thå linå oæ filå  maintenancå +utilitieó  likå SWEEÐ anä NSWÐ (hencå thå "filer¢ parô oæ thå  name)®  Filå +maintenancå  ió  generallù concerneä witè copyinç files¬  lookinç  aô  theiò +contents¬ renaminç them¬ erasinç them¬ anä sï on® ZFILEÒ provideó alì theså +functionó anä more. + Š ZFILER'ó  immediatå parenô waó VFILER¬  wherå thå "V¢ stooä foò  video® +Thå  TCAÐ facilitù iî Z-Systeí makeó iô easù foò programó tï takå  advantagå +oæ  thå full-screeî capabilitieó oæ whateveò videï displaù terminaì  happenó +tï bå iî uså aô anù time®  Iî contrasô tï applicationó undeò CP/M¬ Z-Systeí +programó neeä noô bå configureä tï matcè thå terminal®  Iô was¬  therefore¬ +naturaì tï builä á filå maintenancå prograí iî whicè thå fileó arå displayeä +graphicallù  oî thå screen®  Wheî É decideä tï explorå somå ne÷  directionó +witè VFILER¬  tï avoiä confusioî É gavå thå prograí thå ne÷ namå ZFILER¬ foò +Z-Systeí Filer. + Thå  filå maintenancå taskó describeä abovå woulä noô requirå á  shell® +Makinç thå prograí á shell¬  however¬  allowó iô tï gï beyonä thå  functionó +includeä iî thå program'ó owî code®  Becauså á shelì caî pasó commanä lineó +tï thå operatinç system¬  ZFILEÒ caî perforí anù operatioî thaô thå computeò +ió capablå of® Likå á menõ system¬ however¬ iô helpó thå useò bù generatinç +thå commandó automaticallù aô thå toucè oæ á key. + + Wheî  ZFILEÒ  ió  running¬  thå screeî ió filleä witè  aî  alphabetizeä +displaù oæ thå fileó iî á specifieä directory¬  anä therå ió á pointeò  thaô +thå  useò  caî manipulatå usinç cursoò controì keys®  Iæ wå haä á mouså  tï +movå thå pointer¬  iô woulä bå á littlå likå havinç á Macintosh®  Actually¬ +iô woulä bå á loô more® Iô woulä bå likå havinç á mouså witè fiftù buttons¡ +Oncå  thå pointeò haó beeî positioneä oî á file¬  pressinç á keù (oò twï  oò +three©  causeó  anù oæ á greaô numbeò oæ functionó tï bå invokeä tï  acô  oî +thaô file® Wå wilì describå ho÷ thió workó iî morå detaiì shortly. +:I + Invokinç ZFILEÒ  + + Sincå  ZFILEÒ  performó  full-screeî  operations¬   á  propeò  Z-Systeí +terminaì  descriptoò  (TCAP© musô havå beeî loaded®  Iæ yoõ havå  noô  donå +that¬  oò  iæ  yoõ havå selecteä á terminaì thaô doeó noô  supporô  alì  thå +functionó  ZFILEÒ needs¬  theî ZFILEÒ wilì givå yoõ aî erroò  message®  Thå +TCAP¬  unfortunately¬  doeó  noô  includå informatioî abouô whetheò  dií  oò +reverså  videï  ió  useä  bù thå terminal¬  anä sincå theså  twï  modeó  foò +highlightinç  regionó  oî  thå  screeî arå  sï  different¬  ZFILEÒ  ió  madå +availablå iî separatå versionó foò each. + + Therå  ió  alsï aî optioî tï havå eitheò fouò oò fivå columnó  oæ  filå +nameó iî thå display®  Personally¬  É prefeò thå four-columî version¬ whicè +giveó  aî uncluttereä screeî witè plentù oæ restfuì whitå spacå anä  á  verù +distinct¬  easy-to-spoô  pointer®  Otheró thinë iô ió morå importanô tï  bå +ablå  tï seå thå maximuí numbeò oæ fileó oî eacè screeî anä prefeò thå five- +columî display. + + Theî therå ió thå issuå oæ supporô foò timå anä datå stampinç oæ files® +ZFILEÒ  containó  thå  codå foò preservinç thå timå stampó  wheî  fileó  arå +copied®  Sï  aó noô tï inflicô thå overheaä oæ thió codå oî thoså whï  havå +noô  implementeä DateStampeò (thougè theù shoulä dï that!)¬  ZFILEÒ ió  alsï +provideä iî versionó witè anä withouô thå DateStampeò code. + Iæ wå supporteä alì combinationó oæ thå abovå choices¬  therå woulä  bå +eighô  differenô versionó oæ ZFILER®  Typically¬  thå distributioî  librarù +containó fouò oò fivå oæ thå combinations®  Foò example¬ á five-columî filå +displaù  ió  noô  particularlù compatiblå witè reverså  videï  highlighting¬ +becauså  thå  reverså  videï oæ taggeä fileó  runó  intï  thå  reverse-videï +pointer. + + Wheî  yoõ  geô  ZFILER¬  yoõ havå tï chooså whicè versioî  yoõ  prefer¬ +extracô iô foò thå distributioî library¬ anä givå iô á workinç namå (somå oæ +thå  earlù  Z-Systeí shelló haä tï havå á specifiã name¬  buô yoõ  caî  givå +ZFILEÒ anù namå yoõ like)® É prefeò thå namå ZF¬ sincå iô ió verù quicë anä +easù tï type¬ anä É wilì uså thaô namå iî alì thå exampleó thaô follow. + + Thå generaì syntaø foò invokinç ZFILEÒ is: + + ZÆ filespec + +wherå  "filespec¢ ió á standarä Z-Systeí ambiguouó filå specificatioî  (thaô +is¬  iô  maù  contaiî thå wildcarä characteró "?¢ anä  "*")®  Thå  filespeã +selectó  thå directorù areá anä thå fileó froí thaô areá tï bå  includeä  iî +thå screeî display. + Variouó partó oæ thå filespeã caî bå omitted®  Iæ nï filespeã ió giveî +aô all¬ theî "*.*¢ foò thå currentlù loggeä directorù ió assumed® Similarly¬ +iæ onlù á directorù ió specifieä (e.g.¬  Bº oò 3º oò B3º oò WORK:)¬ theî alì +thå  fileó ("*.*"© iî thaô directorù arå displayed®  Iæ á filå name/typå ió +included¬  theî iô wilì servå aó á masë oî thå fileó tï bå displayed®  Thuó +"ZÆ WORK:*.DOC¢ wilì sho÷ onlù fileó oæ typå DOà iî thå directorù WORK:. + + Thå  directorù anä filå masë caî botè bå changeä froí inside‚ ZFILEÒ  aó +welì usinç thå "L¢ oò LOÇ command®  É brinç thió uð no÷ becauså therå ió  á +confusinç  differencå iî thå waù thå "L¢ commanä works®  VFILEÒ  originallù +alloweä  onå tï changå onlù thå directorù anä noô thå filå masë froí  insidå +thå  program®  Tï  savå  thå useò thå troublå oæ typinç thå coloî  afteò  á +directory¬   itó  inclusioî  waó  madå  optional®  Sincå  useró  becamå  sï +accustomeä tï thió shorthand¬  iô waó carrieä oveò intï ZFILER®  Becauså oæ +this¬ iæ yoõ wanô tï changå onlù thå filå mask¬ yoõ musô remembeò tï precedå +iô  witè  á  colon®  Otherwiså youò masë wilì bå takeî aó  thå  namå  oæ  á +directorù (whicè generallù resultó iî aî erroò message). + + Iô  ió  no÷ possiblå tï answeò ZFILER'ó "Logiî [DIR][:MASKÝ ¢ prompô  witè +".¢  (samå  aó ":"© tï loç iî alì fileó iî  thå  currenô  directory® +Previouslù thió loggeä iî alì fileó aô useò zerï oæ thå currenô drive®  Thå +Logiî commanä no÷ alsï seemó tï accepô ".afn¢ insteaä oæ ":afn". + Onå  brieæ asidå foò programmeò types®  ZFILEÒ caî bå loadeä froí  anù +directory® Onå oæ thå speciaì featureó oæ Z-Systeí sincå versioî 3.³ oæ thå +commanä  processoò  allowó á prograí tï finä ouô botè itó owî namå  anä  thå +directorù froí whicè iô waó actuallù loaded¬ perhapó aó thå resulô oæ á patè +search®  ZFILEÒ  buildó  thå shelì stacë entrù tï invokå ZFILEÒ  undeò  itó +currenô  namå  froí  thå directorù iî whicè iô ió  actuallù  located®  Thió +sometimeó  makeó iô ruî faster¬  anä iô allowó ZFILEÒ tï bå invokeä  froí  á +directorù thaô ió noô oî thå searcè path. +:D + Thå ZFILEÒ Displaù  + + Thå maiî ZFILEÒ displaù containó threå parts® Aô thå toð oæ thå screeî +therå  ió á messagå line®  Iî thå versioî oæ ZFILEÒ thaô ió currenô aô  thå +timå É aí writinç thió columî (versioî 1.0L)¬  thió linå contains¬ froí lefô +tï right¬ thå followinç information: + + (1)  thå  directorù thaô haó beeî selected¬  iî botè DÕ anä DIÒ  (nameä +          directory© format; + + (2)  thå  indicatoò  "[PUBLIC]¢, iæ thaô directorù ió  á  ZRDOÓ  publiã +          directorù (iæ yoõ don'ô kno÷ whaô thió is¬ jusô ignorå it); + + (3)  thå  currenô  timå  oæ daù iæ DateStampeò oò onå oæ thå  ne÷  DOSó +          (ZSDOÓ oò ZDDOS© ió running; + + (4)  thå program'ó officiaì namå anä version; + + (5)  thå texô strinç "Currenô File:"; + +and  (6)  thå  namå oæ thå filå currentlù beinç pointeä tï (thió changeó  aó +          thå pointeò ió moved). + Aô thå bottoí oæ thå screeî ió á commanä prompô oæ thå form + + Command¿ (/=Help¬ X=Quit)º  + +Thå  cursoò (don'ô confuså thió witè thå filå pointer© ió  positioneä  afteò +thió  commanä  prompô tï indicatå thaô ZFILEÒ ió waitinç foò yoõ tï presó  á +key. + + Thå  centeò  2°  lineó oæ thå screeî  sho÷  thå  selecteä  files®  Thå +characteò strinç "-->¢ (onlù "->¢ iî thå five-columî display© floató betweeî +thå rowó oæ filå nameó anä designateó thå so-calleä "pointed-to¢ file® Manù +oæ thå ZFILEÒ commandó automaticallù operatå oî thió file. + + Whaô wå havå describeä sï faò ió thå maiî ZFILEÒ screen¬  buô iô ió noô +thå onlù one®  Aó thå commanä prompô suggests¬ pressinç thå slasè characteò +(oò  "?¢ iæ yoõ prefer© bringó uð á helð screeî thaô summarizeó thå built-iî +commandó oæ ZFILER®  Thió helð screeî replaceó thå filå displaù buô  leaveó +thå  statuó linå aô thå toð anä thå commanä linå aô thå bottom¬  excepô thaô +"/=Help¢ changeó tï "/=Files"®  Aó yoõ might¬  therefore¬  guess¬  pressinç +slasè agaiî wilì takå yoõ bacë tï thå filå displaù screen. + É  dï  noô kno÷ iæ anyonå makeó uså oæ thió  feature¬  buô  alì  ZFILEÒ +commanä operationó caî bå invokeä froí thå helð screen® Althougè yoõ cannoô +seå  thå filå pointer¬  yoõ caî manipulatå iô iî thå usuaì way¬  anä yoõ caî +telì  whaô  filå yoõ arå pointinç tï froí thå namå displayeä  aô  thå  uppeò +righô oî thå statuó line. +:C + ZFILEÒ Commandó  + + Basically¬  thå commandó falì intï severaì classes® Onå classificatioî +reflectó wherå thå codå foò thå commanä resides® Therå arå twï categories: + + A® Built-Iî Commands + B® Macrï Commands + + Clasó  Á includeó thå functionó foò whicè thå codå ió parô  oæ  ZFILER® +Macrï commandó arå likå aliaseó iî thaô theù generatå commanä lineó thaô arå +passeä tï thå commanä processoò foò execution® Theså commandó makå ZFILEÒ á +shell® + + Á  seconä classificatioî dependó oî whaô thå commanä actó upon®  Threå +categorieó describå thå objecô oæ thå commands: + + 1® thå pointed-tï file + 2® á grouð oæ taggeä files + 3® neitheò oæ thå above + + Helð  foò commandó iî clasó A3¬  residenô commandó thaô dï noô  perforí +anù  actioî oî thå files¬  maù bå founä bù selectinç Ð ‚ froí thå maiî menu® +Help for the others may be found by selecting  N . +:P + Pointeò Commandó  + + Clasó A³ includeó thå commandó thaô movå thå filå pointer®  Theså  arå +showî oî thå helð screen¬  anä É wilì noô lisô theí here®  Onå caî movå thå +pointeò  tï  thå  nexô  filå  oî thå screeî oò tï  thå  previouó  onå  (witè +wraparound)»  up¬  down¬  left¬  oò righô (witè wraparound)» tï thå firsô oò +lasô filå oî thå currenô screen»  oò tï thå verù firsô oò verù lasô filå  oæ +thoså  selecteä  bù thå filå mask®  Onå caî advancå tï thå nexô  screeî  oæ +fileó oò tï thå previouó screen® Obviously¬ somå oæ theså functionó wilì bå +redundanô iî somå cases¬  sucè aó wheî alì thå selecteä fileó caî fiô oî onå +screeî (thinë whaô happenó wheî therå ió exactlù onå filå selected). + + ZFILEÒ  learnó froí thå TCAÐ thå controì characteró senô bù anù speciaì +cursoò  keyó oî thå keyboarä (provideä theù senä á singlå controì  characteò +anä provideä thå TCAÐ haó beeî seô uð correctly)¬ anä iô makeó theí generatå +thå  up¬  down¬  left¬  anä righô functions®  Iæ thå cursoò  keyó  generatå +controì codeó normallù useä foò anotheò function¬ theî thaô functioî wilì bå +losô  (thå  cursoò keyó takå precedence)®  Thaô caî  causå  problems®  Onå +solutioî  ió tï eliminatå thå definitioî oæ thå cursoò keyó iî thå TCAР anä +simplù   uså  thå  defaulô  WordStaò  diamonä  keyó  foò  thoså   functions® ŠAlternatively¬  onå  caî  patcè  ZFILEÒ tï uså differenô keyó  foò  itó  owî +functions¬ buô thió ió noô straightforwarä tï do¬ anä É wilì noô describå iô +here. + Thå "J¢ (Jump© commanä allowó yoõ tï jumð tï á filå thaô yoõ name® Thió +ió  verù handù wheî therå arå manù fileó iî thå displaù oò wheî thå filå yoõ +wanô  ió  noô oî thå currenô screen®  Presó thå "J¢ key¬  anä yoõ  wilì  bå +prompteä foò á filå name®  Yoõ dï noô havå tï enteò thå exacô name® ZFILEÒ +automaticallù convertó whaô yoõ typå intï á wildcarä filespec¬  anä iô findó +thå firsô filå thaô matches® Foò example¬ iæ yoõ enteò onlù "Z¢ followeä bù +á return¬  thió ió equivalenô tï "Z*.*"¬ anä ZFILEÒ wilì movå thå pointeò tï +thå firsô filå thaô startó witè á "Z"® Similarly¬ iæ yoõ enteò ".D"¬ ZFILEÒ +wilì movå tï thå firsô filå witè á filå typå thaô startó witè "D". + + Thå "J¢ functioî ió verù handy»  however¬  therå ió more®  Manù peoplå +arå  noô  awarå thaô yoõ maù presó control-Ê tï repeaô thå samå  searcè  anä +finä  thå nexô matchinç file®  Thå searcè wilì wrað arounä froí thå enä  oæ +thå  fileó bacë tï thå beginning®  Thió functioî ió noô listeä oî thå  helð +screeî becauså É coulä noô finä rooí foò it. + + Control-É (TAB© ió aî aliaó foò control-Ê (jumð tï filå again)¬ foò uså +witè terminaì definitionó (TCAPs© whicè definå control-Ê aó aî arro÷ key. +:N + Otheò Non-Filå Commandó  + + Somå otheò commandó thaô dï noô acô oî fileó areº  X¬ L¬ A¬ S¬ E¬ H¬ Z¬ +anä O® "X"¬ aó thå commanä prompô remindó you¬ ió useä tï exiô froí ZFILER® +Besideó  terminatinç thå currenô executioî oæ thå program¬  iô alsï  removeó +ZFILER'ó entrù iî thå shelì stacë (iæ iô diä not¬  yoõ woulä jusô reenteò iô +righô away). + + Wå  alreadù  spokå  abouô  thå "L¢  (Log©  commanä  earlier®  Thå  "A¢ +(Alphabetizå  oò Arrangå oò Alphá sort© toggleó thå waù iî whicè  thå  fileó +arå  sorted¬  namelù  alphabeticallù bù thå filå namå oò bù thå  filå  type® +Thå  "S¢ (Status© commanä promptó yoõ foò á disë drivå letteò anä theî telló +yoõ thå amounô oæ spacå remaininç oî thaô disk. + + Thå "E¢ commanä (refresè scrEEî -- É kno÷ that'ó stretchinç things¬ buô +"R¢ waó alreadù used© redrawó thå screen®  Yoõ mighô thinë thaô thió  woulä +neveò  bå needed¬  buô therå arå twï circumstanceó iî whicè iô comeó iî verù +handy®  Onå  ió wheî ZFILEÒ ió beinç useä oî á remotå system®  Iô ió  truå +thaô verù fe÷ RASó makå ZFILEÒ available¬ buô É dï oî Z-Nodå #3® Iæ yoõ geô +somå  linå noise¬  thå screeî caî becomå garbled®  Theî thå "E¢ keù caî  bå +useä tï dra÷ á fresè screen. + Thå  otheò circumstancå iî whicè thå "E¢ commanä saveó thå daù ió  witè +Backgrounder-ié  iæ yoõ dï noô havå á screeî driveò (É don'ô foò mù  Concepô +10¸  terminaì  -- neveò goô arounä tï writinç one¬  partlù becauså  alì  thå +programó É uså frequentlù havå á redra÷ keù likå thió one)® É simplù definå +á  BGié  keù  macrï  specifyinç  "E¢ aó  thå  "redraw¢  key¬  savå  thå  keù +definitionó  tï  ZFILER.BG¬  anä attacè thaô  definitioî  tï  ZF.COM®  Theî +wheneveò  É swað taskó bacë intï ZFILER¬  BGié simulateó mù pressinç thå "E¢ +key¬  anä thå screeî ió redrawn®  Thió ofteî giveó á fasteò screeî  refresè +thaî onå getó witè á full-fledgeä screeî driver. + + Thå "H¢ (Help© commanä generateó á macrï commanä tï invokå thå Z-Systeí +HELР facility®  Tï  telì thå truth¬  É havå noô useä thió anä  don'ô  eveî +remembeò preciselù whaô iô does® É woulä havå tï looë aô thå sourcå code. + Thå "Z¢ (Z-system© commanä promptó yoõ foò á command¬  anä whateveò yoõ +enteò  ió  passeä  oî  tï  thå Z-Systeí multiplå  commanä  linå  buffeò  foò +execution®  Wheî  thaô  commanä  linå  ió  complete¬  ZFILEÒ  ió  reinvokeä +automatically. + + Wheî  yoõ  uså thå "Z¢ command¬  yoõ wilì normallù bå loggeä  intï  thå +directorù  thaô ió currentlù displayed®  However¬  thió wilì noô alwayó  bå +possible®  ZFILEÒ allowó yoõ tï selecô directorieó witè useò numberó froí ° +tï 31® Unlesó yoõ arå usinç á versioî oæ ZCPR3³ oò ZCPR3´ witè thå HIGHUSEÒ +optioî  enabled¬  yoõ  cannoô loç intï useò areaó abovå 15®  Iî  thaô  caså +ZFILEÒ  wilì puô yoõ iî thå directorù youò werå iî wheî yoõ invokeä  ZFILER® +Iî anù case¬  thå commanä prompô wilì indicatå thå directorù froí whicè youò +commanä linå wilì bå executed. + Sincå  commandó yoõ ruî usinç thå "Z¢ functioî maù puô somå informatioî +oî  thå  screeî thaô yoõ woulä noô wanô ZFILEÒ  tï  obliteratå  immediately¬ +therå ió á flaç seô thaô signaló ZFILEÒ tï prompô yoõ anä tï waiô foò yoõ tï +presó  á  keù  beforå puttinç uð itó display®  Herå ió á tið  foò  advanceä +users® Iæ yoõ enteò youò commanä linå witè onå oò morå leadinç spaces¬ thió +shell-waiô flaç wilì noô bå set¬  anä ZFILEÒ wilì returî withouô youò havinç +tï  presó  á key®  Thå leadinç spaceó arå strippeä froí  thå  commanä  linå +beforå  iô ió passeä tï thå commanä processor®  Thió meanó thaô yoõ  cannoô +uså  á  leadinç spacå tï forcå invocatioî oæ thå extendeä commanä  processoò +(ECP)»  yoõ havå tï uså thå slasè prefiø instead®  Á spacå anä á slasè wilì +forcå invocatioî oæ thå ECÐ anä wilì disablå thå shell-waiô flag. + + Thå   finaì  commanä  iî  clasó  A³  ió  thå  "O¢  (Options©   command® +Informatioî regarding it can be found in the  O  menu. +:S + Single-Filå Built-Iî Functionó  + + No÷ let'ó discusó thå commandó iî clasó A1¬  thå built-iî commandó thaô +acô  oî  thå  pointed-tï file®  Theså arå invokeä bù pressinç  onå  oæ  thå +followinç  keys¬  whoså  meaninç ió indicateä iî parenthesesº  à (Copy)¬  Í +(Move)¬ Ä (Delete)¬ Ò (Rename)¬ Ö (View)¬ Ð (Print)¬ Æ (Filå size)¬ Ô (Tag)¬ +anä Õ (Untag)®  Somå oæ theså arå self-explanatory¬  anä É wilì noô discusó +them. + Thå "C¢ commanä copieó á filå tï anotheò directorù undeò thå samå name» +iô doeó noô allo÷ onå tï givå á ne÷ namå foò thå destinatioî filå  (however¬ +yoõ caî dï thaô witè á macrï command)® Thå "M¢ commanä doeó noô reallù movå +á file» iô copieó thå filå anä then¬ iæ thå copù waó successful¬ deleteó thå +originaì  file®  Iô ió reallù á combinatioî oæ "C¢ anä "D"®  Movinç á filå +thió waù ió inefficienô iæ thå destinatioî directorù ió oî thå samå drivå aó +thå sourcå file® Á macrï commanä thaô invokeó aî ARUNÚ aliaó caî geô arounä +thió limitatioî (anä almosô alì otheò ZFILEÒ limitations). + + Thå  "V¢ (View© anä "P¢ (Print© commandó no÷ havå á switchablå  filteò +whicè onlù passeó printablå characters¬ carriagå return¬ anä linå feeds. + Thå taç anä untaç commandó arå useä tï selecô á grouð oæ fileó oî whicè +operationó  caî bå performed®  Taggeä fileó arå indicateä iî twï  ways®  Á +speciaì characteò ("#"© ió placeä afteò thå filå namå iî thå  display¬  and¬ +iæ thå terminaì supportó videï highlighting¬ thå filå ió highlighted. + + Twï  relateä commandó arå × (Wilä tag© anä Ù (Yanë back?)®  "W¢ allowó +yoõ  tï taç oò untaç groupó oæ fileó designateä bù aî ambiguouó  filå  spec® +Afteò  taggeä fileó arå operateä oî bù thå built-iî grouð commandó describeä +below¬  thå taç markeò "#¢ ió changeä tï "'¢ (á sofô tag)®  Thå "Y¢ commanä +changeó  thå sofô tagó bacë intï harä tagó sï thaô furtheò grouð  operationó +caî bå performeä oî thoså files. + + ZFILEÒ  no÷ rememberó alì thå filå tagó wheî iô returnó froí á macrï oò +Ú  commanä  (iô  writeó á temporarù  file¬  ZFILER.TAG¬  tï  á  configurablå +directorù whicè containó thå filå list). +:G + Built-Iî Grouð Commandó  + + Grouð  commandó  arå initiateä bù pressinç thå "G¢  (Group©  key®  Thå +commanä prompô aô thå bottoí oæ thå screeî changeó to + + Command¿ (/=Help¬ X=Quit© ‚ Groupº (A,C,D,F,M,P,R,T,U,V) + +Foò  no÷ wå wilì consideò onlù thå built-iî grouð functionó (clasó  A2©  anä +wilì takå uð grouð macrï commandó (clasó B2© nexô time. + + Excepô  foò thå fouò functionó describeä below¬  thå letteró invokå thå +samå actioî aó thå individuaì commanä correspondinç tï thaô letter¬  buô thå +functioî  ió performeä oî alì thå taggeä files®  Wå wilì noô discusó  thoså +further®  Notå  iî particulaò thaô thå keyó "A¢ anä "R"¬  however¬  havå  á +grouð functioî thaô ió completelù differenô froí thå individuaì function. + Thå  "U¢ anä "T¢ grouð functionó dï noô acô oî thå taggeä  files»  theù +changå thå tagging® Thå formeò untagó alì files» thå latteò tagó theí all. + + Grouð  Tag/Untaç anä Wilä Taç caî bå independentlù seô tï operatå  froí +thå  filå  pointeò tï thå enä oæ thå ring¬  ratheò thaî oî thå entirå  ring® +Grouð  Tag/Untaç  seeí  tï bå thå mosô usefuì iæ aô thå starô  aô  thå  filå +pointer® Uså thå Optionó commanä tï controì this. + + Thå "R¢ grouð functioî ió anotheò onå thaô doeó not¬ strictlù speaking¬ +acô oî thå taggeä files®  Iô reverseó thå tags¬  tagginç thå fileó thaô haä +beeî untaggeä anä untagginç thå oneó thaô haä beeî tagged® Thió caî bå verù +handù iî severaì circumstances® Foò example¬ yoõ mighô wanô tï copù alì thå +fileó  excepô two®  Iô ió easieò tï taç thoså twï anä theî tï  reverså  thå +tags®  Aó  anotheò  example¬  yoõ mighô wanô tï copù somå oæ thå  displayeä +fileó  tï  onå  diskettå anä thå otheró tï á seconä  diskette®  É  dï  thió +frequently®  É begiî bù tagginç thå oneó tï gï tï thå firsô diskette® Theî +É grouð copù ("GC"© theí tï thå destinatioî diskette® Next¬ É yanë bacë thå +tagó usinç thå "Y¢ commanä anä theî reverså thå tagó witè "GR"®  No÷ É  caî +grouð copù thå resô tï thå seconä diskette. + Thå  "A¢ (Archive© grouð commanä ió verù handù foò automatinç  backups® +Wheî iô ió entered¬  thå tagó arå removeä froí anù taggeä filå whoså archivå +flaç ió set® Aó á result¬ onlù fileó thaô havå beeî modifieä sincå thå flaç +waó  lasô  seô  wilì remaiî tagged®  Iî addition¬  thå  "A¢  grouð  commanä +automaticallù initiateó á grouð copù operatioî buô witè onå speciaì feature® +Afteò thå filå haó beeî copieä successfully¬  thå archivå flaç oî thå sourcå +filå ió seô tï indicatå thaô thå filå haó beeî backeä up. + + Undeò  lateò  versionó oæ VFILER¬  thå grouð "A¢ commanä  automaticallù +taggeä alì unarchiveä files» undeò ZFILEÒ iô untagó thå archiveä ones® Thió +differencå ió verù important®  Witè VFILER¬  yoõ werå forceä tï bacë uð alì +thå fileó selecteä bù thå VFILEÒ filå mask® Undeò ZFILEÒ yoõ caî selecô thå +fileó thaô wilì bå candidateó foò backinç up®  Iæ yoõ wanô thå achievå  thå +samå  functioî aó undeò VFILER¬  jusô taç alì thå fileó firsô witè "GT¢  anä +theî archivå theí witè "GA"®  Oî thå otheò hand¬  iæ yoõ wanô tï exludå BAË +fileó froí thå backup¬ yoõ caî "GT¢ alì files¬ untaç thå "*.BAK¢ fileó usinç +thå "W¢ command¬ anä theî uså thå "GA¢ command. + Afteò  yoõ  enteò  thå  commanä  "GA"¬  yoõ  wilì  bå  prompteä  foò  á +destinatioî directory®  Yoõ dï noô havå tï supplù one¡ Iæ yoõ simplù enteò +á carriagå return¬  thå copù operatioî wilì bå skipped¬ anä yoõ wilì bå lefô +witè tagó oî thå fileó thaô neeä tï bå backeä up®  Yoõ caî theî uså á macrï +functioî  tï  bacë  theí  uð  iî  á  specializeä  way¬   sucè  aó  crunchinç +(compressing© theí tï thå backuð disë (insteaä oæ copyinç theí aó theù  are© +oò puttinç theí intï á librarù oî thå backuð diskette® Thå macrï techniqueó +requireä tï dï thió arå iî thå FILEÒ Scripô Macrï Facility‚ section. +:O + Thå Optioî Commanä  + + Wheî thå optioî commanä letteò "O¢ ió pressed¬ á speciaì optionó screeî +ió  displayed®  Eleveî operatinç characteristicó caî bå changeä froí á menõ +witè thå followinç appearancå (approximately): + + A® singlå replacå querù Y + B® grouð replacå querù Y + C® archivå replacå querù N + D® verifù querù Y + E® verifù defaulô Y + F® suppresó SYÓ fileó Y + G® sorô bù filå namå N + H® seô copieä filå attributeó Y + I® uså desô filå attributeó Y + J® archivå destinatioî Y + K® searcè patè foò CMÄ filå N + Wå  wilì  explaiî  thå meaninç oæ eacè oæ theså optionó  iî  á  moment® +Firsô á fe÷ wordó abouô thå mechanics® Whilå thå optionó menõ ió displayed¬ +pressinç  thå  indeø  letteò  aô thå lefô wilì  causå  thå  settinç  oæ  thå +correspondinç  optioî tï bå toggled¬  anä thå ne÷ statå wilì bå showî iî thå +columî  aô  thå right®  Thå listinç abovå showó thå initiaì  statå  oæ  thå +optionó  iî mù personaì versioî oæ ZFILER®  Wheî yoõ arå finisheä  togglinç +options¬ jusô presó carriagå returî tï returî tï thå maiî ZFILEÒ menu® Theså +optioî  settingó  arå storeä iî thå ZFILEÒ shelì stacë entrù anä  wilì  thuó +continuå  iî  effecô througè alì ZFILEÒ operationó untiì thå commanä "X¢  ió +useä tï terminatå thå shell. + + Thå  firsô threå optionó concerî ho÷ ZFILEÒ respondó wheî  copyinç  (oò +moving©  fileó anä á filå oæ thå samå namå alreadù existó iî thå destinatioî +directory®  Iteí  Á applieó wheî individuaì fileó arå copieä (commandó  "C¢ +anä "M")»  iteí  applieó wheî á grouð copù ió performeä (commandó "GC¢  anä +"GM")»  anä  iteí à applieó wheî performinç aî archivinç operatioî  (commanä +"GA")®  Iæ thå optioî ió "YES"¬ theî ZFILEÒ wilì prompô onå beforå existinç +fileó  arå  eraseä anä givå onå thå chancå tï canceì thå operatioî foò  thaô +file¬  leavinç thå existinç filå intact®  Iæ thå optioî ió toggleä tï "NO"¬ +theî existinç fileó wilì bå overwritteî withouô eveî á message. + Thå nexô twï optionó affecô thå verificatioî oæ thå copieä filå iî  thå +destinatioî  directory®  Iteí Ä determineó whetheò oò noô thå useò wilì  bå +askeä abouô verification®  Iæ thió optioî ió seô tï "N"¬  theî thå statå oæ +optioî  Å  wilì determinå whetheò oò noô verificatioî ió performeä  oî  filå +copies®  Iæ thió optioî ió seô tï "Y"¬  theî beforå eacè copy¬  move¬ grouð +copy¬ oò grouð move¬ ZFILEÒ wilì puô uð thå prompô "Verifù (Y/N)?". + + Thå  nexô twï optionó affecô thå waù fileó arå displayeä oî thå screen® +Iæ iteí Æ ió seô tï "Y"¬  theî fileó witè thå "system¢ oò SYÓ attributå wilì +bå  suppressed¬  thaô  is¬  noô includeä amonç thå selecteä fileó  oî  whicè +ZFILEÒ acts®  Thió ió á reasonablå choicå foò thió option¬  sincå thå  mosô +commoî  uså  oæ  thå  SYÓ  attributå ió tï makå  thå  fileó  disappeaò  froí +consideratioî durinç filå maintenancå anä displaù operations® Iteí Ç oî thå +optionó  menõ determineó whetheò fileó arå sorteä firsô bù namå anä theî  bù +typå oò vicå versa® Changinç thió optioî ió presentlù equivalenô tï thå "A¢ +commanä froí thå maiî ZFILEÒ commanä menu. + Thå  nexô  threå optionó concerî ho÷ filå attributeó arå  treateä  wheî +fileó arå copied® Onå possibilitù ió tï creatå ne÷ fileó witè á cleaî slatå +oæ attributeó (thaô is¬  alì attributeó resetº  noô read-only¬  noô SYS¬ noô +archived)®  Thió ió whaô wilì happeî wheî optioî È ió seô tï "N¢ (buô  notå +optioî J¬  whicè maù overridå this)® Wheî thå attributeó oæ thå destinatioî +filå arå tï bå set¬  theù caî bå seô iî twï possiblå ways® Iæ á filå oæ thå +samå  namå  existeä iî thå destinatioî directory¬  theî itó filå  attributeó +coulä bå useä foò thå copù thaô replaceó it®  Thió ió whaô wilì bå donå  iæ +optioî  É  ió  seô tï "Y"®  Iæ optioî É ió seô tï "N¢ oò iæ  therå  waó  nï +matchinç filå iî thå destinatioî directory¬  theî thå attributeó wilì bå seô +tï matcè thoså oæ thå sourcå file. + Optioî  Ê caî seô á speciaì overridå foò thå archivå oò ARà attribute® +Iæ thå optioî ió seô tï "N"¬ theî thå ARà attributå ió treateä jusô likå thå +otheò  attributeó accordinç tï optionó È anä I®  Iæ optioî Ê ió seô tï YES¬ +theî thå destinatioî filå alwayó haó itó ARà attributå set. + + Therå waó aô onå timå á greaô deaì oæ controversù oveò thå waù thå  ARà +attributå ió handleä undeò ZFILER® Aô onå timå iô waó alwayó reset¬ sï thaô +thå  destinatioî filå woulä bå markeä aó noô backeä up®  Anotheò schooì  oæ +thoughô asserteä that¬  oî thå contrary¬ thå filå waó backeä up¬ sincå therå +waó  á copù oæ iô oî thå sourcå disë froí whicè thå filå waó  copied®  Thaô +latteò  argumenô madå considerablå senså iî thå caså oæ copyinç fileó froí á +masteò  disë  tï á RAÍ disë beforå á worë session®  Herå iô  waó  certainlù +importanô tï starô witè alì fileó markeä witè thå ARà attributå sï thaô  onå +coulä easilù telì aô thå enä oæ thå sessioî whicè fileó haä beeî modifieä sï +thaô theù coulä bå copieä bacë tï thå permanenô storagå medium. + + Alì  iî  all¬  É neveò understooä thió  controversy®  Botè  approacheó +clearlù havå merit¬  anä sincå ZFILEÒ supportó both¬ É sa÷ nï reasoî foò alì +thå argument®  Iî á futurå versioî oæ ZFILER¬ É thinë É woulä likå tï adä á +flaç  worä thaô woulä indicatå whicè driveó shoulä automaticallù seô thå ARà +flaç wheî thå Ê optioî ió seô tï YES® Thaô way¬ thå optioî coulä bå madå tï +applù tï RAÍ driveó only. + Thå finaì iteí oî thå optioî menu¬  optioî K¬  determineó ho÷ thå macrï +commanä filå ZFILER.CMÄ (seå discussioî below© wilì bå located®  Therå  arå +twï choices®  Iæ optioî Ë ió seô tï YES¬ theî ZFILEÒ wilì looë foò iô firsô +iî  thå currentlù displayeä directorù anä theî alonç thå entirå ZCPR³ searcè +path®  Thió  optioî ió usefuì iæ onå wantó tï havå differenô macrï  commanä +fileó thaô applù tï specifiã directorù areas® Alternatively¬ iæ optioî Ë ió +seô  tï  NO¬  theî  ZFILEÒ  locateó thå CMÄ filå  withouô  usinç  thå  path® +Dependinç oî ho÷ ZFILEÒ ió configureä (thió wilì bå discusseä anotheò time)¬ +thå  filå wilì bå soughô eitheò iî thå rooô directorù oæ thå patè (thå  lasô +directorù  specifieä  oî thå searcè path© oò iî á specifiã  drive/useò  areá +codeä intï ZF.COM® Thió alternativå resultó iî fasteò operation¬ especiallù +iæ thå specifieä directorù resideó oî á RAÍ disk. + + Thå  optionó  controlleä  bù thå optioî menõ caî  alsï  bå  permanentlù +changeä iî thå ZFILEÒ prograí filå usinç á patchinç utilitù likå ZPATCH® Iî +thå  firsô  pagå oæ thå file¬  yoõ wilì seå thå ascié  strinç  "OPT:"®  Thå +eleveî byteó followinç thió strinç contaiî thå startuð valueó foò thå eleveî +options® Patcè á bytå tï 0° foò NÏ oò FÆ foò YES. + + Onå maù alsï uså Aì Hawley'ó Zconfig¬ alonç witè thå .CFÇ filå provideä +witè  youò  copù  oæ  ZFILER¬  iî ordeò tï instalì ZFILER  tï  youò  tastes® +Handleó  alì  thå configuratioî normallù needed®  Yoõ maù alsï uså  á  filå +patcheò  tï changå thå defaulô nameó oæ somå oæ ZFILER'ó temporarù fileó, iæ +yoõ wish. +:M:ZFMACRO +:H:ZFHIST +:J + Creditó  + + Thió  documenô containó thå texô oæ twï articleó oî ZFILEÒ bù Jaù  Sagå +whicè  originallù  appeareä  iî  Thå Computeò Journaì issueó  #3¶  anä  #37® +Reprinteä witè permissioî oæ thå publisher: + + Thå Computeò Journal + P.O® Boø 12 + S® Plainfield¬ NÊ 07080-0012 + + Theså  .HLÐ fileó foò ZFILEÒ werå editeä bù Leï M®  Cavanaugè IIÉ  froí +thå documentatioî provideä witè ZFILER. + \ No newline at end of file diff --git a/Source/Images/d_zpm3/u10/ZFMACRO.HLP b/Source/Images/d_zpm3/u10/ZFMACRO.HLP new file mode 100644 index 00000000..960d43c2 --- /dev/null +++ b/Source/Images/d_zpm3/u10/ZFMACRO.HLP @@ -0,0 +1,409 @@ +; ---------------------------------- + | | + | ZFILER's Script Macro Facility | + | | + ---------------------------------- + + + M >> Introduction to ZFILEÒ's Macros + + +  << Macro Execution >>   << Macro Writing >>  + + I >> Invokinç Macros S >> Macrï Scripts + G >> Group Macros D >> Defininç Macroó -- Thå CMÄ File + R >> Ruleó foò Scripô Expansion + E >> Macrï Examples + L >> Sample Macro CMD Listing +:M + Introduction to ZFILEÒ's Macros  + + Althougè ZFILEÒ caî accomplisè manù taskó usinç itó built-iî functions¬ +itó reaì poweò comeó froí thå macrï facility¬ whicè allowó iô tï bå extendeä +tï  includå anù functionó thaô caî bå performeä usinç combinationó oæ  otheò +programs®  Thió  ió wherå ZFILEÒ reallù makeó uså oæ itó poweò aó á  shell® +Firsô  É  wilì  describå ho÷ thå macrï facilitù ió used¬  anä  theî  É  wilì +describå  ho÷  thå useò defineó thå macrï functions®  Aó witè thå  built-iî +functions¬  macrï functionó caî operatå eitheò oî singlå fileó oò oî  groupó +oæ files®  Thå single-filå macrï facilitù ió welì developeä anä waó alreadù +presenô  iî nearlù thå samå forí iî VFILER»  thå grouð macrï facilitù ió ne÷ +witè ZFILEÒ and has been greatly expanded. +:I + Invokinç Macros  + + Onå  waù  tï initiatå á macrï operatioî oî thå pointed-tï  filå  ió  tï +presó thå macrï invocatioî key¬  whicè ió normallù thå escapå key® Á prompô +oæ  "Macro:¢  wilì appeaò afteò thå normaì ZFILEÒ commanä prompt®  Aô  thió +poinô  yoõ havå severaì choices®  Iæ yoõ kno÷ thå keù correspondinç tï  thå +macrï yoõ wanô tï run¬ theî yoõ caî simplù presó thaô key® ZFILEÒ wilì theî +construcô  á  commanä  linå  anä pasó iô oî tï  thå  commanä  processoò  foò +execution®  Iæ  ZFILEÒ  ió  configureä  foò  instanô  macrï  operatioî  (iô +generallù is)¬  theî macroó associateä witè thå numbeò keyó "0¢ througè  "9¢ +caî bå initiateä withouô thå macrï invocatioî key»  thaô is¬  thå numbeò keù +entereä  alonå  aô  thå maiî ZFILEÒ commanä prompô wilì generatå  thå  macrï +function. + + Iæ yoõ presó thå macrï invocatioî keù á seconä time¬  á user-createä helð +screeî  wilì bå displayed®  Thió screeî generallù listó thå availablå macrï +functions®  Yoõ caî no÷ presó thå keù foò thå desireä function¬  oò yoõ caî +presó  carriagå returî tï canceì thå macrï operatioî anä returî tï thå  maiî +ZFILEÒ menu®  Thå helð menõ screeî wilì alsï bå displayeä iæ yoõ presó  thå Š"#¢  key®  Thió ió á holdoveò froí VFILEÒ anä ariseó iî parô becauså oæ thå +structurå  oæ  thå  filå  iî whicè thå macroó  arå  defineä  (morå  oî  thió +shortly). +:G + Group Macros  + + Grouð  macroó  arå  invokeä iî á similaò waù froí  thå  grouð  functioî +commanä line®  Afteò yoõ havå taggeä á grouð oæ files¬ presó thå "G¢ keù tï +enteò  grouð mode®  Thå prompô wilì lisô onlù thå built-iî grouð functions¬ +buô  iæ  yoõ presó thå macrï invocatioî key¬  yoõ caî proceeä  aó  describeä +abovå foò single-filå macrï operations¬  excepô thaô thå macrï functioî wilì +bå performeä oî eacè oæ thå taggeä files. + + Thå  grouð macrï facilitù workó á littlå differentlù thaî  thå  single- +filå  macrï  facility®  Sincå thå commanä linå woulä generallù noô bå  lonç +enougè  tï contaiî thå commandó foò alì thå taggeä files¬  thå  grouð  macrï +facilitù  workó bù writinç ouô á batcè filå foò processinç bù ZEØ oò SUBMIT® +Iî  thió  waù therå ió virtuallù nï limiô tï thå numbeò oæ  fileó  oî  whicè +grouð macroó caî operate. + Therå arå manù configurablå optionó (describeä below© thaô arå associateä +witè  thå grouð macrï operation®  Theså includå thå namå oæ thå ZEØ oò  SU +batcè file¬  thå directorù tï whicè iô ió written¬ anä thå commanä linå thaô +ZFILEÒ  generateó  tï initiatå thå batcè operation®  Thå NZ-COÍ versioî  oæ +ZFILEÒ useó á filå calleä ZFILER.ZEØ anä thå commanä linå "ZEØ ZFILER"® Thå +Z3PLUÓ version¬  undeò whicè ZEØ wilì noô run¬ useó á filå calleä ZFILER.SU +anä á commanä linå oæ "SUBMIÔ ZFILER". + + Sincå macroó (anä thå maiî menõ "Z¢ function© worë bù passinç  commandó +tï  thå  commanä processor¬  filå tagó werå oncå losô bù oldeò  versionó  oæ +ZFILER»  wheî  theså oldeò versionó resumeä operation¬  thå procesó  starteä +afresh® ZFILEÒ no÷ rememberó alì thå filå tagó wheî iô returnó froí á macrï +oò Ú commanä (iô writeó á temporarù file¬  "ZFILER.TAG"¬  tï á  configurablå +directorù whicè containó thå filå list). +  List Capability for Group Macros  + + Iæ á ZFILER.CMÄ scripô beginó witè á commá (beforå oò afteò thå leadinç +"!¢ shelì pauså parameter)¬  ZFILEÒ wilì alwayó expanä thå macrï directlù tï +ZCPR'ó multiplå commanä line®  Iî thå caså oæ á grouð macro¬ $P¬ $F¬ anä $Ô +parameteró  arå  expandeä tï á lisô oæ alì taggeä files¬  separateä  bù  thå +characteò followinç thå leadinç comma® + +For example, the script: + + 1 , !echo the tagged files are $F + +displayó  "THÅ  TAGGEÄ FILEÓ ARÅ "¬  followeä bù á lisô oæ thå taggeä  fileó +separateä  bù  ¢ ¢ oî thå consolå (assuminç ECHÏ ió á valiä  ZCPÒ  command)® +ZFILEÒ theî promptó foò á keypresó beforå returning®  Pleaså notå thaô thió +macrï bufferó thå entirå lisô oæ taggeä fileó tï CP/M'ó commanä linå buffer¬ +whicè ió onlù 12µ byteó iî length®  Thå abovå command¬  therefore¬ normallù +causeó  á  "Commanä toï long¢ erroò iæ morå thaî abouô ¸ fileó  arå  tagged¬ +dependinç oî thå lengthó oæ thå taggeä names. + + Lisô capabilitù allowó programó whicè accepô á filå list¬  sucè aó LPUÔ +oò V¬ tï ruî oî taggeä files® ZEØ ió NOÔ involved. +  Group Macro Tagged File Count  + + Thå  numbeò oæ fileó taggeä ió puô intï Useò Registeò #µ (configurable© +beforå runninç ZEX®  Thå macrï caî theî dowî counô anä takå somå additionaì +actioî afteò iô haó ruî oî eacè taggeä file. + + For example, if you press "G1", the script: + + 1 ! $d$u:;$!crunch $f B1:;reg m5;if reg 5= 0;$"Library DU ":; + lput $"Library Name: " B1:*.* +1;era B1:*.?Z?;fi;$h: + +cruncheó taggeä fileó tï á fixeä emptù directorù B1:¬ makeó á librarù ouô oæ +theí  (leavinç  rooí  foò  ± morå file© witè  inpuô  foò  librarù  namå  anä +location¬  theî  eraseó alì "thå crunchettes"®  Thió macrï requireó REÇ  tï +decremenô Registeò 5. + + Notå  thaô REÇ musô bå á Type³ oò Type´ (oò RCP© foò thå ZEØ GÏ "$!¢ tï +ruî CRUNCÈ repeatedly® Alsï notå thaô registeró onlù holä á 25µ count¡ Foò +consistency¬  singlå  macroó  puô á "1¢ counô iî thå  Useò  Register®  Useò +registeò rangå 0..¹ ió noô enforced¬  sï yoõ caî uså registeró 10..3± iæ yoõ +wish® However¬ 10-1µ arå reserved¬ while 16-1· arå useä bù other programs. +:S + Macrï Scriptó  + + ZFILEÒ  macrï  scriptó arå similaò tï thoså iî ARUNÚ anä iî  thå  otheò +menõ  shelló  (MENU¬  VMENU¬  FMANAGER©  iî thaô parameteò  expressionó  caî +appear®  Thå  criticaì parameteró -- thå oneó thaô implemenô functionó  thaô +cannoô bå achieveä anù otheò waù -- arå thoså thaô conveù informatioî  abouô +thå  directorù currentlù displayeä bù ZFILEÒ anä abouô thå pointed-tï  file® +Parameteró  consisô  oæ  á "$¢ characteò followeä bù onå oæ  thå  characteró +listeä below. + Useò prompô parameters: + + § Useò inpuô prompt + ¢ Useò inpuô prompt + * Clear screen in user prompt + $ Escape character (example: '$"' for double quote) + ^ Control character (example: '^x' for control-X) + + + Parameteró foò directories: + + - currentlù displayeä directory + à DIÒ form + Ä Drivå letter + Õ Useò number + + - homå directorù (froí where ZFILEÒ waó invoked) + È DÕ form + Ò Homå DIR + Parameteró foò pointed-tï file: + + Ð Fulì informatioî (DU:FN.FT) + Æ Filå namå (FN.FT) + Î Filå namå only + Ô Filå typå only + + Speciaì parameters: + + ¡ GÏ substitutioî indicator + ¤ Thå dollaò character + + Thå parameteró arå listeä iî á speciaì ordeò above¬ anä wå wilì explaiî +thaô later® Firsô wå wilì jusô presenô thå meaninç foò eacè parameter. + + Thå  parameteò  expressionó  $¢ anä $§ arå useä  tï  displaù  á  prompô +messagå  tï  thå useò anä tï reaä iî á responså string®  Singlå anä  doublå +quoteó  arå equivalent®  Oncå thå prompô parameteò haó beeî  detected¬  alì +subsequenô characteró uð tï onå oæ thå quotå characteró arå displayeä aó thå +useò prompt®  Thus¬  iæ É aí noô mistaken¬ therå ió presentlù nï waù tï puô +eitheò quotå characteò intï thå prompt®  Thå enä oæ thå linå oò thå enä  oæ +thå filå wilì alsï terminatå thå prompt. + Iæ yoõ wanô tï makå fancù screens¬ yoõ caî includå escapå sequenceó anä +somå controì characteró (obviouslù carriagå returî won'ô work)®  Promptó iî +scriptó  maù  contaiî controì characteró aó "^x"¬  wherå ø ió anù  characteò +froí À tï z. + + Thå  dollaò sigî maù bå useä aî "escapå character¢ foò scripô  prompts¬ +anä caî be used to include the $, ^, ", and ' characters in prompts. + + Examples: + Script Result + $"Enter name or $^C: " Enter name or ^C: + $"Type $"//$" for help: " Type "//" for help: + + Thå $ª scripô parameteò clearó thå screeî during¬ anä rebuildó iô afteò +á scripô prompt® Usagå ió $"$*..text.."® Prompô texô appearó aô thå toð oæ +á cleared screen. + No÷  foò  thå directorù parameters®  Parameteró C¬  D¬  anä  Õ  returî +informatioî  abouô thå currentlù displayeä directory¬  whilå È anä Ò  returî +informatioî  abouô  thå  homå  directory¬  thå onå  froí  whicè  ZFILEÒ  waó +originallù  invoked®  PLEASÅ  NOTEº  macroó alwayó operatå  froí  thå  homå +directory®  Thå reasoî foò thió ió thaô ZFILEÒ caî displaù directorieó witè +useò  numberó higheò thaî 1µ eveî wheî iô ió noô possiblå tï loç intï  theså +areas®  Iæ yoõ wanô tï operatå iî thå displayeä directory¬ theî youò scripô +musô includå aî expliciô directory-changå commanä oæ thå forí "$D$U:¢ aô thå +beginninç  (oò  "$C:¢ iæ youò systeí requireó thå uså oæ nameä  directories© +anä á commanä oæ thå forí "$H:¢ (oò "$R:"© aô thå end. + + Onå speciaì notå abouô thå parameteró thaô returî directorù names®  Iæ +thå directorù haó nï name¬  theî thå strinç "Noname¢ ió returned® Thió wilì +presumablù noô matcè anù actuaì namå anä wilì lead¬  onå hopes¬  tï á benigî +erroò condition® Theså parameteró arå includeä onlù foò systemó thaô dï noô +allo÷ directorieó tï bå indicateä usinç thå DÕ forí (É hopå thaô fe÷ iæ  anù +systemó arå seô uð thió way). + No÷  wå  comå  tï  thå fouò filå namå parameters®  Theù  allo÷  uó  tï +generatå  easilù thå completå filå specificatioî oò anù parô  oæ  it®  Notå +thaô  "$F¢ ió noô quitå thå samå aó "$N.$T"®  Thå latteò alwayó containó  á +dot» thå formeò doeó noô iæ thå filå haó nï filå type. + + Finally¬  wå havå twï speciaì parameters®  "$$¢ ió includeä tï allo÷ á +dollaò  sigî  characteò tï bå entereä intï thå script®  "$!¢ ió  á  controì +parameteò thaô ió useä onlù wheî á grouð macrï ió executed® Iæ iô ió placeä +immediatelù  beforå  á tokeî (strinç oæ contiguouó  characters)¬  theî  thaô +tokeî  wilì bå replaceä bù thå strinç "GO¢ oî alì buô thå firsô expansioî oæ +thå script®  Thió allowó grouð macrï scriptó tï operatå fasteò bù  avoidinç +repetitivå  loadinç  oæ  á COÍ file®  Iô musô bå useä witè greaô  carå  anä +consideration¬ however¬ foò reasonó thaô É wilì noô gï intï here. +:D + Defininç Macroó ‚ -- Thå CMÄ File + + No÷  let'ó  learî  ho÷ tï definå thå macrï functionó  wå  want®  Aó  É +indicateä earlier¬ thå macroó arå defineä iî á filå calleä "ZFILER.CMD¢ (thå +ZFILEÒ ComManÄ file)®  Iî thå versioî oæ ZFILEÒ distributeä witè NZ-COÍ anä +Z3PLUS¬  thå  CMÄ  filå ió searcheä foò iî thå rooô directorù oæ  thå  ZCPR³ +commanä  searcè  path®  Aó describeä earlier¬  thå optioî menõ  allowó  thå +entirå patè tï bå used® Therå arå alsï somå additionaì configurablå optionó +thaô  wilì  bå  discusseä  anotheò  time®  Yoõ musô bå  surå  tï  puô  youò +ZFILER.CMÄ  filå  iî  thå appropriatå directory®  Iæ  thå  filå  cannoô  bå +located¬  yoõ wilì stilì geô thå macrï prompt¬ but¬ afteò yoõ havå specifieä +á macrï key¬ thå erroò messagå "ZFILER.CMÄ NOÔ Found¢ wilì bå displayed. + + Thå  ZFILER.CMÄ filå ió aî ordinarù texô filå thaô yoõ caî creatå  witè +anù  editoò  oò wordprocessoò thaô caî makå plaiî ASCIÉ fileó  (WordStaò  iî +nondocumenô mode¬ foò example)® Thå CMÄ filå haó twï parts® Thå firsô parô +containó thå macrï commanä definitions»  thå seconä containó thå helð screeî +(describeä earlier). + Iî  thå firsô parô oæ thå CMÄ file¬  eacè linå defineó  á  macro®  Thå +characteò  iî  thå firsô columî ió thå keù associateä witè  thaô  definitioî +(caså  doeó noô matter)®  Macroó caî bå associateä witè thå 1° numbeò keys¬ +2¶  letteò  keys¬  anä  alì  printablå speciaì  characteró  excepô  foò  "#¢ +(explaineä below)®  Thå spacå characteò anä alì controì characteró arå  noô +allowed®  Owinç  tï  aî  oversight¬  thå  rubouô  (DEL©  characteò  caî  bå +associateä witè á macro! + + Afteò  thå  characteò thaô nameó thå macrï therå maù bå anù  numbeò  oæ +blankó (includinç zero)®  Iæ thå firsô non-blanë characteò ió "!"¬ theî thå +"strikå  anù key¢ (shell-wait© prompô wilì appeaò beforå ZFILEÒ putó uð  thå +filå displaù afteò á macrï commanä ió run® Thió shoulä bå useä wheneveò thå +macrï  wilì  leavå  informatioî oî thå screeî thaô yoõ wilì  wanô  tï  read® +Afteò  thå "!¢ therå caî agaiî bå anù numbeò oæ spaces®  Anù remaininç texô +oî thå linå ió takeî aó thå scripô foò thå macrï command. + Thå  seconä parô oæ thå CMÄ filå startó wheî á "#¢ characteò ió founä  iî +thå  firsô  columî (hencå thå exclusioî oæ thaô characteò aó á macrï  name)® +Oncå thaô characteò appears¬ alì remaininç text¬ includinç texô oî thå line¬ +wilì bå useä aó thå helð screen®  Sincå ZFILEÒ wilì adä somå informatioî tï +thå  displaù  (thå  namå oæ thå pointed-tï filå  anä  á  prompt)¬  yoõ  wilì +generallù  wanô tï keeð thå helð screeî tï nï morå thaî 2° lines¬  includinç +aî extrá blanë linå aô thå enä foò spacing®  Witè somå experimentatioî  yoõ +wilì geô thå hanç oæ designinç thió screen. + + + Here is a parameter reminder you can include in your ZFILER.CMD file: + + $! ZEX 'GO' $P DU:FN.FT $D Current Drive + $".." PROMPT $F FN.FT $U Current User + $'..' PROMPT $N FN $C Current DIR + $* CLS on Prompt $T FT $H Home DU $R Home DIR +:R + Ruleó foò Scripô Expansioî  + + ZFILEÒ  followó á specifiã sequencå oæ stepó wheî expandinç  á  script¬ +onå thaô giveó iô á speciaì featurå that¬ É woulä guess¬ fe÷ useró arå awarå +of®  Thå  firsô  steð  iî thå expansioî ió tï procesó onlù  thå  user-inpuô +prompô parameters¬  substitutinç foò thå prompô whateveò thå useò entereä iî +response®  Thió resultó iî á modifieä scripô thaô ió theî processeä bù  thå +seconä  steð iî thå expansion®  Becauså thå expansioî ió handleä thió  way¬ +thå useò inpuô caî includå ZFILEÒ scripô parameters¡  Thus¬  thå scripô caî +bå useä tï writå á script® Yoõ wilì seå aî examplå oæ thió later. + + Thå  seconä  steð  iî  thå expansioî ió tï substitutå  valueó  foò  thå +directorù parameters¬ whicè arå somewhaô likå constants® Theù dï noô changå +aó  á  functioî oæ thå pointed-tï file®  Finally¬  iî  á  thirä  step¬  thå +remaininç parameteró arå expanded® Foò grouð macros¬ thió finaì steð iî thå +expansioî ió repeateä foò eacè oæ thå taggeä files® Thå filå parameteró arå +expandeä  differentlù foò eacè file¬  and¬  startinç witè thå seconä  taggeä +file¬ thå "$!¢ parameteò causeó "GO¢ substitution. +:E + Macrï Exampleó  + + Thå  macrï CMÄ listinç iî sectioî Ì ‚ showó aî examplå oæ á  ZFILER.CMÄ +file¬  onå  designeä tï illustratå somå techniqueó oæ macrï writing®  Whilå +writinç thió article¬ É discovereä thaô onå caî includå blanë lineó aó showî +tï makå thå CMÄ filå easieò tï read® Thå helð screeî parô oæ thå listinç ió +takeî froí mù personaì scripô filå (oî which¬  É havå tï confess¬ É havå noô +reallù workeä verù hard)® Thå macrï definitioî parô oæ thå listinç includeó +onlù  á fe÷ oæ thå definitions®  Yoõ maù wisè tï uså thå Prinô functioî  oæ +thå  HELÐ utilitù tï prinô ouô thió section¬  iî ordeò tï follo÷ alonç  witè +thió discussion. + Thå macrï "Q¢ ió includeä tï illustratå á verù simple¬ buô useful¬ typå +oæ macro®  Iô invokeó thå verù powerfuì filå typinç prograí QÌ (quicë look© +oî  thå pointed-tï file®  Thió ió handù wheî yoõ wanô morå powerfuì viewinç +capabilitù  thaî thaô offereä bù thå built-iî "V¢ command®  QÌ  caî  handlå +cruncheä  fileó  anä libraries¬  anä iô caî displaù texô oò heø  forwarä  oò +backward. + + Macrï  "U¢ uncompresseó á file®  Iô illustrateó á morå compleø  scripô +thaô involveó flo÷ controì anä parameteró thaô extracô individuaì componentó +oæ  thå pointed-tï filå name®  Iô testó thå filå typå tï seå iæ thå  middlå +letteò ió "Q¢ oò "Z"®  Iî thå formeò case¬  iô unsqueezeó thå file»  iî thå +latter¬  iô  uncruncheó  it®  Thå uncompresseä filå iô puô intï thå  sourcå +file'ó directory. + + Macroó S¬  K¬  anä  illustratå thå uså oæ inpuô prompting®  Thå firsô +onå allowó thå useò tï specifù thå filå attributeó tï bå set® Notå thaô thå +prompô includeó á helpfuì remindeò oæ thå syntaø requireä bù SFA. + Macrï  Ë  cruncheó  fileó tï á  user-specifieä  destination®  Iô  alsï +illustrateó ho÷ onå logó intï thå currentlù displayeä directory®  É dï thió +herå sï thaô á nulì answeò tï thå prompô (i.e.¬ jusô á carriagå return© wilì +resulô  iî  thå  cruncheä  fileó beinç placeä  iî  thå  currentlù  displayeä +directorù ratheò thaî iî thå homå directory¬  aó woulä otherwiså bå thå caså +(sincå thaô ió wherå thå macrï runó from¬  remember)® Aó á result¬ however¬ +thió  macrï wilì noô operatå properlù iî useò areaó abovå 1µ undeò  BGié  oò +versionó  oæ thå commanä processoò thaô dï noô allo÷ logginç intï higè  useò +areas. + + Macrï  Â  performó  á  slightlù morå compleø  function®  Iô  noô  onlù +compresseó thå pointed-tï filå tï á specifieä destinatioî directory¬  buô iô +theî markó thå sourcå filå aó havinç beeî backeä up®  Á combinatioî oæ  thå +grouð  archivå  built-iî commanä (tï taç fileó thaô neeä backinç up©  anä  á +grouð macrï  (tï perforí thå backup© giveó thå ZFILEÒ useò á waù tï bacë uð +fileó iî cruncheä forí oî thå backuð disk. + Macrï  Í ió includeä tï sho÷ thaô á ZFILEÒ macro¬  wheî iô needó tï  dï +somethinç  morå compleø thaî iô ió capablå oæ doinç alì bù itself¬  caî pasó +thå  tasë tï aî ARUNÚ alias®  Thå MOVÅ aliaó firsô determineó  whetheò  thå +sourcå  anä destinatioî arå oî thå samå drive®  Iî thaô case¬  MOVE.COÍ  ió +useä  tï  perforí thå move®  Otherwise¬  thå sourcå filå ió copieä  tï  thå +destinatioî anä theî deleted®  Whaô wå have¬  therefore¬  ió á MOVÅ commanä +thaô freeó thå useò oæ thå responsibilitù oæ worryinç abouô whicè driveó arå +involveä -- anotheò examplå oæ ho÷ Z-Systeí caî freå yoõ froí considerationó +thaô neeä noô concerî you¬ thaô dï noô requirå humaî intelligencå tï decide. + + Thå  finaì threå macrï exampleó arå executioî macros®  Macrï Ø  causeó +thå  pointed-tï  filå tï bå executed®  Á morå sophisticateä  versioî  mighô +checë  tï makå surå thaô thå filå typå ió COM®  É opteä foò thå flexibilitù +oæ pointing¬  foò example¬  tï PROGRAM.Z8° anä havinç PROGRAM.COÍ  run®  Iæ +therå ió nï COÍ filå witè á matchinç name¬  thå erroò handleò wilì takå carå +oæ things®  Yoõ wilì notå thå leadinç coloî beforå thå "$n¢ parameter®  Iô +makeó  surå thaô thå currenô directorù ió searcheä eveî iæ iô ió noô oî  thå +path® Prompteä inpuô ió useä tï allo÷ á commanä taiì tï bå included. + Thå  Ú macrï performó á user-specifieä functioî oî thå pointed-tï file® +Twï  separatå useò promptó allo÷ botè thå commanä anä á commanä taiì  tï  bå +given®  Foò  example¬  iæ yoõ wanteä tï squeezå thå filå tï A0:¬  yoõ woulä +enteò  "SQ¢  iî responså tï thå firsô prompô anä "A0:¢ iî  responså  tï  thå +second. + + Thå  ° macrï illustrateó ho÷ thå responså tï á prompô caî bå useä aó  á +ZFILEÒ  script®  Thió macrï takeó carå oæ alì thoså functionó wå forgoô  tï +includå iî ZFILER.CMD® Thå wholå macrï ió jusô prompteä input¬ anä whateveò +wå answeò wilì bå ruî aó á script®  É uså thió functioî sï ofteî thaô É puô +iô  oî á numbeò keù sï thaô iô caî bå invokeä witè á singlå keù ratheò  thaî +thå usuaì pair®  Also¬ aó yoõ maù havå noticed¬ É includå iî thå macrï helð +screeî á lisô oæ thå parameteró thaô caî bå used. + + Thå  onlù  reaì limitatioî oæ thió macro-to-write-a-macrï  approacè  ió +thaô  prompteä inpuô cannoô bå includeä iî thå response®  Aó É writå  this¬ +however¬  iô  occuró  tï  må  thaô  thió limitatioî  coulä  bå  overcomå  bù +recursivelù parsinç thå prompô parameteró untiì nonå remain¬  anä onlù  theî +goinç oî tï thå subsequenô macrï expansioî steps. +:L + SAMPLE MACRO LISTINÇ  + -------------------- +Ñ qì $p + +Õ ¡ iæ $t=?q?;$!sys:uæ $ð $d$u:;else;$!sys:uncò $ð $d$u:;fi + +Ó ¡ $!sfá $ð $¢ SFÁ Optionó (/o,o.® o=ARC,-ARC,R/O,R/W,SYS,DIR)º " +Ë ¡ $d$u:;$!cruncè $æ $"Destinatioî directorù (DU:© -- ";$h: + $d$u:;cruncè $æ $"Destinatioî directorù (DU:© -- ";sfá $æ /arc;$h: + +Í ¡ /movå $ð $"Destinatioî directorù foò moveº " + +Ø ¡ $d$u:;:$î $¢ Commanä Tailº ";$h: +Ú ¡ $d$u:;$¢ Commanä tï perforí oî fileº ¢ $æ $¢ Tailº ";$h: +° ¡ $"Enteò ZFILEÒ macrï scriptº " + £ SAMPLÅ ZFILEÒ MACROÓ FOÒ TCJ + +0® on-linå macrï A® seô Archivå biô N® NULU +1® LPUÔ B® Backuð (cr/sfa© O. +2® Z80ASÍ tï COÍ C® CRà P® Protect +3® Z80ASÍ tï REÌ D® Datå displaù Q® QL +4® Comparå Fileó E® Ediô R. +5® F® S® SFA +6® G® T® Type +7® H® U® Uncompress +8® I® V® VLU +9® J® W. + K® Kruncè X® eXecute + L® LDIÒ Y. + M® Movå Z® ruî command + +$¡  ZEØ  'GO§ $Ä DRIVÅ $Р DU:FN.FÔ $Æ FN.FT +$"..¢ PROMPÔ $Õ USEÒ $Î FÎ $Ô FT +$'..§ PROMPÔ $È HOME + \ No newline at end of file diff --git a/Source/Images/d_zpm3/u10/ZP.HLP b/Source/Images/d_zpm3/u10/ZP.HLP new file mode 100644 index 00000000..e375067b --- /dev/null +++ b/Source/Images/d_zpm3/u10/ZP.HLP @@ -0,0 +1,344 @@ +;   +  Z P  +   + Version 1.7 + + A ZCPR3 Screen-Oriented File/Disk/Memory Patcher + + I -->  Installation  + C -->  Configuration  + O -->  Overview  + S -->  Syntax  + R -->  Running ZP  + M -->  Memory Mode  + D -->  Disk Mode  + F -->  File Mode  + X -->  String Search  + E -->  Record Edit  + H -->  HP Calculator  + Q -->  HP Quik Ref  + K -->  Record Cache  +:I +Installation + +Thå distributioî versioî oæ ZÐ ió supplieä readù foò uså iî ZCPR33« +environments® ZCPR3° useró shoulä instalì ZÐ usinç theiò normaì method® +:C +Configuration + +ZCNFÇ anä ZP.CFÇ maù bå useä tï configurå ZÐ tï alteò thå operatioî oæ thå +prograí tï suiô thå user® Defaulô values¬ sucè aó thå startinç memorù modå +addresó anä thå ASCIÉ recorä characteò displaù modå maù bå configured® + +Thå linå graphicó menõ boø displaù maù bå configureä tï displaù thå menõ +boø iî linå graphicó (foò useró witè reverså videï standout)¬ standouô linå +graphicó (foò useró witè darë screenó anä dií standout© anä tï noô displaù +thå menõ boø aô all. + +Foò useró witè reverså videï standouô anä unblinkinç blocë cursors¬ ZÐ maù +bå configureä tï refresè thå recorä displaù oî enterinç thå ediô modå iî +ordeò tï deletå thå standouô videï placå markeró produceä bù á strinç +searcè oò aî addresó find® Thå heø recorä displaù standouô markeò caî +causå confusioî wheî attemptinç tï ediô thå markeä byte witè aî unblinkinç +cursor. + +ZÐ maù alsï bå configureä tï cleaò thå screeî oî exit® Thå defaulô ió tï +leave the screen display on screen. +:O +Overview + +ZÐ (Zcpr3 Patcher© ió á Z8° screen-orienteä recorä editor-patcher® ZP.1OM¬ +á typå ± version¬ loadinç anä executinç aô 100h¬ wilì ruî undeò anù versioî +oæ ZCPR3® ZP.4OM¬ á typå ´ version¬ executeó onlù undeò ZCPR34+¬ Z3PLUÓ oò +BGii® Botè versionó requirå aî extendeä Z3TCAÐ (aó defineä bù VLIB4D+© anä +alsï requirå thaô thå ZCPR³ wheeì bytå bå set® Thå sourcå codå maù alsï bå +assembleä tï producå á typå ³ version¬ runninç aô 8000è undeò ZCPR33+¬ ŠZ3PLUÓ oò BGii. + +ZÐ waó originallù looselù baseä oî SUPERZAP¬ aká SPÚ oò ZAP33¬ anä waó +developeä aó á work-alikå foò Steveî Cohen'ó greaô ZPATCH1° thaô would¬ iî +additioî tï editinç filå records¬ alsï providå screen-orienteä editinç oæ +memorù anä disë records® ZÐ ió intendeä tï complemenô ratheò thaî replacå +ZPATCH¬ aó welì aó tï complemenô Roâ Friefeld'ó excellenô BCOMÐ anä VCOMP¬ +especiallù wheî theù arå patcheä tï uså thå ZPATCH/WÓ commanä sets® Iî +ordeò tï keeð ZÐ relativelù simple¬ iô lackó ZPATCH'ó comprehensivå strinç +searcè optionó anä doeó noô havå á commanä tï senä thå currenô recorä tï +thå printer® Unlikå ZPATCH¬ ZÐ ió á transienô utilitù anä noô á shell. + +ZÐ haó á one-recorä cachå foò temporarù recorä storagå anä caî movå thå +cachå contentó betweeî file¬ memorù anä disë records® Thió allowó you¬ foò +example¬ tï takå á one-recorä Z3TCAP.Z3Ô filå anä patcè iô intï memorù iî +thå ZCPR³ environment® + +ZÐ alsï containó thå powerfuì anä full-featureä HP12 RPÎ programmer'ó +integeò calculator¬ whicè maù bå calleä uð iî file¬ disk¬ memorù oò recorä +ediô modeó (eveî durinç thå editinç process)¬ tï perform¬ savå oò recalì +thå resultó oæ integeò calculationó oò logicaì operationó iî hex¬ decimal¬ +binary¬ oò characteò modes® +:S +Syntax + +Whilå ZÐ allowó thå useò tï freelù switcè amonç modeó durinç prograí +operation¬ iô alsï maù bå instructeä tï comå uð iî thå desireä initiaì modå +bù usinç thå followinç commanä linå syntax: + + ZP [hex_addr] - Memory mode at hex address + ZP dir: - Disk mode + ZP [dir:]afn - File mode + ZP // - Display syntax help screen +:R +Running ZP + +Excepô foò thå HÐ calculator¬ ZÐ ió menu-driveî anä useó commandó similaò +tï ZPATCH10¬ sï iô shoulä requirå littlå iî thå waù oæ instruction® Morå +specifiã informatioî abouô thå operatioî oæ eacè modå maù bå founä iî thå +following sectionó. + +Severaì alternatå commands¬ however¬ dï noô appeaò iî thå menu® Foò +example¬ therå arå severaì alternatå exiô commands® Whilå thå 'Q§ commanä +ió thå onlù exiô commanä showî iî thå commanä menus¬ 'X§ anä ESà arå +also accepted as exiô commands® + +Foò easieò browsing¬ thå spacå characteò ió accepteä aó aî alternatå next- +screeî commanä aô thå memory¬ filå anä disë menus. + ASCII Record Display Mode + +ZÐ caî displaù characteró iî thå ASCIÉ parô oæ thå recorä displaù iî onå oæ +twï user-selectablå ways® ZP'ó distributioî defaulô ASCIÉ displaù modå +displays onlù thå actuaì displayablå characters¬ displayinç alì otheò +characteró aó '.'s® Thió typå oæ display¬ typicaì oæ DDT¬ ió relativelù +uncluttereä buô iô ió morå difficulô tï reaä texô thaô haó thå higè bitó oæ +characters set, such as WordStaò fileó anä disë directorieó. + +Alternately¬ ZÐ caî bå commandeä tï filteò thå higè bitó oæ thå characteró +beinç displayed¬ convertinç ASCIÉ characteró witè thå higè biô seô tï +displayablå characteró anä displayinç alì otheò characteró aó '.'s® Thió +typå oæ display¬ typicaì oæ PEEK¬ convertó morå characteró tï ASCII® Whilå +iô caî ofteî appeaò morå cluttered¬ iô ió easieò tï reaä WordStaò fileó anä +disë directorieó iî thió mode® + +Durinç operation¬ thå useò caî togglå thå typå oæ ASCIÉ displaù desireä bù +usinç thå non-menõ commanä Control-Á (^A© aô anù oæ thå maiî modå menus® +Therå ió alsï á ZCNFG-configurablå flaç thaô controló thå defaulô ASCIÉ +displaù mode® +:M +Memory Mode + +Memorù modå ió ZP'ó defaulô modå oæ operatioî wheî nï otheò modå ió +specifieä oî thå commanä line® Iæ nï startinç addresó ió specifieä oî thå +commanä line¬ thå addresó oæ thå ZCPR³ environmenô ió thå defaulô startinç +addresó foò thå ZÐ display¬ buô ZCNFÇ maù bå useä tï configurå á differenô +defaulô startinç address¬ sucè aó 100h¬ iæ desired® Thå displaù wilì alsï +starô aô thió configureä addresó wheî memorù modå ió entereä froí anotheò +ZÐ mode. + +Iæ yoõ includå á startinç addresó oî thå commanä linå (uð tï ´ hexidecimaì +characteró witè NÏ non-heø characters)¬ ZÐ wilì begiî itó displaù aô thå +memorù recorä containinç thå specifieä address® Á subsequenô ediô commanä +wilì alsï begiî aô thå specifieä address® + +ZÐ useó thå commanä buffeò aô 80è aó itó workinç recorä buffer¬ sï wheneveò +yoõ vie÷ thå commanä buffeò record¬ yoõ wilì actuallù bå lookinç aô á copù +oæ thå recorä thaô waó previouslù viewed® Notå ho÷ thå commanä buffeò +displaù changeó wheî yoõ steð througè iô froí oppositå directions® Wheî iô +ió displayinç thå commanä buffeò contents¬ ZÐ alsï displayó á speciaì +headeò tï reminä yoõ thaô yoõ arå viewinç thå commanä buffer¬ aó iô ió +generallù inadvisablå (anä usuallù futile© tï attempô tï ediô thå contentó +oæ thå commanä buffer. + +Thå 'T§ (Toð oæ Memory© commanä displayó thå highesô memorù recorä (FF80h- +FFFFh© anä thå 'B§ (Bottoí oæ Memory© commanä displayó thå firsô memorù +recorä (0000h-007Fh)® + +Thå 'A§ commanä allowó yoõ tï selecô thå addresó oæ á memorù 'record§ tï +view® ZÐ wilì displaù thå 12¸ bytå 'record§ containinç thaô address¬ witè +thå bytå aô thå specifieä addresó highlighteä iî botè thå Heø anä ASCIÉ +displays® Iæ thå ediô modå ió subsequentlù selected¬ thå ediô cursoò wilì +alsï bå positioneä aô thå specifieä address. +:D +Disk Mode + +Wheî yoõ selecô thå drivå yoõ wisè tï accesó iî disë mode¬ yoõ maù alsï +specifù á useò area¬ usinç eitheò á DUº oò DIRº specification® Thå +specifieä drive/useò theî becomeó thå defaulô foò anù subsequenô filå +accesses. + +ZÐ startó thå disë modå displaù witè thå firsô directorù record® Otheò +recordó maù bå selecteä bù block¬ record¬ oò track® + +Floppù disë systeí tracë recordó maù bå displayeä bù selectinç tracë ° oò +bù steppinç bacë froí thå directory® Harä disë systeí recordó maù bå +displayeä wheî thå 'first§ disë (á DP OFFSEÔ oæ lesó thaî 10© ió loggeä +in® Otherwiså thå displaù ió limiteä tï thå actuaì loggeä disë tracks® +Wheî steppinç pasô thå firsô oò lasô tracë oò recorä oæ thå disk¬ ZÐ wilì +wrað arounä tï thå otheò enä oæ thå disk. +:F +File Mode + +Thå drive/useò containeä iî anù filenamå specificatioî becomeó thå defaulô +foò anù subsequenô filå oò disë accesses® + +Wheî wildcarä filenameó arå entered¬ ZÐ wilì displaù thå firsô filå founä +iî thå directorù thaô matcheó thå specifieä ambiguouó filename® Iæ thå +filenamå entereä ió á COÍ file¬ á displaù offseô oæ 0100è wilì bå addeä +automaticallù tï thå filå recorä addresseó sï thaô thå firsô filå recorä +wilì starô aô á displaù addresó oæ 0100h® Otheò filå typeó wilì bå +displayeä startinç aô aî addresó oæ 0000è (offseô ½ 0)® Thå 'O§ (Offset© +commanä allowó yoõ tï changå thå displaù offseô aó desired. + +Thå 'A§ commanä allowó yoõ tï selecô aî addresó iî thå file® ZÐ wilì +displaù thå 12¸ bytå recorä containinç thaô address¬ witè thå bytå aô thå +specifieä addresó highlighteä iî botè thå Heø anä ASCIÉ displays® Iæ thå +ediô modå ió subsequentlù selected¬ thå ediô cursoò wilì alsï bå positioneä +aô thå specifieä address. +:X +String Search + +ZP'ó strinç searcè functioî ió similaò tï ZPATCH's¬ buô iô ió mucè morå +basiã anä haó nï options® Stringó tï bå locateä maù bå entereä aó heø oò +ASCIÉ characteró oò anù combination® ASCIÉ stringó arå entereä delimiteä +bù singlå oò doublå quoteó anä wilì bå automaticallù capitalized® Heø +characteró arå entereä delimiteä bù commaó oò spaces® Botè singlå anä +doublå quotå characteró musô bå entereä aó heø characters® Thå remaindeò Šoæ thå terminaì displaù linå afteò thå prompt¬ abouô 4µ characters¬ maù bå +useä foò characteò entry® Foò example¬ thå entry: + + 'Fil',65,6e,61 "me" will create the search string 'FILENAME' + +Á fe÷ compromiseó anä assumptionó havå beeî madå sï thaô thå samå searcè +routinå maù bå useä iî alì situations® Wheî doinç thå search¬ ZÐ wilì +firsô comparå á searcè characteò witè thå buffeò characteò withouô regarä +tï ASCIÉ case® Iæ nï matcè ió found¬ thå buffeò characteò wilì bå filtereä +anä capitalizeä (iæ ASCII© anä anotheò comparisoî made® Thió allowó yoõ tï +searcè foò texô iî WordStaò fileó oò disë directorieó withouô worryinç +abouô caså oò higè bits¬ whilå stilì retaininç thå abilitù tï dï searcheó +foò heø characteró sucè aó commanä oò addresó characters® Foò example: + + 'Filename§ wilì matcè 'FILEN','A'+80h,'ME§ or 'FILENAME§ + (wherå thå 'A§ haó thå higè biô set)¬ while + + 'Filen',E1,"me¢ wilì matcè 'FILEN','A'+80h,'ME'¬ buô noô +          'FILENAME§ (specificallù insistinç oî aî exacô matcè +          foò E1h¬ whicè ió 'A§ witè thå higè biô set). + +Aó á resulô oæ thió relativelù simplå searcè scheme¬ heø searcè characteró +thaô happeî tï alsï bå loweò caså ASCIÉ characteró wilì matcè botè exacô +anä 'capitalized§ characters¬ whicè mighô resulô iî aî occasionaì falså +positivå match® Foò examplå 65è ('e'© wilì matcè botè 65è anä 45è ('E'). + +Strinç searcheó maù bå stoppeä aô anù timå bù enterinç anù character® Disë +modå strinç searcheó wilì continuå untiì á matcè ió founä oò untiì yoõ stoð +thå search¬ wrappinç aô thå lasô tracë bacë tï thå firsô track® + +Wheî thå 'C§ commanä ió useä tï continuå á search¬ thå searcè wilì starô +froí thå addresó oæ thå lasô matcè iæ iô ió locateä iî thå recorä currentlù +displayed¬ otherwiså iô wilì starô aô thå beginninç oæ thå currenô record. + +Wheî á strinç ió located¬ thå recorä iî whicè iô ió founä wilì bå displayeä +anä thå firsô bytå oæ thå strinç wilì bå highlighteä iî botè thå Heø anä +ASCIÉ displays® Iæ thå ediô modå ió theî selected¬ thå ediô cursoò wilì bå +positioneä aô thå starô oæ thå founä string® Iæ á strinç ió noô found¬ thå +currenô recorä wilì bå redisplayed. +:E +Record Edit + +ZP¬ likå ZPATCH¬ useó thå WordStaò diamonä keyó (^S¬ ^E¬ ^D¬ ^X© tï controì +thå cursoò durinç editing® Thå backspacå keù ió aî alternatå tï ^Ó tï movå +thå cursoò left® Thå arro÷ keyó defineä iî thå Z3TCAÐ arå alsï mappeä aó +alternatå cursoò controì keys¬ eveî thougè theù arå noô showî iî thå ediô +modå commanä menu® + +Somå terminals¬ sucè aó ANSÉ terminals¬ have arro÷ keyó thaô generatå morå +thaî onå character® Foò theså terminals¬ thå arro÷ keù definitionó iî thå +Z3TCAÐ wilì probablù bå thå WordStaò diamonä keys® Pressinç aî actuaì +arro÷ keù maù causå itó strinç tï bå interpreteä bù ZÐ aó thå equivalenô +serieó oæ characters® Thió wilì usuallù bå undesirable® Iî thå caså oæ +ANSÉ terminals¬ thå arro÷ codeó wilì causå aî exiô froí ZP. + +Wheî thå ediô modå ió entereä followinç á strinç searcè oò aî addresó +specification¬ thå ediô cursoò wilì bå placeä aô thå beginninç oæ thå founä +strinç oò aô thå specifieä address® Specifyinç thå desireä ediô addresó +beforå enterinç thå ediô modå ió á quicë waù tï directlù reacè thå desireä +addresó iî thå recorä beinç edited® Thå addresó oæ thå bytå currentlù +beinç editeä ió displayeä iî thå ediô modå menõ box. + +Thå ESà keù toggleó thå ediô modå betweeî thå Heø anä ASCIÉ displays® + +ZÐ haó á speciaì Filì commanä (^F© thaô allowó yoõ tï filì á numbeò oæ +byteó iî thå 12¸ bytå recorä beinç editeä witè á specifieä filì byte® Thå +fill syntaø isº + + Filì bytå (iî heø oò quoteä ASCII: 0, e5, ff, '*', etc) + Separatoò (comma¬ spacå oò tab© + Decimaì numbeò oæ byteó tï bå filled (* = fill to end of record) + +Filló begin witè thå currenô cursoò position® Filì lengthó uð tï 25µ (FFh© +wilì bå accepted¬ buô á maximuí oæ 12¸ byteó can bå filled® Thå filì wilì +alwayó stoð aô thå enä oæ thå recorä iæ yoõ enteò á lengtè greateò thaî thå +numbeò oæ byteó remaininç iî thå record® Yoõ maù alsï enteò thå speciaì +lengtè characteò ª tï telì ZÐ tï filì tï thå enä oæ thå record® Enteò '?§ +aô thå filì prompô foò á helð screen. + +ZÐ useó thå ZPATCÈ commandó tï exiô thå ediô mode® Enteò ^Ñ tï quiô +withouô savinç thå changes¬ anä enteò ^× tï savå thå changeó anä exit® +:Q +HP Calculator Quick Reference + +ZÐ containó thå powerfuì anä full-featureä HP12 RPÎ programmer'ó integeò +calculator® Thå calculatoò maù bå calleä uð aô anù timå (eveî durinç thå +editinç process© tï perform¬ savå oò recalì thå resultó oæ integeò +calculationó oò logicaì operationó iî hex¬ decimal¬ binary¬ oò characteò +modes® + +Thå calculatoò ió botè invokeä anä exiteä bù enterinç ^C® Sincå thå +calculatoò retainó alì registeò informatioî aó lonç aó ZÐ ió running¬ +calculatoò registeró maù bå useä tï storå referencå datá betweeî otheò ZÐ +operations® Thå calculatoò maù alsï bå invokeä durinç recorä editinç +operationó withouô affectinç thå editinç procesó iî anù way. + HP12 Command Summary + + ^X Clear entire stack + BS Delete last character + Enter number into register X + = Exchange the contents of X and Y + L Restore last X value + < or , Roll stack down one level + > or . Roll stack up one level + Sn Store contents of X in memory register n (0-5 or R) + Rn Recall contents of memory register n to register X + ^C Exit HP12 + + HP12 Math Function Summary + +     «    Addition¬ X=Y+Ø +     ­    Subtraction¬ X=Y-Ø +     ª    Multiplication¬ X=Y*Ø +     Þ    Exponentiation¬ X=Y^Ø (Y to the power X) +     ¯    Integeò quotient¬ X=INT(Y/X© +      - Remainder in Register R=X*(Y/X-INT(Y/X)© +     ¦    Bitwiså AND¬ X=Y&X +     ü    Bitwiså OR¬ X=Y|X +     þ    Negatioî [2'ó complement]¬ X=~Ø +:K +Record Cache + +ZÐ provideó á one-recorä buffeò intï whicè yoõ caî copù thå currentlù +displayeä record® Yoõ caî lateò exchangå thå cacheä recorä witè thå recorä +beinç currentlù displayed® Thió allowó you¬ foò example¬ tï experimentallù +ediô á recorä whilå maintaininç á copù oæ thå unediteä recorä iî thå cachå +buffer® Notå thaô wheî yoõ exiô ZP¬ thå contentó oæ thå cachå buffeò wilì +bå lost¬ sï iæ yoõ wisè tï preservå á record¬ exchangå iô witè á recorä +froí á 'holding§ filå oæ aô leasô onå recorä iî lengtè thaô yoõ havå +previouslù reserveä foò recorä storagå (ZÐ wilì noô creatå files.© Yoõ caî +alsï savå á cacheä recorä iî aî unuseä 12¸ bytå 'record§ iî memory¬ usinç +SAVE.COÍ tï savå thå memorù recorä tï á filå afteò yoõ exiô ZP® Finally¬ +ZÐ contentó wilì remaiî preserveä iæ yoõ reenteò iô usinç thå JUMÐ (tï thå +loaä addresó displayeä iî thå signoî banner© command¬ assuminç thaô yoõ +haven'ô overwritteî thå ZÐ locations. + +Thå seconä ZÐ statuó linå displayó thå thå origiî oæ thå current cacheä +record® Copyinç á recorä intï thå cachå wilì overwritå thå previouó +contentó oæ thå cache® Performinç á cachå copù oò exchangå operatioî +automaticallù returnó yoõ tï youò previouó modå anä updateó thå currenô +recorä display. +:N:HP-RPN.HLP +:H:HP-ZP.HLP + \ No newline at end of file diff --git a/Source/Images/d_zpm3/u14/COPY.CFG b/Source/Images/d_zpm3/u14/COPY.CFG new file mode 100644 index 00000000..e55fcebf Binary files /dev/null and b/Source/Images/d_zpm3/u14/COPY.CFG differ diff --git a/Source/Images/d_zpm3/u14/ERASE.CFG b/Source/Images/d_zpm3/u14/ERASE.CFG new file mode 100644 index 00000000..aee78543 Binary files /dev/null and b/Source/Images/d_zpm3/u14/ERASE.CFG differ diff --git a/Source/Images/d_zpm3/u14/HELPC15.CFG b/Source/Images/d_zpm3/u14/HELPC15.CFG new file mode 100644 index 00000000..33b0cb1c Binary files /dev/null and b/Source/Images/d_zpm3/u14/HELPC15.CFG differ diff --git a/Source/Images/fd1/u0/ZCNFG24.CFG b/Source/Images/d_zpm3/u14/ZCNFG24.CFG similarity index 100% rename from Source/Images/fd1/u0/ZCNFG24.CFG rename to Source/Images/d_zpm3/u14/ZCNFG24.CFG diff --git a/Source/Images/d_zpm3/u14/ZEX.CFG b/Source/Images/d_zpm3/u14/ZEX.CFG new file mode 100644 index 00000000..20c5a255 Binary files /dev/null and b/Source/Images/d_zpm3/u14/ZEX.CFG differ diff --git a/Source/Images/d_zpm3/u14/ZF11.CFG b/Source/Images/d_zpm3/u14/ZF11.CFG new file mode 100644 index 00000000..32568460 Binary files /dev/null and b/Source/Images/d_zpm3/u14/ZF11.CFG differ diff --git a/Source/Images/d_zpm3/u14/ZP17.CFG b/Source/Images/d_zpm3/u14/ZP17.CFG new file mode 100644 index 00000000..0477e176 Binary files /dev/null and b/Source/Images/d_zpm3/u14/ZP17.CFG differ diff --git a/Source/Images/d_zpm3/u15/alias.com b/Source/Images/d_zpm3/u15/alias.com new file mode 100644 index 00000000..bdd1e667 Binary files /dev/null and b/Source/Images/d_zpm3/u15/alias.com differ diff --git a/Source/Images/d_zpm3/u15/arunz.com b/Source/Images/d_zpm3/u15/arunz.com new file mode 100644 index 00000000..9fb0a2f0 Binary files /dev/null and b/Source/Images/d_zpm3/u15/arunz.com differ diff --git a/Source/Images/d_zpm3/u15/copy.com b/Source/Images/d_zpm3/u15/copy.com new file mode 100644 index 00000000..606c81a5 Binary files /dev/null and b/Source/Images/d_zpm3/u15/copy.com differ diff --git a/Source/Images/d_zpm3/u15/date.com b/Source/Images/d_zpm3/u15/date.com new file mode 100644 index 00000000..67a84b2d Binary files /dev/null and b/Source/Images/d_zpm3/u15/date.com differ diff --git a/Source/Images/d_zpm3/u15/dev.com b/Source/Images/d_zpm3/u15/dev.com new file mode 100644 index 00000000..4360aab6 Binary files /dev/null and b/Source/Images/d_zpm3/u15/dev.com differ diff --git a/Source/Images/d_zpm3/u15/device.com b/Source/Images/d_zpm3/u15/device.com new file mode 100644 index 00000000..78075a3f Binary files /dev/null and b/Source/Images/d_zpm3/u15/device.com differ diff --git a/Source/Images/d_zpm3/u15/dir.com b/Source/Images/d_zpm3/u15/dir.com new file mode 100644 index 00000000..16ead13b Binary files /dev/null and b/Source/Images/d_zpm3/u15/dir.com differ diff --git a/Source/Images/d_zpm3/u15/diskinfo.com b/Source/Images/d_zpm3/u15/diskinfo.com new file mode 100644 index 00000000..068cc97a Binary files /dev/null and b/Source/Images/d_zpm3/u15/diskinfo.com differ diff --git a/Source/Images/d_zpm3/u15/du.com b/Source/Images/d_zpm3/u15/du.com new file mode 100644 index 00000000..896b1daa Binary files /dev/null and b/Source/Images/d_zpm3/u15/du.com differ diff --git a/Source/Images/d_zpm3/u15/dump.com b/Source/Images/d_zpm3/u15/dump.com new file mode 100644 index 00000000..97c10c03 Binary files /dev/null and b/Source/Images/d_zpm3/u15/dump.com differ diff --git a/Source/Images/d_zpm3/u15/ed.com b/Source/Images/d_zpm3/u15/ed.com new file mode 100644 index 00000000..203eafd7 Binary files /dev/null and b/Source/Images/d_zpm3/u15/ed.com differ diff --git a/Source/Images/d_zpm3/u15/erase.com b/Source/Images/d_zpm3/u15/erase.com new file mode 100644 index 00000000..cdc3ea60 Binary files /dev/null and b/Source/Images/d_zpm3/u15/erase.com differ diff --git a/Source/Images/d_zpm3/u15/gencom.com b/Source/Images/d_zpm3/u15/gencom.com new file mode 100644 index 00000000..d0816425 Binary files /dev/null and b/Source/Images/d_zpm3/u15/gencom.com differ diff --git a/Source/Images/d_zpm3/u15/gencpm.com b/Source/Images/d_zpm3/u15/gencpm.com new file mode 100644 index 00000000..d9d67499 Binary files /dev/null and b/Source/Images/d_zpm3/u15/gencpm.com differ diff --git a/Source/Images/d_zpm3/u15/get.com b/Source/Images/d_zpm3/u15/get.com new file mode 100644 index 00000000..7f12456e Binary files /dev/null and b/Source/Images/d_zpm3/u15/get.com differ diff --git a/Source/Images/d_zpm3/u15/goto.com b/Source/Images/d_zpm3/u15/goto.com new file mode 100644 index 00000000..5d9476fd Binary files /dev/null and b/Source/Images/d_zpm3/u15/goto.com differ diff --git a/Source/Images/d_zpm3/u15/help.com b/Source/Images/d_zpm3/u15/help.com new file mode 100644 index 00000000..378cf03b Binary files /dev/null and b/Source/Images/d_zpm3/u15/help.com differ diff --git a/Source/Images/d_zpm3/u15/hexcom.com b/Source/Images/d_zpm3/u15/hexcom.com new file mode 100644 index 00000000..e8eb54b4 Binary files /dev/null and b/Source/Images/d_zpm3/u15/hexcom.com differ diff --git a/Source/Images/d_zpm3/u15/if.com b/Source/Images/d_zpm3/u15/if.com new file mode 100644 index 00000000..01b857bb Binary files /dev/null and b/Source/Images/d_zpm3/u15/if.com differ diff --git a/Source/Images/d_zpm3/u15/initdir.com b/Source/Images/d_zpm3/u15/initdir.com new file mode 100644 index 00000000..03cce335 Binary files /dev/null and b/Source/Images/d_zpm3/u15/initdir.com differ diff --git a/Source/Images/hd0/s1/u0/LBREXT.COM b/Source/Images/d_zpm3/u15/lbrext.com similarity index 100% rename from Source/Images/hd0/s1/u0/LBREXT.COM rename to Source/Images/d_zpm3/u15/lbrext.com diff --git a/Source/Images/d_zpm3/u15/lib.com b/Source/Images/d_zpm3/u15/lib.com new file mode 100644 index 00000000..de16a896 Binary files /dev/null and b/Source/Images/d_zpm3/u15/lib.com differ diff --git a/Source/Images/d_zpm3/u15/link.com b/Source/Images/d_zpm3/u15/link.com new file mode 100644 index 00000000..fc66084b Binary files /dev/null and b/Source/Images/d_zpm3/u15/link.com differ diff --git a/Source/Images/d_zpm3/u15/loadseg.com b/Source/Images/d_zpm3/u15/loadseg.com new file mode 100644 index 00000000..503d7679 Binary files /dev/null and b/Source/Images/d_zpm3/u15/loadseg.com differ diff --git a/Source/Images/d_zpm3/u15/mac.com b/Source/Images/d_zpm3/u15/mac.com new file mode 100644 index 00000000..f1fdba57 Binary files /dev/null and b/Source/Images/d_zpm3/u15/mac.com differ diff --git a/Source/Images/hd0/s1/u0/MBASIC.COM b/Source/Images/d_zpm3/u15/mbasic.com similarity index 100% rename from Source/Images/hd0/s1/u0/MBASIC.COM rename to Source/Images/d_zpm3/u15/mbasic.com diff --git a/Source/Images/d_zpm3/u15/names.ndr b/Source/Images/d_zpm3/u15/names.ndr new file mode 100644 index 00000000..fc153076 Binary files /dev/null and b/Source/Images/d_zpm3/u15/names.ndr differ diff --git a/Source/Images/d_zpm3/u15/patch.com b/Source/Images/d_zpm3/u15/patch.com new file mode 100644 index 00000000..548b266e Binary files /dev/null and b/Source/Images/d_zpm3/u15/patch.com differ diff --git a/Source/Images/d_zpm3/u15/pip.com b/Source/Images/d_zpm3/u15/pip.com new file mode 100644 index 00000000..e1321a2a Binary files /dev/null and b/Source/Images/d_zpm3/u15/pip.com differ diff --git a/Source/Images/d_zpm3/u15/put.com b/Source/Images/d_zpm3/u15/put.com new file mode 100644 index 00000000..c9d35243 Binary files /dev/null and b/Source/Images/d_zpm3/u15/put.com differ diff --git a/Source/Images/d_zpm3/u15/remove.com b/Source/Images/d_zpm3/u15/remove.com new file mode 100644 index 00000000..4f2bc236 Binary files /dev/null and b/Source/Images/d_zpm3/u15/remove.com differ diff --git a/Source/Images/d_zpm3/u15/rename.com b/Source/Images/d_zpm3/u15/rename.com new file mode 100644 index 00000000..8582175f Binary files /dev/null and b/Source/Images/d_zpm3/u15/rename.com differ diff --git a/Source/Images/d_zpm3/u15/rmac.com b/Source/Images/d_zpm3/u15/rmac.com new file mode 100644 index 00000000..f608dc11 Binary files /dev/null and b/Source/Images/d_zpm3/u15/rmac.com differ diff --git a/Source/Images/d_zpm3/u15/rsxdir.com b/Source/Images/d_zpm3/u15/rsxdir.com new file mode 100644 index 00000000..dd880a61 Binary files /dev/null and b/Source/Images/d_zpm3/u15/rsxdir.com differ diff --git a/Source/Images/d_zpm3/u15/sainst.com b/Source/Images/d_zpm3/u15/sainst.com new file mode 100644 index 00000000..33075a7d Binary files /dev/null and b/Source/Images/d_zpm3/u15/sainst.com differ diff --git a/Source/Images/d_zpm3/u15/salias.com b/Source/Images/d_zpm3/u15/salias.com new file mode 100644 index 00000000..b9f3562f Binary files /dev/null and b/Source/Images/d_zpm3/u15/salias.com differ diff --git a/Source/Images/d_zpm3/u15/save.com b/Source/Images/d_zpm3/u15/save.com new file mode 100644 index 00000000..03ceb989 Binary files /dev/null and b/Source/Images/d_zpm3/u15/save.com differ diff --git a/Source/Images/d_zpm3/u15/set.com b/Source/Images/d_zpm3/u15/set.com new file mode 100644 index 00000000..e4c374dc Binary files /dev/null and b/Source/Images/d_zpm3/u15/set.com differ diff --git a/Source/Images/d_zpm3/u15/setdef.com b/Source/Images/d_zpm3/u15/setdef.com new file mode 100644 index 00000000..005ac7ad Binary files /dev/null and b/Source/Images/d_zpm3/u15/setdef.com differ diff --git a/Source/Images/d_zpm3/u15/setpath.com b/Source/Images/d_zpm3/u15/setpath.com new file mode 100644 index 00000000..4af639bc Binary files /dev/null and b/Source/Images/d_zpm3/u15/setpath.com differ diff --git a/Source/Images/d_zpm3/u15/show.com b/Source/Images/d_zpm3/u15/show.com new file mode 100644 index 00000000..5f2458ec Binary files /dev/null and b/Source/Images/d_zpm3/u15/show.com differ diff --git a/Source/Images/d_zpm3/u15/submit.com b/Source/Images/d_zpm3/u15/submit.com new file mode 100644 index 00000000..e0473c7c Binary files /dev/null and b/Source/Images/d_zpm3/u15/submit.com differ diff --git a/Source/Images/d_zpm3/u15/tcap.z3t b/Source/Images/d_zpm3/u15/tcap.z3t new file mode 100644 index 00000000..1a43a949 Binary files /dev/null and b/Source/Images/d_zpm3/u15/tcap.z3t differ diff --git a/Source/Images/d_zpm3/u15/type.com b/Source/Images/d_zpm3/u15/type.com new file mode 100644 index 00000000..17e695f2 Binary files /dev/null and b/Source/Images/d_zpm3/u15/type.com differ diff --git a/Source/Images/d_zpm3/u15/verror.com b/Source/Images/d_zpm3/u15/verror.com new file mode 100644 index 00000000..687ea587 Binary files /dev/null and b/Source/Images/d_zpm3/u15/verror.com differ diff --git a/Source/Images/d_zpm3/u15/vlu.com b/Source/Images/d_zpm3/u15/vlu.com new file mode 100644 index 00000000..722a9a00 Binary files /dev/null and b/Source/Images/d_zpm3/u15/vlu.com differ diff --git a/Source/Images/d_zpm3/u15/xref.com b/Source/Images/d_zpm3/u15/xref.com new file mode 100644 index 00000000..32c57ae0 Binary files /dev/null and b/Source/Images/d_zpm3/u15/xref.com differ diff --git a/Source/Images/hd0/s0/u0/zcnfg.com b/Source/Images/d_zpm3/u15/zcnfg.com similarity index 100% rename from Source/Images/hd0/s0/u0/zcnfg.com rename to Source/Images/d_zpm3/u15/zcnfg.com diff --git a/Source/Images/d_zpm3/u15/zerase.com b/Source/Images/d_zpm3/u15/zerase.com new file mode 100644 index 00000000..bda93efc Binary files /dev/null and b/Source/Images/d_zpm3/u15/zerase.com differ diff --git a/Source/Images/d_zpm3/u15/zex.com b/Source/Images/d_zpm3/u15/zex.com new file mode 100644 index 00000000..cd46405d Binary files /dev/null and b/Source/Images/d_zpm3/u15/zex.com differ diff --git a/Source/Images/d_zpm3/u15/zfiler.com b/Source/Images/d_zpm3/u15/zfiler.com new file mode 100644 index 00000000..e926d231 Binary files /dev/null and b/Source/Images/d_zpm3/u15/zfiler.com differ diff --git a/Source/Images/d_zpm3/u15/zhelp.com b/Source/Images/d_zpm3/u15/zhelp.com new file mode 100644 index 00000000..990301fe Binary files /dev/null and b/Source/Images/d_zpm3/u15/zhelp.com differ diff --git a/Source/Images/d_zpm3/u15/zp.com b/Source/Images/d_zpm3/u15/zp.com new file mode 100644 index 00000000..593fe381 Binary files /dev/null and b/Source/Images/d_zpm3/u15/zp.com differ diff --git a/Source/Images/d_zpm3/u15/zshow.com b/Source/Images/d_zpm3/u15/zshow.com new file mode 100644 index 00000000..0bd0b1de Binary files /dev/null and b/Source/Images/d_zpm3/u15/zshow.com differ diff --git a/Source/Images/d_zpm3/u15/zsid.com b/Source/Images/d_zpm3/u15/zsid.com new file mode 100644 index 00000000..686b3775 Binary files /dev/null and b/Source/Images/d_zpm3/u15/zsid.com differ diff --git a/Source/Images/d_zpm3/u15/zxd.com b/Source/Images/d_zpm3/u15/zxd.com new file mode 100644 index 00000000..16469ed0 Binary files /dev/null and b/Source/Images/d_zpm3/u15/zxd.com differ diff --git a/Source/Images/d_zsdos.txt b/Source/Images/d_zsdos.txt new file mode 100644 index 00000000..c9e552a1 --- /dev/null +++ b/Source/Images/d_zsdos.txt @@ -0,0 +1,17 @@ +# +# Add RomWBW utilities +# +../../Binary/Apps/*.com 0: +# +# Add Tune sample files +# +../../Binary/Apps/Tunes/*.pt? 3: +../../Binary/Apps/Tunes/*.mym 3: +# +# Add OS image +# +../ZSDOS/zsys_wbw.sys 0:zsys.sys +# +# Add Common Applications +# +Common/*.* 0: diff --git a/Source/Images/hd0/s1/u0/ASM.COM b/Source/Images/d_zsdos/u0/ASM.COM similarity index 100% rename from Source/Images/hd0/s1/u0/ASM.COM rename to Source/Images/d_zsdos/u0/ASM.COM diff --git a/Source/Images/d_zsdos/u0/CLOCKS.DAT b/Source/Images/d_zsdos/u0/CLOCKS.DAT new file mode 100644 index 00000000..e64c7e05 Binary files /dev/null and b/Source/Images/d_zsdos/u0/CLOCKS.DAT differ diff --git a/Source/Images/fd1/u0/COPY.CFG b/Source/Images/d_zsdos/u0/COPY.CFG similarity index 100% rename from Source/Images/fd1/u0/COPY.CFG rename to Source/Images/d_zsdos/u0/COPY.CFG diff --git a/Source/Images/fd1/u0/COPY.COM b/Source/Images/d_zsdos/u0/COPY.COM similarity index 100% rename from Source/Images/fd1/u0/COPY.COM rename to Source/Images/d_zsdos/u0/COPY.COM diff --git a/Source/Images/fd1/u0/COPY.UPD b/Source/Images/d_zsdos/u0/COPY.UPD similarity index 100% rename from Source/Images/fd1/u0/COPY.UPD rename to Source/Images/d_zsdos/u0/COPY.UPD diff --git a/Source/Images/hd0/s1/u0/CR.COM b/Source/Images/d_zsdos/u0/CR.COM similarity index 100% rename from Source/Images/hd0/s1/u0/CR.COM rename to Source/Images/d_zsdos/u0/CR.COM diff --git a/Source/Images/fd1/u0/DATSWEEP.COM b/Source/Images/d_zsdos/u0/DATSWEEP.COM similarity index 100% rename from Source/Images/fd1/u0/DATSWEEP.COM rename to Source/Images/d_zsdos/u0/DATSWEEP.COM diff --git a/Source/Images/d_zsdos/u0/DDT.COM b/Source/Images/d_zsdos/u0/DDT.COM new file mode 100644 index 00000000..70e4ebfe Binary files /dev/null and b/Source/Images/d_zsdos/u0/DDT.COM differ diff --git a/Source/Images/hd0/s1/u0/DDTZ.DOC b/Source/Images/d_zsdos/u0/DDTZ.DOC similarity index 100% rename from Source/Images/hd0/s1/u0/DDTZ.DOC rename to Source/Images/d_zsdos/u0/DDTZ.DOC diff --git a/Source/Images/d_zsdos/u0/DIRX.COM b/Source/Images/d_zsdos/u0/DIRX.COM new file mode 100644 index 00000000..413bceca Binary files /dev/null and b/Source/Images/d_zsdos/u0/DIRX.COM differ diff --git a/Source/Images/fd1/u0/DSCONFIG.COM b/Source/Images/d_zsdos/u0/DSCONFIG.COM similarity index 100% rename from Source/Images/fd1/u0/DSCONFIG.COM rename to Source/Images/d_zsdos/u0/DSCONFIG.COM diff --git a/Source/Images/hd0/s1/u0/DUMP.COM b/Source/Images/d_zsdos/u0/DUMP.COM similarity index 100% rename from Source/Images/hd0/s1/u0/DUMP.COM rename to Source/Images/d_zsdos/u0/DUMP.COM diff --git a/Source/Images/hd0/s1/u0/ED.COM b/Source/Images/d_zsdos/u0/ED.COM similarity index 100% rename from Source/Images/hd0/s1/u0/ED.COM rename to Source/Images/d_zsdos/u0/ED.COM diff --git a/Source/Images/fd1/u0/FA16.CFG b/Source/Images/d_zsdos/u0/FA16.CFG similarity index 100% rename from Source/Images/fd1/u0/FA16.CFG rename to Source/Images/d_zsdos/u0/FA16.CFG diff --git a/Source/Images/fd1/u0/FA16.DOC b/Source/Images/d_zsdos/u0/FA16.DOC similarity index 100% rename from Source/Images/fd1/u0/FA16.DOC rename to Source/Images/d_zsdos/u0/FA16.DOC diff --git a/Source/Images/fd1/u0/FA16A.FOR b/Source/Images/d_zsdos/u0/FA16A.FOR similarity index 100% rename from Source/Images/fd1/u0/FA16A.FOR rename to Source/Images/d_zsdos/u0/FA16A.FOR diff --git a/Source/Images/fd1/u0/FA16CFG.TXT b/Source/Images/d_zsdos/u0/FA16CFG.TXT similarity index 100% rename from Source/Images/fd1/u0/FA16CFG.TXT rename to Source/Images/d_zsdos/u0/FA16CFG.TXT diff --git a/Source/Images/fd1/u0/FILEATTR.COM b/Source/Images/d_zsdos/u0/FILEATTR.COM similarity index 100% rename from Source/Images/fd1/u0/FILEATTR.COM rename to Source/Images/d_zsdos/u0/FILEATTR.COM diff --git a/Source/Images/fd1/u0/FILEDATE.CFG b/Source/Images/d_zsdos/u0/FILEDATE.CFG similarity index 100% rename from Source/Images/fd1/u0/FILEDATE.CFG rename to Source/Images/d_zsdos/u0/FILEDATE.CFG diff --git a/Source/Images/fd1/u0/FILEDATE.COM b/Source/Images/d_zsdos/u0/FILEDATE.COM similarity index 100% rename from Source/Images/fd1/u0/FILEDATE.COM rename to Source/Images/d_zsdos/u0/FILEDATE.COM diff --git a/Source/Images/hd0/s1/u0/INITDIR.CFG b/Source/Images/d_zsdos/u0/INITDIR.CFG similarity index 100% rename from Source/Images/hd0/s1/u0/INITDIR.CFG rename to Source/Images/d_zsdos/u0/INITDIR.CFG diff --git a/Source/Images/hd0/s1/u0/INITDIR.COM b/Source/Images/d_zsdos/u0/INITDIR.COM similarity index 100% rename from Source/Images/hd0/s1/u0/INITDIR.COM rename to Source/Images/d_zsdos/u0/INITDIR.COM diff --git a/Source/Images/d_zsdos/u0/LBREXT.COM b/Source/Images/d_zsdos/u0/LBREXT.COM new file mode 100644 index 00000000..c0c950e3 Binary files /dev/null and b/Source/Images/d_zsdos/u0/LBREXT.COM differ diff --git a/Source/Images/d_zsdos/u0/LDDS.COM b/Source/Images/d_zsdos/u0/LDDS.COM new file mode 100644 index 00000000..357f1360 Binary files /dev/null and b/Source/Images/d_zsdos/u0/LDDS.COM differ diff --git a/Source/Images/d_zsdos/u0/LDNZT.COM b/Source/Images/d_zsdos/u0/LDNZT.COM new file mode 100644 index 00000000..87cf0b47 Binary files /dev/null and b/Source/Images/d_zsdos/u0/LDNZT.COM differ diff --git a/Source/Images/d_zsdos/u0/LDP2D.COM b/Source/Images/d_zsdos/u0/LDP2D.COM new file mode 100644 index 00000000..a89e03d8 Binary files /dev/null and b/Source/Images/d_zsdos/u0/LDP2D.COM differ diff --git a/Source/Images/hd0/s1/u0/LIB.COM b/Source/Images/d_zsdos/u0/LIB.COM similarity index 100% rename from Source/Images/hd0/s1/u0/LIB.COM rename to Source/Images/d_zsdos/u0/LIB.COM diff --git a/Source/Images/hd0/s1/u0/LINK.COM b/Source/Images/d_zsdos/u0/LINK.COM similarity index 100% rename from Source/Images/hd0/s1/u0/LINK.COM rename to Source/Images/d_zsdos/u0/LINK.COM diff --git a/Source/Images/hd0/s1/u0/LOAD.COM b/Source/Images/d_zsdos/u0/LOAD.COM similarity index 100% rename from Source/Images/hd0/s1/u0/LOAD.COM rename to Source/Images/d_zsdos/u0/LOAD.COM diff --git a/Source/Images/hd0/s1/u0/MAC.COM b/Source/Images/d_zsdos/u0/MAC.COM similarity index 100% rename from Source/Images/hd0/s1/u0/MAC.COM rename to Source/Images/d_zsdos/u0/MAC.COM diff --git a/Source/Images/d_zsdos/u0/MBASIC.COM b/Source/Images/d_zsdos/u0/MBASIC.COM new file mode 100644 index 00000000..c9ec3cd3 Binary files /dev/null and b/Source/Images/d_zsdos/u0/MBASIC.COM differ diff --git a/Source/Images/hd0/s1/u0/PIP.COM b/Source/Images/d_zsdos/u0/PIP.COM similarity index 100% rename from Source/Images/hd0/s1/u0/PIP.COM rename to Source/Images/d_zsdos/u0/PIP.COM diff --git a/Source/Images/d_zsdos/u0/PMARC.COM b/Source/Images/d_zsdos/u0/PMARC.COM new file mode 100644 index 00000000..59bd3ef3 Binary files /dev/null and b/Source/Images/d_zsdos/u0/PMARC.COM differ diff --git a/Source/Images/d_zsdos/u0/PMEXT.COM b/Source/Images/d_zsdos/u0/PMEXT.COM new file mode 100644 index 00000000..d3a51ca3 Binary files /dev/null and b/Source/Images/d_zsdos/u0/PMEXT.COM differ diff --git a/Source/Images/fd1/u0/PUTBG.COM b/Source/Images/d_zsdos/u0/PUTBG.COM similarity index 100% rename from Source/Images/fd1/u0/PUTBG.COM rename to Source/Images/d_zsdos/u0/PUTBG.COM diff --git a/Source/Images/hd0/s1/u0/PUTDS.COM b/Source/Images/d_zsdos/u0/PUTDS.COM similarity index 100% rename from Source/Images/hd0/s1/u0/PUTDS.COM rename to Source/Images/d_zsdos/u0/PUTDS.COM diff --git a/Source/Images/fd1/u0/RELOG.COM b/Source/Images/d_zsdos/u0/RELOG.COM similarity index 100% rename from Source/Images/fd1/u0/RELOG.COM rename to Source/Images/d_zsdos/u0/RELOG.COM diff --git a/Source/Images/hd0/s1/u0/RMAC.COM b/Source/Images/d_zsdos/u0/RMAC.COM similarity index 100% rename from Source/Images/hd0/s1/u0/RMAC.COM rename to Source/Images/d_zsdos/u0/RMAC.COM diff --git a/Source/Images/fd1/u0/SETTERM.COM b/Source/Images/d_zsdos/u0/SETTERM.COM similarity index 100% rename from Source/Images/fd1/u0/SETTERM.COM rename to Source/Images/d_zsdos/u0/SETTERM.COM diff --git a/Source/Images/fd1/u0/SETUPZST.COM b/Source/Images/d_zsdos/u0/SETUPZST.COM similarity index 100% rename from Source/Images/fd1/u0/SETUPZST.COM rename to Source/Images/d_zsdos/u0/SETUPZST.COM diff --git a/Source/Images/hd0/s1/u0/STAMPS.DAT b/Source/Images/d_zsdos/u0/STAMPS.DAT similarity index 100% rename from Source/Images/hd0/s1/u0/STAMPS.DAT rename to Source/Images/d_zsdos/u0/STAMPS.DAT diff --git a/Source/Images/hd0/s1/u0/STAT.COM b/Source/Images/d_zsdos/u0/STAT.COM similarity index 100% rename from Source/Images/hd0/s1/u0/STAT.COM rename to Source/Images/d_zsdos/u0/STAT.COM diff --git a/Source/Images/d_zsdos/u0/SUBMIT.COM b/Source/Images/d_zsdos/u0/SUBMIT.COM new file mode 100644 index 00000000..f651bfee Binary files /dev/null and b/Source/Images/d_zsdos/u0/SUBMIT.COM differ diff --git a/Source/Images/hd0/s0/u0/SUPERSUB.COM b/Source/Images/d_zsdos/u0/SUPERSUB.COM similarity index 100% rename from Source/Images/hd0/s0/u0/SUPERSUB.COM rename to Source/Images/d_zsdos/u0/SUPERSUB.COM diff --git a/Source/Images/fd1/u0/TD.CFG b/Source/Images/d_zsdos/u0/TD.CFG similarity index 100% rename from Source/Images/fd1/u0/TD.CFG rename to Source/Images/d_zsdos/u0/TD.CFG diff --git a/Source/Images/fd1/u0/TD.COM b/Source/Images/d_zsdos/u0/TD.COM similarity index 100% rename from Source/Images/fd1/u0/TD.COM rename to Source/Images/d_zsdos/u0/TD.COM diff --git a/Source/Images/fd1/u0/TERMBASE.DAT b/Source/Images/d_zsdos/u0/TERMBASE.DAT similarity index 100% rename from Source/Images/fd1/u0/TERMBASE.DAT rename to Source/Images/d_zsdos/u0/TERMBASE.DAT diff --git a/Source/Images/fd1/u0/TESTCLOK.COM b/Source/Images/d_zsdos/u0/TESTCLOK.COM similarity index 100% rename from Source/Images/fd1/u0/TESTCLOK.COM rename to Source/Images/d_zsdos/u0/TESTCLOK.COM diff --git a/Source/Images/hd0/s1/u0/UNCR.COM b/Source/Images/d_zsdos/u0/UNCR.COM similarity index 100% rename from Source/Images/hd0/s1/u0/UNCR.COM rename to Source/Images/d_zsdos/u0/UNCR.COM diff --git a/Source/Images/hd0/s1/u0/UNZIP.COM b/Source/Images/d_zsdos/u0/UNZIP.COM similarity index 100% rename from Source/Images/hd0/s1/u0/UNZIP.COM rename to Source/Images/d_zsdos/u0/UNZIP.COM diff --git a/Source/Images/hd0/s1/u0/XSUB.COM b/Source/Images/d_zsdos/u0/XSUB.COM similarity index 100% rename from Source/Images/hd0/s1/u0/XSUB.COM rename to Source/Images/d_zsdos/u0/XSUB.COM diff --git a/Source/Images/d_zsdos/u0/ZCAL.COM b/Source/Images/d_zsdos/u0/ZCAL.COM new file mode 100644 index 00000000..f239e952 Binary files /dev/null and b/Source/Images/d_zsdos/u0/ZCAL.COM differ diff --git a/Source/Images/hd0/s1/u0/ZCNFG.COM b/Source/Images/d_zsdos/u0/ZCNFG.COM similarity index 100% rename from Source/Images/hd0/s1/u0/ZCNFG.COM rename to Source/Images/d_zsdos/u0/ZCNFG.COM diff --git a/Source/Images/hd0/s1/u0/ZCNFG24.CFG b/Source/Images/d_zsdos/u0/ZCNFG24.CFG similarity index 100% rename from Source/Images/hd0/s1/u0/ZCNFG24.CFG rename to Source/Images/d_zsdos/u0/ZCNFG24.CFG diff --git a/Source/Images/hd0/s1/u0/ZPATH.COM b/Source/Images/d_zsdos/u0/ZPATH.COM similarity index 100% rename from Source/Images/hd0/s1/u0/ZPATH.COM rename to Source/Images/d_zsdos/u0/ZPATH.COM diff --git a/Source/Images/hd0/s1/u0/ZSCONFIG.COM b/Source/Images/d_zsdos/u0/ZSCONFIG.COM similarity index 100% rename from Source/Images/hd0/s1/u0/ZSCONFIG.COM rename to Source/Images/d_zsdos/u0/ZSCONFIG.COM diff --git a/Source/Images/d_zsdos/u0/ZSID.COM b/Source/Images/d_zsdos/u0/ZSID.COM new file mode 100644 index 00000000..686b3775 Binary files /dev/null and b/Source/Images/d_zsdos/u0/ZSID.COM differ diff --git a/Source/Images/fd1/u0/ZSVSTAMP.COM b/Source/Images/d_zsdos/u0/ZSVSTAMP.COM similarity index 100% rename from Source/Images/fd1/u0/ZSVSTAMP.COM rename to Source/Images/d_zsdos/u0/ZSVSTAMP.COM diff --git a/Source/Images/fd1/u0/ZSVSTAMP.DOC b/Source/Images/d_zsdos/u0/ZSVSTAMP.DOC similarity index 100% rename from Source/Images/fd1/u0/ZSVSTAMP.DOC rename to Source/Images/d_zsdos/u0/ZSVSTAMP.DOC diff --git a/Source/Images/hd0/s1/u0/ZXD.CFG b/Source/Images/d_zsdos/u0/ZXD.CFG similarity index 100% rename from Source/Images/hd0/s1/u0/ZXD.CFG rename to Source/Images/d_zsdos/u0/ZXD.CFG diff --git a/Source/Images/fd1/u0/ZXD.COM b/Source/Images/d_zsdos/u0/ZXD.COM similarity index 100% rename from Source/Images/fd1/u0/ZXD.COM rename to Source/Images/d_zsdos/u0/ZXD.COM diff --git a/Source/Images/d_zsdos/u1/SAMPKEY.DOC b/Source/Images/d_zsdos/u1/SAMPKEY.DOC new file mode 100644 index 00000000..911c4a11 --- /dev/null +++ b/Source/Images/d_zsdos/u1/SAMPKEY.DOC @@ -0,0 +1,134 @@ +SAMPKEY.DOC: +Sample Macros for ZDE, 01 June 90 by Carson Wilson. + +This file describes the sample macros contained in SAMPKEY.ZDK +and SAMPKEY.ZDT. As distributed, ZDE contains no Macro Key +definitions. I have included the SAMPKEY.* files both to give +you an idea of the versatility available through ZDE's macro +capability and to provide you with some useful choresaving +routines. Many of the sample Macros come directly from the ZDE +Manual, and illustrate the use of conditional testing, jumps, and +control key entry from within Macros. In addition to being +educational, most of these "sample" Macros do useful work. While +you may (and should) define ZDE's Macros to do anything you wish +(or nothing, if you wish), I think you may find one or more of +these samples useful just as they are. + +To install the macros in SAMPKEY.ZDK (or any .ZDK file) into your +copy of ZDE, just type "ZDENSTAL ZDE SAMPKEY.ZDK." Then exit +ZDENSTAL with the ave option. This produces a working copy of +ZDE.COM with Macro Keys 0 to 9 as defined in the .ZDK file. + +To alter or replace the Macros in SAMPKEY.ZDK, first edit the +text file SAMPKEY.ZDT with ZDE. Then translate SAMPKEY.ZDT into +a new macro file with the command "ZDKCOM SAMPKEY.ZDT." You can +also translate .ZDK files back into .ZDT files if you wish; see +ZDKCOM.DOC for more information. + +The following briefly describes each of the Macros in the SAMPKEY +files: + + +MACRO 0 - SWAP CHARACTERS. + + Macro Text: + q^S^S^G^D^U^U^D^G + + Purpose: + Swaps the character behind the cursor with the previous + character. Useful for catching typos. + + +MACRO 1 - SOFTEN PARAGRAPH. + + Macro Text: + q^QS^X^[=^M]^S^V ^V^D^[![ + + Purpose: + "Softens" Hard Carriage Returns from the line the cursor + is on to the end of the paragraph. This allows + reformatting with ^B of files from other word + processors. + + +MACRO 2 - MOVE TO START OF SENTENCE. + + Macro Text: + q^[~.1^S^[1^[~.<^[2^D^[= 2^[=^M2 + + Purpose: + Moves the cursor to the beginning of the sentance the + cursor is currently on. + + +MACRO 3 - MOVE TO START OF PARAGRAPH. + + Macro Text: + q^QS^S^S^[= [^D^D + + Purpose: + Moves the cursor to the beginning of the paragraph the + cursor is currently on. + + +MACRO 4 - REFORM AND RETURN. + + Macro Text: + q^B^QP + + Purpose: + Reformat beginning with the line the cursor is on and + return cursor to current location. Useful when + revising text in the middle of a paragraph. + + +MACRO 5 - BROWSE FILE. + + Macro Text: + n^[;^C^[![ + + Purpose: + Move through file a screen at a time. Press ESC to + stop. + + +MACRO 6 - DELETE WORD LEFT. + + Macro Text: + n^A^T + + Purpose: + Erases the word to the left of the cursor. + + +MACRO 7 - UNDERLINE WORD RIGHT. + + Macro Text: + n^V^PS^F^PS + + Purpose: + Brackets the word to the right of the cursor with the + control code ^S, causing the word to be underlined when + printed (NOTE: printer must be installed). + + +MACRO 8 - SWAP LINES. + + Macro Text: + q^QS^KB^S^X^KK^S^E^E^KV + + Purpose: + Swaps the current line with the previous line, moving + the cursor with it. Can be used to move a single line + up any distance within your file. + + +MACRO 9 - HELP MESSAGE. + + Macro Text: n^QE^QS^N^N0=SwCh 1=Soft 2=SentBg 3=ParaBg + 4=Reform 5=Browse 6=DelLft 7=Undl 8=SwLn 9=Hlp + ^[;^[;^[;^Y^Y^QP + + Purpose: + Briefly displays help message at top of screen. + \ No newline at end of file diff --git a/Source/Images/d_zsdos/u1/SAMPKEY.ZDK b/Source/Images/d_zsdos/u1/SAMPKEY.ZDK new file mode 100644 index 00000000..9bb5971f Binary files /dev/null and b/Source/Images/d_zsdos/u1/SAMPKEY.ZDK differ diff --git a/Source/Images/d_zsdos/u1/SAMPKEY.ZDT b/Source/Images/d_zsdos/u1/SAMPKEY.ZDT new file mode 100644 index 00000000..d41b3db3 --- /dev/null +++ b/Source/Images/d_zsdos/u1/SAMPKEY.ZDT @@ -0,0 +1,11 @@ +q^S^S^G^D^U^U^D^G +q^QS^X^[=^M]^S^V ^V^D^[![ +q^[~.1^S^[1^[~.<^[2^D^[= 2^[=^M2 +q^QS^S^S^[= [^D^D +q^B^QP +n^[;^C^[![ +n^A^T +n^V^PS^F^PS +q^QS^KB^S^X^KK^S^E^E^KV +n^QE^QS^N^N0=SwCh 1=Soft 2=SentBg 3=ParaBg 4=Reform 5=Browse 6=DelLft 7=Undl 8=SwLn 9=Hlp ^[;^[;^[;^Y^Y^QP + \ No newline at end of file diff --git a/Source/Images/d_zsdos/u1/ZDE10.DOC b/Source/Images/d_zsdos/u1/ZDE10.DOC new file mode 100644 index 00000000..83ce8cc9 --- /dev/null +++ b/Source/Images/d_zsdos/u1/ZDE10.DOC @@ -0,0 +1,1049 @@ +..You must print this file in "W" mode with page length set to 54 +..(the default) so that page numbers correspond with the Table of +..Contents and the Index. Use the "L" option for a left margin +..("L8" centers standard pitch on 8.5" wide paper), and the "" +..option to number pages. + + ZDE + + Z-System Display Editor + + Version 1.0 10 Mar 89 + + ZDE and its documentation are copyright 1989 by Carson Wilson, + all rights reserved. They may not be circulated in any + incomplete or modified form without written permission of the + author. Any commercial use of ZDE, defined as any situation + where the duplicator receives revenue by duplicating or + distributing ZDE by itself or in conjunction with any hardware + or software product, is expressly prohibited unless authorized + in writing by Carson Wilson. + + +1. Introduction. + + ZDE, the Z-System Display Editor, is a small, fast, powerful +text editor based Eric Meyer's famous Video Display Editor (VDE). +ZDE retains all features of VDE, but removes defects and supports +special ZCPR and ZSDOS features such as named directories, +register variables, automatic disk relog, and file datestamp +support. The install program has also been improved (see +ZDE10.NEW for a complete rundown of improvements). At the same +time, I have tried to be conscientious about the balance between +features and memory usage. If I added every new feature or +convenience that came to mind, we would soon have a luxurious +in-memory editor for 2 kilobyte files! Much of the following is +adapted with permission from Eric Meyer's VDE.DOC. + + ZDE's native ASCII mode and definable macros make it an ideal +programmer's editor; its full formatting and printing features +also make it an efficient word processor. Written entirely in Z80 +assembler, ZDE is F-A-S-T. There are no disk overlays, and all +editing is done in memory. As a result, finding a string near the +end of a 50K file takes WordStar 3.3 about 14 seconds (8MHz Z80); +ZDE does it in under a second. This is like moving through air +instead of molasses: you will find that you can do more of your +work on screen, and less on paper. + +Among ZDE's features are: + +Full-screen editing User area and named directory support +Block operations Wordwrap and reformat +File datestamp support Macro programs +Disk file operations Margins, tabs, spacing +Find and replace WordStar compatibility +Many print features Undeletion +Configurable options Support for all CP/M terminals + + +2. Installing ZDE. + + ZDE works with Z80 CP/M 2.2 and 3.0 and compatible systems. +It is ideal for portable computers with limited disk space. There +are many user configurable options, and ZDE can be installed for +all CP/M terminals. If you are running ZCPR, ZSDOS or Z3PLUS, ZDE +offers features not available with less advanced system software, +but these system enhancements are not required. + + To install ZDE, use the ZDENSTAL configuration program; see +the accompanying file ZDENSTAL.DOC. You should install ZDE for +your computer's terminal at the earliest convenience, since this +greatly enhances performance. There are many other installable +options; you will discover how you want everything set in the +course of using ZDE, so don't worry about going through all of it +at first. For ease of reference, portions of this manual +referring to installable options and settings are enclosed in +square brackets ("[" and "]"). + + +3. Invoking ZDE. + + ZDE is invoked from your system's command prompt using the +following syntax ("ufn" means "unambiguous file name"): + + ZDE Begin working on a new file. + + ZDE ufn Edit a new or already existing file. + + ZDE dir:ufn Edit a new or existing file from another + directory. + + ZDE ufn m Edit a file using mode "m." + ZDE ufn[m + +"m" above can be either W, A, or N (see File Modes, below), and +"dir" can be either a drive, a user area, a drive followed by a +user area, or a ZCPR named directory. For example: + + ZDE A4:MYFILE.TXT N Begin working on MYFILE.TXT at user + area 4, drive A, in non-document + mode. + + The size of the file to be edited is limited by available +memory. This will vary for different systems, but normally the +maximum size will be between 45 and 55k. If a file is too large +to edit, you must break it up and edit the pieces separately. +Numerous CP/M utilities are available which will break up and +rejoin ASCII files. + + +4. ZDE's Command Set. + + ZDE's commands consist of simple one- or two-key +combinations, easily found by the touch typist without +distraction. Most commands are the same as WordStar's. If you +have questions that this file can't answer, a WordStar manual may +be a useful reference. But ZDE is not a WordStar "clone"; there +are significant differences, including an extended set of +ESC-commands for functions such as macros (see below). + Virtually complete compatibility with the WordStar command +(sub)set can be achieved UNLESS the keys ^J, ^K, and ^L are used +as arrow keys (as on many CP/M computers). In this event ZDE +synonyms must be used: ESC- for the ^K- prefix; ESC-H for ^J; and +^\ for ^L (see ZDENSTAL.DOC for terminal installation). + +4.1. Command Summary. + + Below, the "^" character indicates use of the Ctrl key: ^K = +Ctrl+K. The ESC (^K), ^O, and ^Q prefixes require pressing two +keys in sequence: ^O C, for instance means press ^O, then C (or +^C). Any prefix may be canceled by typing ESC or Space. + +4.1.1. Control Keys: Single Keystroke Commands. + + ^J (ESC H) = display Help menus. + + CR = Carriage return (^M). Marks a paragraph end. + BS = Backspace (^H). + TAB = Hard Tab mode: insert tab (^I). Variable Tab mode: move + to next stop. + + Arrow keys: WordStar: ^E up, ^X down, ^S left, ^D right. + Alternate: configurable, default ^K, ^J, ^H, ^L. + + ^F = move to start of next word right. + ^A = move to start of next word left. + ^R = scroll back one screen. + ^C = scroll forward one screen. + ^W = scroll back one line. + ^Z = scroll forward one line. + + ^G = delete character to the right of the cursor. + DEL = delete character to the left (configurable). + ^U = undelete a character. + ^T = delete word to right of cursor. + ^Y = delete current line. + + ^N = insert a carriage return (break line) at present + position. + ^V = toggle INSERT mode on and off. + ^^ = toggle case (upper/lower) of character at cursor. + ^P = insert following control code in text. + ^B = reformat current paragraph. + ^L (^\) = repeat find/replace (repeats last ^QF or ^QA + command). + +4.1.2. File and Block Commands: first hit ^K (or ESC), then the + key shown. + + ^K I = display file/memory Information message. + + ^K F = List files in disk directory. + ^K E = Erase a disk file. + + ^K L = Load a brand new file to begin editing. + ^K N = change the current file Name (affects Save, eXit). + ^K S = Save the current file to disk, and continue editing. + ^K D = Done. Save the file, then load a new one. + ^K X = eXit: Save the file, then Quit to CP/M. + ^K Q = Quit to CP/M, abandoning current file. + + ^K R = Read a disk file into text at cursor position. + ^K P = Print the text (whole file or block). + + ^K B = mark the start of a Block. + ^K K = mark the end of a block. + ^K U = Unmark the block. + ^K Y = Delete the marked block. + ^K C = Copy the block text at present cursor position. + ^K V = moVe the block text to the present cursor position. + ^K W = Write the marked block to a disk file. + +4.1.3. Escape and Macro Commands: first hit ESC, then the key + shown. + + ESC arrows: Left/Right (including ^S/D) = shift screen + horizontally + by 32 columns. + Up/Down (including ^E/X) = shift screen + vertically + by 1/4 screen. + ESC TAB = move back to last tab stop. + + ESC M = execute a Macro string of commands. + ESC # = store macro on numeric key for later recall. + ESC 0..9 = use stored key. (In macro mode: jump label.) + ESC !,=,~,+ = used in Macro programming (see below). + ESC ; = brief pause, during Macro execution only. + +4.1.4. Quick Commands: first hit ^Q, then the key shown. + + ^Q Arrows: Left/Right (including ^S/D) = go to start or end + of line. + Up/Down (including ^E/X) = go to top or bottom + of screen. + ^Q R = move to top of file. + ^Q C = move to end of file. + ^Q I = move to specified page or line number. + ^Q B = move to marked block. + ^Q Q = move to next line in queue (ZCPR only). + ^Q Z = move to next place marker. + + ^Q F = find next occurrence of a string. + ^Q A = find and replace a string. + + ^Q Y = delete from cursor to end of current line. + ^Q DEL = delete from cursor to beginning of current line. + ^Q T = delete until specified character (caution--powerful!). + ^Q U = undelete a line. + +4.1.5. Onscreen Commands: first hit ^O, then the key shown. + + ^O Arrow: Up (including ^E) = make current line top of + screen. + + ^O R = set right margin (column 1 turns off + wordwrap/formatting). + ^O L = set left margin. + ^O X = toggle Margin Release on/off. + ^O C = center current line. + ^O F = align current line flush with the right margin. + + ^O Q = toggle header on/off. + ^O T = toggle ruler line on/off. + ^O D = toggle display of hard CRs on/off. + + ^O A = toggle Auto Indent on/off. + ^O S = toggle Double Spacing on/off. + ^O H = toggle hyphenation on/off. + ^O V = toggle tab mode Hard/Variable. + ^O I = set variable tab stop. + ^O N = clear variable tab stop. + + ^O P = set page length (0 turns off pagination). + ^O W = toggle windowing on/off (see below). + ^O Z = temporarily blank the entire screen. + + +5. Command Descriptions. + +5.1. Auto-Indent Mode (^O-A). + + Auto-Indent is useful for typing outlines, structured program +source code, and other text where the "left margin" varies. +Auto-Indent causes the RETURN key to act differently: If you are +entering new text, it will be indented to match the previous line. +If you are just moving through the file, the cursor advances past +any existing indentation. + +5.2. Block Commands (^K-B, -K, -U, -R, -Y, -W, -C, -V, -P-B, + ^Q-B). + + A "block" of text is normally delimited by two markers +[default: ^@] which remain in memory until reset or deleted. + ^K-B marks the beginning of the block; ^K-K marks its end. +Markers are inserted in the text. The two markers are identical; +the first one present is the start. + ^K-U unmarks the block, removing any marker(s) set (block +markers can also be deleted individually as ordinary characters). +Markers are automatically removed as appropriate when ^K-B/-K are +used again. + ^K-R reads in the contents of a disk file, inserting it as a +block after the current cursor position. You will be asked for +the name (and, optionally, mode) of the file. Other block +operation commands all require a Block to be marked: + ^K-Y deletes the block (including markers). + ^K-W writes the block text to a disk file; you will be asked +for the filename (and optional mode). + ^K-V moves the Block text to the present cursor location, +deleting the original; ^K-C copies it and leaves the original. +Sometimes ZDE will run out of memory when moving blocks within a +large file. If this occurs, just write the block to a file, +delete it, and read it back in as: "^K-W, ^K-Y, ^K-R". + ^K-P-B (^K-P with B option) prints the block text only. + The ^Q-B command, from wherever you are in the file, moves +the cursor to the Block start. + +5.3. Cursor Movement (Arrow keys; ^F; ^A; ^Q-R, -C, -I). + + ZDE supports three sets of Arrow keys, which function +interchangeably. The two built-in sets support the WordStar +"arrow key diamond" ^E, ^X, ^D, ^S, and the ANSI standard 3-byte +sequences (ESC-[-A, etc.). The third set is user-configurable, +and must be installed with ZDENSTAL. These keys move the cursor +up, down, right, and left respectively. Note: if you install ^J, +^K, and ^L as arrow keys, you must use ESC- commands for help, +file operations, and repeat find/replace, respectively. + Preceded by ^Q-, any arrow key (except ANSI) moves more +quickly: to the top or bottom of the screen, to the left or right +end of the line. + There are also two word movement commands: ^F moves right, to +the start of the next word; ^A moves left, to the start of the +previous (or current) word. Both have maximum ranges of 255 +characters. + For quickly covering large distances, the commands ^Q-R and +^Q-C go all the way to the beginning and end of the file, +respectively, and ^Q-I goes to any specified page (or line, in +non-documents). + +5.4. Deleting (^G, DEL, ^T, ^Y, ^Q-Y, -DEL, -T). + + You can delete text one CHARACTER at a time: ^G deletes to +the right of the cursor, and DEL to the left. [If you have no DEL +key, you can install another equivalent.] Note that the ordinary +BS (^H) does not normally delete. + ^T deletes an entire WORD to the right (up to 255 characters) + ^Y deletes the entire current LINE. ^Q-Y deletes only the +part of the line to the right of the cursor; ^Q-DEL deletes the +part to the left. + ^Q-T deletes UP TO a specified character. Example: "^Q-T." +deletes to the end of the sentence. Special case: "^Q-T-CR" +deletes to the next HARD CR, the end of the paragraph. This is a +powerful command, so use it with caution. + Accidentally deleted text can usually be recovered (see +Undelete, below). + +5.5. Disk Operations (^K-F, -E). + + ^K-F gives an unsorted list of disk FILES: hit CR to list the +directory specified by the current file, or specify a drive, user, +or named directory (colon optional). If there is not enough room +to fit all the files on the screen, you will see "..." at the end +to indicate that there were still more. Press ESC or Space to +continue. + ^K-E will ERASE a single disk file to provide more room on +the disk (no wildcards allowed). + +5.6. File Commands (^K-N, -S, -X, -Q, -D, -L). + + ^K-N NAMES your work or changes the current file mode. You +can change the filename in the header before saving, and/or change +its mode to WordStar, ASCII, or Nondocument. To set the file +mode, introduce the mode character with the left square bracket, +e.g., "Name: MYFILE.WS [W". + ^K-S SAVES your work: what's in memory is written to disk +under the file name in the header (you must have a file name; one +will be requested if necessary). If that file already existed, a +backup (.BAK) file may be preserved [configurable; see +installation guide]. If you haven't changed the file, ZDE prompts +you to confirm that you want to resave it anyway. + There are several different commands for finishing up: + ^K-X saves your work and then EXITS to CP/M. + ^K-Q just QUITS. If the file has been modified, ZDE asks if +you want to abandon the changes. + ^K-D (DONE) saves your work, then loads a new file to edit. + ^K-L quits the current file and LOADS a new one to begin +editing. + +5.7. Find, Replace (^Q-F, -A, ^L, ^\). + + ^Q-F is the command to FIND a string. The search normally +proceeds from the cursor position forward, and is case +(upper/lower) insensitive. There are two options: "B" = search +backwards; "C" = case sensitive search. If used, the options +must be enclosed in slashes (eg, "/bc/") before the search string. +(If you want to search for a string beginning with a slash, use an +empty (//) option first.) In addition, a ^P-Z [this can be +reconfigured] functions as a wildcard matching any single +character. Other control codes, like ^M for newline, can be +included (with the ^P prefix where needed). Examples: + + Find: /c/^MLABEL matches "LABEL" at start of line only; + Find: 4^Z^Z01 matches "42201", "47401", etc; + Find: wordstar matches "WORDSTAR", "WordStar" etc; + Find: ///88 matches "/88"; + Find: /b/esc looks backwards for "Esc", "esc", etc. + + ^Q-A is the FIND/REPLACE command. It asks for a string to +find, and what to change it to (all options above apply to the +search string only). The cursor will be placed on each occurrence +of the string successively, starting at the cursor location. You +will see the prompt "Chg?" in the header. To replace the old +string with the new one, press "Y"; anything else skips to the +next. To change all further occurrences without being asked, +press "*". ESC cancels at any time. (Note: if ^Q-A is used in a +Macro, it will automatically assume "*" with no further input +required.) + ^L (or ^\, if ^L is an arrow key) repeats the last ^Q-F or +^Q-A command. For ^Q-A, you will be asked whether you want to +replace the found string. For both, direction and case options +remain unchanged. + +5.8. Header (^O-Q). + + ZDE normally gives you a header, or status line, at the top +of the screen. However, if you like you can toggle the header +display on and off with the ^O-Q (QUIET) command. [The header may +also be suppressed by default.] This lets you see more file text. +It can also speed up operation on some slower terminals, as the +position doesn't have to be continually updated. + + A typical header line looks something like: +------------------------------------------------------------------ +B0/WORK:ZDE.DOC [A Pg 8 Ln 31 Cl 53 INS vt hy AI DS MR ^Q_ +------------------------------------------------------------------ + "B0/WORK:ZDE.DOC [A" = Current directory, filename, and mode. + "Pg 8, etc" = Current position in file by page, line, column. +For "N"ondocuments, there is no page number display. If +pagination (^O-P) is off, you will see "Pg 0" (document) or "OP" +(nondocument) here. + "INS" = Insert mode on (^V). + "vt" = Variable tabs on (^O-V). + "hy" = Hyphenation enabled. (^O-H) Doesn't display in "N" + mode. + "AI" = Auto indent mode on (^O-A). + "DS" = Double spacing (^O-S). + "MR" = Margins released (^O-X). Doesn't display in "N" mode. + "^Q_": Prefix keys (and some prompts) display here. + +5.9. Information (^K-I). + + ^KI displays an Information message with: + + - the ZDE version and date; + - whether (Y/N) the file has been changed since last saved; + - the current size of the file in bytes; + - the number of bytes of text memory used and free (1024 + bytes = 1K). + + You will note that the file uses about 20-25% less memory +than its actual length; this is due to ZDE's compression of text. +(Note: ZDE is limited to 16-bit arithmetic. In the unlikely event +that file size should exceed 64K it will be shown mod 64K; just +add 65536.) + Block moves and copying are limited by the amount of free +memory. Further, when there is less than 1K free, response time +can get very slow. + +5.10. Insert Mode (^V). + + ^V toggles insertion on and off. If insert is OFF, any text +to the right of the cursor is overwritten as you type. If insert +is ON, what you type is inserted, and existing text moves to the +right. + +5.11. Line Spacing (^O-S). + + The ^O-S command toggles between single and double line +spacing. In double space mode, the following functions generate +double carriage returns: CR (^M), Insert CR (^N), Reform (^B), +Wordwrap. You can easily mix single and double spacing; the ^B +command can convert between the two. Note: if you prefer to keep +your actual file single spaced, you can still get a double-spaced +printout by using the "D" option of the ^K-P command (see +Printing). + +5.12. Margins (^O-R, -L, -X, -C, -F). + + ^O-R sets the RIGHT margin, enabling wordwrap, reformatting, +and centering. A right margin of 1 disables all formatting +functions (same as in "N"ondocument mode). At the prompt +"Column:" enter the column number (1-255), or just hit CR for the +current cursor column. If the value entered conflicts with the +current left margin, ZDE sets the left margin to 1. + ^O-L sets the LEFT margin in an identical fashion; of course, +the value must always be less than the current right margin, +meaning it's best to set the right margin first. + ^O-X temporarily RELEASES the margins (resets them to 1), +allowing you to type outside them. Use ^O-X again to restore the +margins. + ^O-C CENTERS the current line with respect to the margins, if +set. ^O-F sets the current line FLUSH right, if the right margin +is set. + +5.13. Pagination (^O-P). + + The ^O-P command sets the page length. Enter a value from 0 +to 255, or just hit CR to restore the default value [normally 54]. + When the value is nonzero, it determines the page and line +shown in the document header ("Pg xx Ln xx"), and page-oriented +Print functions (formfeeds, headers, start/stop print) are enabled. + When the value is zero, pagination is off. The header will +say "Pg 0 Ln xxxx", showing you the absolute line number in the +file. Also, printing occurs with no page breaks or formfeeds. +This can be useful for printing small things right after each +other on the same sheet; or, in conjunction with the "*" print +option, can be used to print out multiple copies of index cards, +labels, etc. + ZDE never sends a formfeed before printing; begin your file +with a ^P-L character if you need one. Otherwise, use of ^P-L is +not recommended in document files as it renders ZDE's page count +inaccurate. + +5.14. Place Markers and Line Queue (^P-Z, ^Q-Z, ^Q-Q). + + You can set any number of temporary PLACE MARKERS in the text +with ^P-Z (they will appear as ^Z). The ^Q-Z command moves the +cursor to the next place marker in the file, cycling back to the +top of the file if needed. Place markers are NOT saved to disk. + ZCPR users may also store a QUEUE of up to eight line numbers +in their user-defined messages (a set of sixteen bytes of +protected memory "available for user definition"). The ^Q-Q +command will then cycle through the queue of line numbers, +returning to the first location when it reaches a value of zero. +A value greater than the number of lines in the file takes the +cursor to the end of the file. The numbers are stored as two-byte +inverted hexadecimal words. This facility is intended as an +interface between other programs and ZDE for such purposes as +storing the locations of compiler errors, however ZCPR users may +also find it useful to POKE these locations directly before +invoking ZDE. + +5.15. Printer Codes (^P). + + Control codes for special effects in printing can be entered +in the text with the ^P prefix. Thus pressing ^P-^H (or ^P-H) +embeds a ^H, etc. Several standard codes are: + + ^H - backspace - overstrike previous character + ^I - hard (ASCII) tab - printers respond variously to this + ^L - formfeed (Not recommended unless pagination (^O-P) + disabled) + + The Block marker, normally ^@, cannot be embedded, and ^Z is +reserved for use as a place marker. Control codes display on +screen as capital letters, highlighted if possible. ZDE assumes +they are not characters, so they are not counted when reformatting +text. + For greater convenience, you can also arrange to have a +single ^P-code produce an entire string of bytes for commonly used +printing effects. ZDE supports a subset of WordStar's printer +installation, seven definable codes: four toggles, four switches. +The Printer Installation in ZDENSTAL allows you to choose what +codes you want to use, and install the actual control sequences +your printer needs. The defaults are: + + toggles: ^B, ^D, ^S, ^Y + switches: ^Q, ^W, ^E, ^R + + In WordStar these toggles are Boldface, Doublestrike, +Underline, and Ribbon/Italic respectively, but you can make them +anything you like. "Toggles" are good for features like underline +and bold that are turned on and off. "Switches" are better for +multivalued parameters like character pitch. + Example: your printer uses ESC-U-01 (and 00) to turn +underlining on (and off). If you install these codes with +ZDENSTAL: + ^S toggle on: (03)1B5501 + ...and off: (03)1B5500 + +then all you have to type in ZDE to get underlined text is: +^PSunderlined text^PS. + +5.16. Printing (^K-P). + + The ^K-P command PRINTS all or part of the file in memory. +You will be asked for a set of "Options:", at which point you may +enter zero or more of the following, in any order: + + B prints only the currently marked BLOCK. + P PAUSES for your keystroke before each page (sheet + feed). + D DOUBLE SPACES all text to be printed. + Lnn sets the printer LEFT MARGIN to nn columns [the + default value can be set with ZDENSTAL]. + ^ FILTERS control characters ^X to text "^X". + *nn prints the job out nn TIMES (nn=1..255). + @nn begins printing AT the nn'th page. + =nn renumbers the pages beginning with nn. + #nn prints only (up to) a TOTAL of nn pages. + "..." uses the quoted string as a HEADER. The string, + followed immediately by the page number, will print + at the top right of each page near the margin (maxi- + mum length is 50 characters; an empty string, "", + gives numbered pages with no header text.) + +The last four options (@,=,#,"") are NOT allowed if pagination is +disabled (^O-P), or if "B"lock print was chosen. Examples: + +Options: L12P + - Print the file with a left margin of 12 columns, pausing + before each page for you to press a key (other than Esc). + +Options: @6#2=14"Instructions, page " + - Print the sixth and seventh pages of the file, but numbered + 14 and 15, with the header shown. + +Options: BD^*2 + - Print out the current marked Block twice, double spaced, + with control filtering. + + In "W" mode, lines beginning with a "." will be regarded as +WordStar dot commands, and will neither print out nor affect the +page count (they have no other effect either). + You can abort printing at any time with ESC (this may take a +moment, or a few keystrokes, depending on your BIOS). + +5.17. Ruler Line (^O-T). + + To help you align text properly, ^O-T toggles display of a +"ruler line" above the text on and off. + In each column you will see one of several symbols: "[]" +designate the current margins (if set); "-" indicates areas within +the current margins, "." outside them. Also, tab stops are marked +by either "T" (Variable) or "I" (Hard). Examples: + +("A" mode) [-----T-----T-----------T--------------]........ +("N" mode) I.......I.......I.......I.......I.......I....... + +5.18. Tabs (^I, ESC-TAB, ^O-V, ^O-I, ^O-N). + + There are two Tab modes: Variable and Hard, and the ^O-V +command toggles between them. In Hard Tab mode the Tab key +produces an actual ^I (ASCII 09); whether this overwrites any +existing text depends on the Insert toggle. Hard Tabs display by +default at fixed intervals of 8 screen columns [this is +configurable to any multiple of 2]. + In Variable Tab mode, the Tab key moves the cursor to the +next variable tab stop (you can always get a Hard Tab by typing +^P-I). If Insert is on, spaces are inserted up to the next tab +stop. Otherwise, existing text is skipped over, but spaces are +still added at the end of a line as needed. Up to eight tab stops +may be set with ^O-I and cleared with ^O-N; the defaults are in +columns 6, 11, 16, and 21 [these are configurable]. At the +prompt, enter the column number desired, or hit RETURN for the +cursor column. + The ^O-I command also accepts two multiple-set inputs: "@nn" +sets tab stops every "nn" columns, while "#n1,n2,..." sets tabs at +columns "n1,n2, ...". Both remove any pre-existing tabs, and +typing "@" or "#" alone simply clears all tabs. + The ESC-TAB command moves backward (left) to the previous +variable or hard tab stop. + +5.19. Undelete (^U, ^Q-U). + + The undelete functions may be used (repeatedly if necessary) +to recover a reasonable amount of text deleted either by +overtyping, or with any of ZDE's delete commands, IF the cursor +has not yet been moved away. ^U undeletes one character; ^Q-U +does a whole line. + Restrictions: undelete may not recover all of a Block delete +unless the cursor was in or near the block deleted, and it may +work imperfectly if DEL has been used several times in sequence. + Further use after all deleted text is recovered will produce +junk (usually duplicates of text above the cursor, which is +sometimes useful). + +5.20. Upper/Lower Case (^^). + + The ^^ (ctl-caret) command changes the case of the character +at the cursor, if it was a letter, and moves the cursor one +character to the right. Useful for capitalizing a string of +lowercase text, or vice versa. + +5.21. Window and Screen (^W, ^Z, ESC-Arrows, ^O-Up, -W, -Z). + + ZDE supports several functions to move and alter the display +screen. + The ^W and ^Z commands scroll the screen up and down a line +at a time, without moving the cursor (unless necessary). + Preceded by ESC, any arrow (except ANSI) moves the screen +rather than the cursor: the text as a whole shifts up/down 1/4 +screen, or right/left 32 columns, while the cursor stays put (the +cursor must be past column 32 for horizontal shifts to work). + Preceded by ^O-, any UP arrow key (except ANSI) makes the +current text line the top of the screen. + The ^O-W command creates a WINDOW in the bottom half of the +screen, which retains a copy of the file text starting at the +current line, plus the current status and/or ruler line(s), if +any. Editing continues normally in the top half of the screen. +This is useful for comparing different sections of text within a +file, or even between different files. Typing ^O-W again removes +the Window. Note: Windowing is only supported for screens of 15 +or more lines. + The ^O-Z command temporarily zaps (blanks) the entire screen; +good for avoiding CRT burn-in, or just protecting work from prying +eyes or fingers. Restore the screen by pressing ESC or SPACE. +Note: if Windowing was in effect, any text in the window will be +lost. + +5.22. Wordwrap, Reformat (^B, ^O-D). + + WORDWRAP is automatic in Document ("W" or "A") modes whenever +the right margin is set. Any text entered will be formatted to +the current margin settings as you type. The end of a paragraph +is marked by a "HARD CR", which occurs when you press the RETURN +key (this is a CR immediately following a character of text). In +contrast, when wordwrap occurs you get a "SOFT CR" (which is +actually a CR with a space before it). You can change a hard CR +into a soft one, or vice versa, by deleting or adding a space at +the end of the line. Hitting RETURN also hardens a soft CR. The +distinction between hard and soft CRs is only important when +reformatting. + ^B REFORMATS from the line the cursor is on, to the end of +the paragraph, according to the current margin settings and line +spacing. ^B may be used not only to reshape a paragraph after +editing, but also to change its margins and line spacing. +Indentation can be tricky if you have a left margin set. If the +current line is indented relative to the next one, ZDE assumes +that amount as your paragraph indentation. + ^O-D toggles DISPLAY OF HARD CRs. Hard CRs, otherwise +invisible, can be caused to display as a "<" character. This can +be useful; some may find it distracting, so it turns off. + + +6. Macros. + +6.1. Macro (ESC-M). + + A MACRO is a string of ZDE commands and text that, once +defined, can be repeated automatically. When you type ESC-M you +will be asked for the string, then a "Repeat count". Usually you +will simply type a number for the repeat count (0-9, or "*" for +"indefinitely"). You will see the results as the macro executes, +and you can abort it at any time by pressing ESC. If you are sure +you know what you're doing, you can speed up Macro execution by +specifying "Quiet" mode: press "Q" before the repeat count. Only +the header, if present, will be updated as the Macro runs. + Macros also stop any time an error occurs; the error message +will be visible, and can be cleared by pressing ESC. Many +commands (like Find or Reformat) are designed to generate errors +at the end of the file so that indefinite ("*") Macros containing +them halt there. You may need to abort other indefinite Macros +manually. + ZDE turns INSERT OFF before running a macro, so that the same +key sequence will always have the same effect. INSERT status is +restored when the macro terminates. + In order to include any input line editing characters (BS, +CR, etc.) in a macro, you must prefix them with ^P. (NOTE: To +make them easier to read and understand, Macro listings are given +here as they function, not as they are typed in. ^P prefixes as +needed are NOT shown. Keystrokes are separated by dashes or +commas for clarity, and "_" means a space or blank.) Here are some +sample Macros: + + 1. View a file by scrolling slowly through it: + + ESC-;, ^C + + This will pause, then scroll down. (Use a repeat count + of "*".) + + 2. Can you figure out what this one does? + + ^QR, ^QF, (, CR, ^G, ^KB, ^QF, ), CR, ^G, ^KK, ^QC, CR, ^KV + + (Answer: .elif eht fo dne eht ta meht fo tsil a gnikam + elif a fo tuo sesehtnerap ni stnemmoc ekat lliw tI) + + ZDE Macros are very powerful tools, particularly given their +programmability and storage on function keys. + +6.2. Macro Key (ESC-#, ESC-0..9). + + Up to 10 MACRO KEYS can be stored (0..9), by entering them +with the ESC-M command, then using ESC-# to save them. They can +then be recalled and used simply by typing ESC-number. Example: +typing ESC-#-0 will store the last Macro used in the ESC-0 command +so that it can be recalled and reused at any future time simply by +typing ESC-0. + Ordinarily, Macro Keys operate just like the original Macro, +asking for a "Repeat count" when executed. If you want to +suppress this prompt (resulting in a "function key" that executes +just once) you can type "N" (for No repeat) before storing the key +number. Example: ESC-#-N-0 makes ESC-0 a no-repeat macro key. +If you also don't need to see the Macro work, and want to make it +faster, you can make it QUIET as well by typing "Q" instead (for +Quiet) before the key number. Example: ESC-#-Q-0 stores ESC-0 as +a quiet no-repeat key sequence. + If the Macro (ESC-M) string is empty, using ESC-# will delete +a Macro Key. Using a defined Key makes it the last macro used, so +it can be stored again in another key if desired. Trying to use +an undefined Macro Key results in an error. + There are 500 bytes total available for all 10 keys, and a +128 byte limit for any one Key (ZDE's own input line will only +accept 65 bytes, but ZDENSTAL can handle up to the full 128.) + USAGE HINT: If you want to re-use a macro without having to +type it in again, use ESC-# to assign it to a key, then call it up +by number. + Besides simply storing Macros, here are some useful Macro +Keys: + + 1. Storing different sets of margins (e.g., for quotes). + For example, + ^OL, 12, CR, ^OR, 66, CR + + 2. Typing any frequently repeated phrase; "ESC-6" is much + more convenient than "^PSWorld Wide Widgets Ltd. (N.A.)^PS". + + 3. Swap the two characters preceding the cursor: + ^S, ^S, ^G, ^D, ^U, ^U, ^D, ^G + +[ZDENSTAL allows you to install defaults for all ten Macro Keys. +Thus a copy of ZDE can be customized for any task, such as the +formatting requirements of specialized writing or programming +languages.] + +6.3. Macro Programming (ESC-0..9, ESC-!,=,~,+,;). + + ZDE has several commands which function only in a Macro +string, and give you control over the execution of a macro, +allowing true programming. + ESC-0..9 function as LABELS 0..9 when entered as part of a +Macro. They have no effect, but can be "jumped" to by other +commands. + ESC-! followed by 0..9 is a JUMP instruction, and causes +macro execution to resume with the command following the label +ESC-0..9. Example: ESC!2 jumps to label 2. As two special cases, +ESC-![ jumps to the beginning of the Macro, and ESC-!] jumps to +the end (aborts). + ESC-= and ESC-~, followed by a character and then a label +0..9 (or "[","]"), are CONDITIONAL JUMPS: they jump to the label +or the start or end of the macro IF the character at the cursor +position matches (ESC-=) or doesn't match (ESC-~) the character +specified. Example: ESC~^M2 jumps to ESC-2 if the current +character is NOT a CR. + ESC-= and ESC-~, followed by a character and then ">" or "<", +are SEARCH LOOPS. They will continue to move the cursor right +(">") or left ("<") AS LONG AS the character at the cursor matches +(ESC-=) or doesn't match (ESC-~) the specified character. Both +search loops terminate automatically at the beginning or end of +the file. Example: ESC=_> moves right as long as the current +character is a space. + ESC-+, followed by 0..9, CHAINS to another macro key, +allowing you to build macros strings longer than the storage limit +of any one key. It does not "call" the key; there is no +returning. Example: ESC+9 chains to Key 9. + ESC-; (semicolon) gives a brief pause, presumably so the user +can see what's happening on screen. + Macro programs are stored just like any other macro string +(usually with "N"o repeat count). If you program in an endless +loop, you will at some point have to abort with ESC. Don't make +macro programs "Q"uiet until you're sure they work. + Example: here is a good macro program (best stored as a Quiet +Key) to move the cursor to the start of the current sentence: + + ESC~.1, ^S, ESC1, ESC~.<, ESC2, ^D, ESC=_2, ESC=^M2 + +You could write this out in programmer's pseudo-code as: + + If not "." goto label1 + Move left ;move left if already on period +label1: While not "." move Left ;move left to previous period +label2: Move right ;now move back right + If " " goto label2 ; as long as you see a space + If "^M" goto label2 ; or a Return + (all done) + + +7. General Information. + +7.1. Disk Space. + + ZDE isn't disk-bound; if you run out of disk space, just +insert another disk (always keep a blank FORMATTED disk around). +Alternatively, you can use the ESC-F and ESC-E commands to purge +unneeded files. [If you have small disks, you can also install +ZDE not to preserve BAK files.] + Let ZDE's filesize limits encourage you to break work up into +files of 50K or less; larger files make inefficient use of floppy +disks. + +7.2. Error Messages. + + Press ESC or Space to continue. "Error" alone means the +command used just won't work in this situation. (Example: a block +command was used with no block marked.) More specific errors are: + + "Out of Memory" - the file, block, or key string won't fit in + RAM. + "Invalid Key" - an illegal command key sequence was pressed. + "I/O Error" - file not found, disk full, empty/invalid + drive, etc. +"Cannot Reformat" - word too long, or margins invalid. + "Not Found" - the object of a search was not found. + "Syntax Error" - a macro programming command was misused. + +7.3. File Modes. + + ZDE has three FILE MODES: "W"ordStar document, "A"SCII +document, and "N"on-document. The basic difference concerns the +format of disk files. + In "W" mode, ZDE reads and writes WordStar-compatible files. +ZDE can edit a WordStar document, except that any right +justification will be lost. WordStar can edit any ZDE "W" file in +Document mode. + In "A" or "N" mode, ZDE reads and writes text as standard +ASCII 7-bit characters, a universal format accepted by virtually +all software. The only difference between "A" and "N" modes lies +in the default settings on loading a new file. Typically "A" mode +is used for word processing, and "N" mode for programming and +other technical applications. + +DEFAULTS FOR: Margins Tabs Hard CR disp. +------------ ------- ---- ------------ +"A"SCII or "W"ordStar mode ACTIVE VARIABLE [ON] +"N"on-document DISABLED HARD OFF + + The file mode option can be specified along with the filename +at any ZDE file function prompt, allowing you to mix WordStar and +ASCII disk files as needed (see Invoking ZDE, above). You can +also change the current mode with the ^K-N function by entering a +new mode (e.g., "[W") with or without a filename. + [ZDENSTAL allows you to select a default file mode, to be +used when no mode is specified (originally this is "A"). Further, +you can specify two filetype masks for automatic mode selection +(e.g., all "ASM" files as "N" mode) to override that default.] + NOTE ON ASCII FILES - The "A"SCII-document file format used +since VDE 2.50, to allow accurate formatting and WordStar +compatibility, distinguishes between HARD and SOFT CRs (see +Wordwrap, above). Consequently, if you edit an ASCII file created +by other software (including earlier versions of VDE), it may +appear to be full of hard CRs, making text impossible to reformat. +There are two easy ways to solve this problem: first, you can use +^Q-A to find "^M"s and selectively replace them with " ^M". +Alternatively, the following Macro (best stored as a Quiet macro +Key) does a good job of "softening up" paragraphs for reformatting: + + ^QS,^X,ESC=_],ESC=CR],^S,^V_^V,^D,ESC![ + +7.4. Hyphenation (^O-H). + + ZDE can't introduce hyphens, but it does recognize them in +the text, treating them as a legitimate place to break a line, so +if you have a long word close to the margin, you can insert a +hyphen where you'd like the word to be broken. + Similarly, ZDE can't unhyphenate. If it is trying to +reformat and finds a hyphen at the end of a line, it will pause to +ask you what to do with it. You will see the prompt "Chg?" in the +header, and can: + Press "Y" to remove the hyphen and space, joining the word; + "N" to leave the hyphen, but still join the word; + "ESC" to leave both the hyphen and the space alone. +Typically you would choose "Y" for "soft" hyphens that you +introduced to break up a word ("intro-duction"); "N" for hard +hyphens that are part of a word ("vis-a-vis"); and "ESC" for +punctuation (dashes "--", etc.). After your choice, reformatting +will proceed automatically. + You can toggle hyphenation on and off with ^O-H. If off, +hyphens are not treated differently from any other text character. +[The ^O-H default can be changed with ZDENSTAL.] + +7.5. Interruption. + + If ZDE is interrupted by messages from other software (BIOS +error, resident utilities, etc.), it may lose control of the +screen. The best way to return it to normal is by blanking and +restoring the screen (^O-Z, ESC). + +7.6. Prompts. + + First, ZDE has several simple prompts requiring you to +confirm an action by typing "Y" or "N": + + "Abandon changes?" - warns the file you want to Quit has + been changed. +"Unchanged; save anyway?" - reminds the file you want saved isn't + changed. + "Delete original?" - do you want to delete block copied + from 2nd file? + "Chg?" - do you want to change this instance of + a string? + +Then, there are a few special purpose prompts: + + "Repeat count:" - enter (optional "Q" and) repeat count + for Macro. + "Key number:" - enter (optional "N/Q" and) key number + for Key. + "Rdy" - press any key to print next page (ESC + quits). + +Finally, there are a variety of prompts for either numeric or +string input, like "Column:" or "Find string:". You are expected +to type in a string (up to 65 characters). The following control +keys operate: + + Correct mistake: BS (^H), ^S, or Del + Erase entire entry: ^X + Abort operation: ^U + +Note that to get any of these codes, or a CR, into the string +itself, you must precede it with ^P (this includes ^P). Examples: +to find a line beginning with a "*" (find "^M,*") type + + ^Q-F, ^P-CR, *, CR. + +Now to get this into a Macro with ESC-M, you would have to type + + ESC-M, ^Q-F, ^P-P, ^P-CR, *, ^P-CR, CR. + +7.7. WordStar Compatibility. + + In most respects ZDE operates much like WordStar; one big +difference is the absence of a "No-File" menu. You are always in +edit mode; use ^K-L to select a new file to edit. + If the keys ^J, ^K, ^L are in use as CP/M arrow keys, you +must use ZDE's original command set to substitute for them as +follows: + + Block prefix (^K-)... ESC- + Help menu (^J)....... ESC-H + Repeat find (^L)..... ^\ + + If these keys are NOT installed as arrow keys, they retain +their WordStar functions. Then, of course, ZDE lacks some +WordStar commands, and has some of its own. Aside from these, +there are the following differences in common commands: + +COMMON USE WORDSTAR 4 ZDE ZDE NOTES +Hide block ^K-H ^K-U Actually unmarks block. +Erase disk file ^K-J ^K-E +Set Place Mark ^K-0..9 ^P-Z Not individually numbered. +Go to Place Mark ^Q-0..9 ^Q-P Cyclic. + +COMMON COMMAND WORDSTAR 4 USE ZDE USE + ^^ Soften hard CR Transpose upper/lowercase + ^K-N Column block mode Rename current work + ^O-D Display ^P codes Display hard CRs + ^O-F Ruler from text Flush right + ^O-P Preview mode Page length + ^Q-U Repeat align Repeat undelete + +Note that ZDE does not implement WordStar "dot commands" in text, +though in "W" mode it avoids printing them. + + +8. Disclaimer. + + You use ZDE at your own risk. The author accepts no +liability for any damages resulting from its use or misuse. +Direct problem reports and suggestions to the author; include a +stamped return envelope for a reply if desired. Thanks to the +many users of ZDE whose feedback has led to improvements and bug +fixes in the past. + + Carson Wilson Sysop: Antelope Freeway RAS + 1359 W. Greenleaf 312-764-5162, Chicago + Chicago, IL 60626 24 hours, 3-12-2400 baud + \ No newline at end of file diff --git a/Source/Images/d_zsdos/u1/ZDE10.FOR b/Source/Images/d_zsdos/u1/ZDE10.FOR new file mode 100644 index 00000000..79b4dd02 --- /dev/null +++ b/Source/Images/d_zsdos/u1/ZDE10.FOR @@ -0,0 +1,7 @@ +Z-System Display Editor is Eric Meyer's famous VDE improved and +enhanced for Z-System. ZCPR named directory and line queue +capabilities, preserves file datestamps and runs faster under ZSDOS, +improved install program and documentation, and key compiler +included. Over twenty flaws in VDE 2.66 corrected. Also runs under +vanilla CP/M 2.2 and CP/M Plus. + \ No newline at end of file diff --git a/Source/Images/d_zsdos/u1/ZDE10.NEW b/Source/Images/d_zsdos/u1/ZDE10.NEW new file mode 100644 index 00000000..4c568f0d --- /dev/null +++ b/Source/Images/d_zsdos/u1/ZDE10.NEW @@ -0,0 +1,304 @@ + New in ZDE + + Z-System Display Editor + + Version 1.0 10 Mar 89 + + ZDE and its documentation are copyright 1989 by Carson Wilson, + all rights reserved. They may not be circulated in any + incomplete or modified form without written permission of the + author. Any commercial use of ZDE, defined as any situation + where the duplicator receives revenue by duplicating or + distributing ZDE by itself or in conjunction with any hardware + or software product, is expressly prohibited unless authorized + in writing by Carson Wilson. + + + CONTENTS + + 1. Functional Enhancments. + 1.1. File Datestamp Capability. + 1.2. Named Directory Capability. + 1.3. Line Queue Capability. + 1.4. Other Enhancements. + 2. Bugs Fixed. + 3. ZDENSTAL. + 4. Notes to DosDisk Users. + 5. Planned Enhancements. + 6. Development History. + + +1. Functional Enhancments. + +1.1. File Datestamp Capability. + + Under ZSDOS, the Create date and time of files edited with +ZDE are maintained across editions. If .BAK files are being +created, the create dates of new files will be the same as their +.BAK files. Note that if a file with the name of a block being +saved with ^KW or ESC-W already exists, the saved block will have +the same Create stamp as the (previously) existing file. I hope +to address this side-effect in future versions by prompting before +erasing existing files during ^KW commands. + +1.2. Named Directory Capability. + + ZDE will now accept ZCPR named directories when loading, +renaming, and erasing files, and when requesting a directory +listing with the ^KF command. The named directory (if available) +is also displayed as part of the current file name in the status +line. + Since ZCPR directory names can occupy up to eight columns on +the status line, it was necessary to move the rest of the status +line over by eight columns. I was able to gain an extra column by +putting only one space between the filetype and the "[x" +specifier, but the result is that if your terminal displays less +than 55 columns the current column number will not be visible in +your status line. + This is unfortunate, but the status line is already about as +compressed as possible. To include all of the information I would +have to add a second status line for narrow terminals, which would +add a good deal of code and complexity to what is already a +complicated program. Since most CP/M terminals (including my own) +have 80 columns, I have decided in favor of the majority in this +instance. + You may also notice that the ^QA prompt "Chg? (Y/N/*)" has +been shortened to "Chg?" and the "Wait..." message is now "Wait". +These changes were also necessary because of the decrease in +available space on the status line. + +1.3. Line Queue Capability. + + A third functional enhancment is the new ^QQ command, which +causes ZDE to skip to line numbers stored in ZCPR's user-defined +message bytes. Under ZCPR3, up to eight line or page numbers may +be passed to ZDE. The original idea was to use the interface to +produce a Turbo Pascal-like integrated environment for program +development, but the interface is certainly not limited to +programmers. + The numbers are stored in ZCPR's 16 user-defined message +bytes, and ZDE treats these bytes as a packed array of up to eight +elements. ^QQ causes ZDE to cycle through the array, going to the +stored locations sequentially until the end of the array or a zero +element is reached, at which point ZDE returns to the first stored +location. + The numbers are stored as inverted 2-byte hex words (LSB +first), so for example to store the lines 12, 44, and 108 a +program would set the 16 user-defined bytes as follows: + + 0C 00 2C 00 6C 00 00 00 00 00 00 00 00 00 00 00 + +In this example, when ZDE reaches the fourth word (00 00) it +returns to the start of the array and goes to line 12 (0C 00). + Any ZCPR program, or even an alias (using Jay Sage's powerful +ARUNZ parameters) can now give ZDE the locations of up to eight +line numbers of interest. With a little imagination we could +probably create an integrated text search/edit environment which +searches multiple files for a string and points ZDE to the exact +location of each match for editing. Thanks to Cameron Cotrill and +Al Grabauskas for suggesting this feature. + +1.4. Other Enhancements. + + In response to a suggestion by Phil Newman, ZDE now properly +accepts and displays user areas between 16 and 31. While I do not +normally suggest storing files in these areas because many CP/M +programs do not handle them properly, they are useful occasionally +on very large disks or for security purposes, and are supported by +ZCPR and ZSDOS, so I have included them in ZDE. + For NewWord compatibility, I have changed the "To Mark" +command from ^QP to ^QZ. This is also better mnemonically, since +the "place mark" command is ^PZ. + The search and replace functions now match upper or lower +case by default. To search for an exact match by case, you must +enter "/c/" or "/C/" (for Case) before the search string. The +"/i/" option is no longer active. I have changed default +find/replace wildcard character to ^Z so that ZDE can find strings +with literal '_' characters. Use the ^QZ command to find a +literal ^Z. + ^KB, ^KK, ^KU and ^QY no longer cause unnecessary redisplays. + For faster operation, ZDE no longer resets drives under ZSDOS +(same as CP/M Plus). + Under ZSDOS and CP/M Plus, ZDE sets the error mode to 0FEh. +All BDOS errors are displayed on the screen, but ZDE retains +control. + For your protection, ZSDOS Path and Public files are Read +Only under ZDE. That is, you can access files via Path or Public +but you cannot erase Path or Public files. If you get a ZSDOS +"File W/P" error when saving data with the ^KS, ^KD, ^KX, or ^KW +commands or when erasing a file with ^KE, you may have given the +name of a ZSDOS Path or Public file. Try again with a new name. + + +2. Bugs Fixed. + + If just a drive letter in given during the ^KF (display +files) command, files in the current user area are displayed +rather than files in user area 0 (thanks to Ben Cohen). + ZDE no longer crashes if no name is given in response to the +^KW prompt (thanks to Eric Meyer for finding this one). + ^KB and ^KK now behave properly when entered at the rightmost +column of the screen. + The "Chg?" prompt is now properly erased in all circumstances. + Full disk errors caused CP/M Plus and ZSDOS systems to report +the wrong amount of free space. This has been corrected by +closing the output file after full disk errors (thanks to Howard +Goldstein for this idea). + File read-only errors no longer set the drive byte to "^@" +under CP/M 2.2 (thanks again to Phil Newman for finding this one). + Backwards replace now finds the first occurrence of the +string before the cursor even if the cursor is only one character +to the right of the string. If the string is replaced, the cursor +now returns to the START of the new string instead of one after it +as before (this may change the effects of some ZDE key macros). + Ben Cohen noticed that VDE's left margin (Lnn) print option +sometimes failed to indent the first line. This has been fixed. +ZDE also prints the header text at the same location no matter +what left margin is used, rather than indenting the header and +causing it to wrap to the next line. Print option verification is +also much more rigorous now than in previous versions. + ^KS, ^KD, and ^KX file name requests now abort with a +carriage return as well as ^U, and ^U now returns you to ZDE in +response to the ^KX filename request. + The bug fix published in VDE266.FIX has been incorporated. + Several ZDE bugs affecting use with DosDisk have been +eliminated (see below for bugs IN DosDisk 1.00, however). + Tabs displayed in the last column of the last row no longer +cause the screen to scroll. + Virtual screen displays no longer show garbage at the end of +the line if the cursor is at the left side of the screen, and +moving to the left edge of the screen with ^A no longer causes +redisplay. + ^A now moves to the first character on line 2 before jumping +to line 1 when line 1 is empty. + ^PZ now causes a horizontal scroll if it moves the cursor +beyond the right edge of the screen. + Block and place markers no longer set file status to +"Changed." + The cursor no longer jumps around when moving left from +columns after 254. The cursor now stops at column 254 instead of +255, and the status line says "Col 255" for any column past 254. +Text past column 254 CAN actually be edited, but it will not be +displayed by ZDE. + Strings can now be found with ^QA even if they are the very +last item in the file (credit goes to Cameron W. Cotrill for +finding this one). + "^OI@" now removes all soft tab stops "as advertised" in +VDE266.DOC instead of setting them all to 1, making it possible to +set new tabs after entering "^OI@". "^OI#" has the same effect. + ^^ now sets file status to "Changed" when converting from +lower-to-upper case as well as upper-to-lower. + "Wait..." message now disappears after disk operations with +ruler on and status line off. VDE266 forgot to erase the message +in this situation. + Backwards find and replace operations now work properly +instead of repeating the prompt indefinitely when the replace +string is shorter than the find string (thanks to Ben Cohen for +pointing this out). + VDE restored INSert to its entry status just BEFORE the final +byte of the macro, resulting in irregular behavior and sometimes +causing macro elements to appear after the "INS" indicator in the +status line. ZDE waits until the entire macro has executed before +restoring entry status. Due to ZDE's code structure, the INS +status line indicator does not change during macro execution. + ZDE restores INSert to entry status when macros abort due to +errors (e.g., "[[[ Not Found ]]]"). + + +3. ZDENSTAL. + + Included in this library is ZDENSTAL.COM, the new install +program for ZDE. ZDENSTAL it is very similar to VINST in +operation, but adds the feature of displaying the actual print +toggles and switches at all prompts, rather than the default ones. +So if you change the first default print toggle character to ^K +for example, all other prompts referring to that toggle now +reflect the change. + Files for use with ZDENSTAL must have the type ".ZDE" (option +installation), ".ZDP" (printer installation), or ".ZDK" (macro +keys), but are otherwise compatible with files used to install VDE +2.66. With Fred Haines' permission, I have adapted his wonderful +VDKCOM.COM utility for use with ZDE, and renamed it (what else?) +ZDKCOM.COM (see ZDKCOM.DOC). + ZDENSTAL also corrects the bug which prevented VINST from +loading installation files whose archive bits were set. + + +4. Notes to DosDisk Users. + +DosDisk users are advised of a bug in DosDisk 1.00 which appeared +during the development of ZDE. Calling Reset 13 while DosDisk is +active can cause DosDisk to think other active drives are all +drive A:. Bridger Mitchell, author of DosDisk, is aware of this +bug and is working on a fix. In the meantime, users of CP/M 2.2 +and ZRDOS who wish to edit files on MS-DOS disks should NOT log +into the MS-DOS drive while editing with ZDE, as this causes ZDE +to do a Reset 13 when writing to the MS-DOS drive. To edit files +on the MS-DOS drive, run ZDE from drive A: or another non-MS-DOS +drive. + +Users of ZSDOS and CP/M Plus need not worry about this problem +with ZDE, as ZDE never resets disks under ZSDOS or CP/M Plus. By +the same token, however, ZSDOS and CP/M Plus users must not change +disks in the MS-DOS drive from within ZDE, because DosDisk (like +CP/M 2.2) requires a disk reset or warm boot when disks are +changed (see DosDisk manual, p. 7). + + +5. Planned Enhancements. + + If time permits, future versions of ZDE may contain the +following enhancements: + An improved ZDENSTAL which directly accepts ASCII files for +key definitions, along the lines of ZDKCOM. + Bridger Mitchell has suggested command "bindings" which would +allow any control key or meta key sequence to be linked to any ZDE +command. This idea will be familiar to users of Perfect Writer. +Key bindings would allow ZDE to simulate EMACS, for example. + Bruce Morgen and others have suggested automatic terminal +installation under ZCPR, allowing the same copy of ZDE to be used +with any terminal. + Allow more than two default filetypes available and fixed +disks. + It may be possible to preserve file create datestamps under +CP/M Plus and Z3PLUS. + + +6. Development History. + +1984: VDE began as Eric Meyer's set of improvements on the tiny + (4k) "VDO" memory- mapped editor by Fritz Schneider (1982), + as adapted by George Peace (1984) for the Osborne Executive. + +1985: The first terminal adaptation, VDE-PX for the Epson PX-8. + Ongoing improvements also in parallel Osborne versions + VDE-OS. + +1986: The unified VDE(M) version 2.00, with generic terminal + installation. Versions 2.1-2.3 added new features + including macros; global replace; tighter compression; many + Print options; file directory; undelete. + +1987: 2.4 (1/87): Improved scrolling and screen functions; more + screen sizes supported; WordStar file mode; + place markers; ^OZ; block print; larger macro + keys; VINSTALL terminal menu. + + 2.5 (3/87): User area support; variable tabs; double + spacing; search wildcards; more standard block + functions; improved format- ting; many + additions to VINSTALL. + + 2.6 (7/87): Greater WordStar compatibility; many new + functions; more free RAM; faster scrolling; + search options; keyboard buffer. + +1988: Development through v2.64, with new functions; top margin; + doublespaced printing; auto mode filetypes. + + 2.65 (4/88): Auto indent; tab set enhancements; small fixes. + + 2.66 (6/88): Printer left margin; small fixes. + +1989: First version of ZDE. + \ No newline at end of file diff --git a/Source/Images/d_zsdos/u1/ZDE10.QRF b/Source/Images/d_zsdos/u1/ZDE10.QRF new file mode 100644 index 00000000..3256a079 --- /dev/null +++ b/Source/Images/d_zsdos/u1/ZDE10.QRF @@ -0,0 +1,55 @@ + QUICK REFERENCE FOR ZDE 1.0 +------- [Note: the ^K- and ESC- prefixes are nearly interchangeable.] -------- + + + CURSOR AND SCREEN: + + = ^E = ^D ^Q, start, end of line + = ^X = ^S or ^H ^Q, top, end of screen + [Note: An alternate arrow key set can also be installed.] + + ^F word right ^QR to top of file ^QB to block start + ^A word left ^QC to end of file ^QZ to place marker + ^R page back ^QI to page/line no. ^QQ to queue line + ^C page forward + ^W scroll back line ESC, shift up, down 1/4 screen + ^Z scroll down line ESC, shift right, left 32 cols + ^O make current line top + + DELETING: MISCELLANY: FIND and REPLACE: + + ^G delete char right ^V INSERT on/off ^QF find string + DEL delete char left ^N insert a CR ^QA find and replace + ^T delete word right TAB Tab or move to stop /B/ackwards + ^Y delete line ^^ upper/lower case /C/ase-specific + ^QY del to end of ln ^KI information message /// quote "/" +^Q-DEL " to beg of ln ^PZ set place marker ^Z = wildcard +^QT_ del to character ^OD HCR display on/off ^L [^\] repeat last + ^U UNdelete char ^OQ header on/off find/repl + ^QU UNdelete line ^OW make window + ^OZ blank screen + + DISK FILES: BLOCKS: PRINTING: + + ^KN change filename ^KB block start ^KP print text + ^KL load new file ^KK block end (Options: P,B,T,L, + ^KR read in disk file ^KU unmark block ^,"",*nn,@nn,#nn) + ^KF show disk files ^KC copy block ^OP set page length + ^KE erase disk file ^KV move block ^P- insert ^-code: + ^KS save work, continue ^KY delete block B,D,S,Y toggles + ^KD save, load new file ^KW write block to disk Q,W,E,R switches + ^KX save and exit + ^KQ quit without save + + FORMATTING: + + ^OR,L set rt, lf margins ^OI,N tab set, clear ^B reform text + ^OX margin release ^OV tabs variable/fixed ^OT ruler line + ^OC center line ^OA auto indent mode + ^OF flush line right ^OS double spacing + + MACROS: + + ESC-M define macro ESC-# store macro key ESC-!,=,~,+ + ESC-; wait (pause) ESC-0..9 use key macro programming + \ No newline at end of file diff --git a/Source/Images/d_zsdos/u1/ZDE10.TOC b/Source/Images/d_zsdos/u1/ZDE10.TOC new file mode 100644 index 00000000..3b54a99c --- /dev/null +++ b/Source/Images/d_zsdos/u1/ZDE10.TOC @@ -0,0 +1,67 @@ +..Print in Wordstar mode, using the "L8" option for normal pitch +..printers and 8.5" paper. + + ZZZZZZZZ DDDDDDD EEEEEEEE + ZZ DD DD EE + ZZ DD DD EE + ZZ DD DD EEEEEE + ZZ DD DD EE + ZZ DD DD EE + ZZZZZZZZ DDDDDDD EEEEEEEE + + Z-System Display Editor + + + Table of Contents + +1. Introduction. . . . . . . . . . . . . . . . . . . . . . 1 +2. Installing ZDE. . . . . . . . . . . . . . . . . . . . . 2 +3. Invoking ZDE. . . . . . . . . . . . . . . . . . . . . . 2 +4. ZDE's Command Set. . . . . . . . . . . . . . . . . . . 2 +4.1. Command Summary. . . . . . . . . . . . . . . . . . . 3 +4.1.1. Control Keys: . . . . . . . . . . . . . . . . . . . 3 +4.1.2. File and Block Commands: . . . . . . . . . . . . . 3 +4.1.3. Escape and Macro Commands . . . . . . . . . . . . . 4 +4.1.4. Quick Commands . . . . . . . . . . . . . . . . . . 4 +5. Command Descriptions. . . . . . . . . . . . . . . . . . 5 +5.1. Auto-Indent Mode (^O-A). . . . . . . . . . . . . . . 5 +5.2. Block Commands . . . . . . . . . . . . . . . . . . . 5 +5.3. Cursor Movement . . . . . . . . . . . . . . . . . . . 6 +5.4. Deleting . . . . . . . . . . . . . . . . . . . . . . 6 +5.5. Disk Operations . . . . . . . . . . . . . . . . . . . 7 +5.6. File Commands . . . . . . . . . . . . . . . . . . . . 7 +5.7. Find, Replace . . . . . . . . . . . . . . . . . . . . 7 +5.8. Header . . . . . . . . . . . . . . . . . . . . . . . 8 +5.9. Information . . . . . . . . . . . . . . . . . . . . . 8 +5.10. Insert Mode . . . . . . . . . . . . . . . . . . . . 9 +5.11. Line Spacing . . . . . . . . . . . . . . . . . . . . 9 +5.12. Margins . . . . . . . . . . . . . . . . . . . . . . 9 +5.13. Pagination . . . . . . . . . . . . . . . . . . . . . 9 +5.14. Place Markers and Line Queue . . . . . . . . . . . . 10 +5.15. Printer Codes . . . . . . . . . . . . . . . . . . . 10 +5.16. Printing . . . . . . . . . . . . . . . . . . . . . . 11 +5.17. Ruler Line . . . . . . . . . . . . . . . . . . . . . 12 +5.18. Tabs . . . . . . . . . . . . . . . . . . . . . . . . 12 +5.19. Undelete . . . . . . . . . . . . . . . . . . . . . . 12 +5.20. Upper/Lower Case . . . . . . . . . . . . . . . . . . 13 +5.21. Window and Screen . . . . . . . . . . . . . . . . . 13 +5.22. Wordwrap, Reformat . . . . . . . . . . . . . . . . . 13 +6. Macros. . . . . . . . . . . . . . . . . . . . . . . . . 14 +6.1. Macro . . . . . . . . . . . . . . . . . . . . . . . . 14 +6.2. Macro Key . . . . . . . . . . . . . . . . . . . . . . 15 +6.3. Macro Programming . . . . . . . . . . . . . . . . . . 15 + + + + + +7. General Information. . . . . . . . . . . . . . . . . . 16 +7.1. Disk Space. . . . . . . . . . . . . . . . . . . . . . 16 +7.2. Error Messages. . . . . . . . . . . . . . . . . . . . 17 +7.3. File Modes. . . . . . . . . . . . . . . . . . . . . . 17 +7.4. Hyphenation . . . . . . . . . . . . . . . . . . . . . 18 +7.5. Interruption. . . . . . . . . . . . . . . . . . . . . 18 +7.6. Prompts. . . . . . . . . . . . . . . . . . . . . . . 18 +7.7. WordStar Compatibility. . . . . . . . . . . . . . . . 19 +8. Disclaimer. . . . . . . . . . . . . . . . . . . . . . . 20 + \ No newline at end of file diff --git a/Source/Images/d_zsdos/u1/ZDE13.FOR b/Source/Images/d_zsdos/u1/ZDE13.FOR new file mode 100644 index 00000000..7b0f2694 --- /dev/null +++ b/Source/Images/d_zsdos/u1/ZDE13.FOR @@ -0,0 +1,8 @@ +Update to Z System Display Editor, a small, fast editor for assembly +language and light word processing. Z System required for optimal +performance; also works with CP/M 2.2 and CP/M Plus. This version +adds hardware video handling, auto-return to file position under +ZCPR, WordStar-like ruler line, cursor on/off, and generally +F-A-S-T-E-R performance. Ten little bugs also fixed. See ZDE10.LBR +for full documentation. 26 Aug 89 by Carson Wilson. + \ No newline at end of file diff --git a/Source/Images/d_zsdos/u1/ZDE13.NEW b/Source/Images/d_zsdos/u1/ZDE13.NEW new file mode 100644 index 00000000..1e8517a1 --- /dev/null +++ b/Source/Images/d_zsdos/u1/ZDE13.NEW @@ -0,0 +1,241 @@ + + New in ZDE + + Z-System Display Editor + + Version 1.3 26 Aug 89 + + + ZDE and its documentation adapted with permission from Eric + Meyer's VDE version 2.66, and are now copyright 1989 by Carson + Wilson, all rights reserved. They may not be circulated in any + incomplete or modified form without the written permission of + Carson Wilson. Any commercial use of ZDE, defined as any + situation where the duplicator receives revenue by duplicating + or distributing ZDE by itself or in conjunction with any + hardware or software product, is expressly prohibited unless + authorized in writing by Carson Wilson. + + + This file describes changes to ZDE since version 1.0. I have + decided not to release a revision of the documents while ZDE is + still in active development. For now, please obtain ZDE10.DOC + and ZDENSTAL.DOC from ZDE10.LBR, available on Antelope Freeway + and many other remote CP/M systems. Except for the changes + detailed below, these files provide a thorough explanation of + how to use and install ZDE. Alternatively, send a contribution + of $6 or more to support further work on ZDE, and I will mail + you a high quality printed manual. See section 4 of this file + for details. + + + - CONTENTS - + + 1. FUNCTIONAL ENHANCMENTS. + 1.1. Hardware Codes for Faster Scrolling. + 1.2. ZCPR Auto-Return to Previous Position. + 1.3. WordStar-like Ruler line. + 1.4. ZCPR-like Option Lead-in Character. + 1.5. Cursor Dance Eliminated. + 1.6. Other Minor Enhancments. + 2. BUGS FIXED. + 3. INSTALLATION. + 4. FOR MORE INFORMATION. + + +1. FUNCTIONAL ENHANCEMENTS. + + 1.1. Hardware Codes for Faster Scrolling. + +ZDE now uses "hardware" video scrolling in its delete line (^Y) +command, if your terminal has a delete line capability (see +ZDENSTAL's Terminal Installation). If hardware insert line is +available, hardware scrolling is also implemented in the insert +line (^N) command when used at the beginning of a line. For most +terminals these are considerably faster than repainting all or +part of the screen, especially if more than one line is being +inserted or deleted. + +On many ASCII terminals a pause is needed after giving a hardware +scroll string so that the screen can catch up with the keyboard. +As distributed the same value of 38 hex is used for both +horizontal and vertical scrolls. To determine the optimal setting +for your terminal, load a large text file and execute "scroll up" +or "scroll down" (^W or ^Z) continuously. If your terminal beeps +at you or if garbage characters appear you probably need to +install a larger "Horizontal & vertical scroll delay" value with +ZDENSTAL. + + 1.2. ZCPR Auto-Return to Previous Position. + +ZDE now loads ZCPR's user-defined message bytes 0 and 1 (if +available) with the absolute line number of your position in a +file on exit. This allows you to exit to ZCPR, perform some other +commands, then return to the same line in your file using the ^QQ +command. You can also use this feature to jump to the equivalent +line in another file. Since the line number is absolute, it does +not vary with page length or file mode settings. + + 1.3. WordStar-like Ruler line. + +New in this version is the appearance of ZDE's ASCII/WordStar mode +ruler line. As with WordStar and NewWord, the left margin is now +marked with "L", the right margin with "R", and soft tabs with +"!". The appearance of ZDE's Non-Document mode ruler line remains +unchanged. + + 1.4. ZCPR-like Option Lead-in Character. + +ZDE now accepts ZCPR's familiar "/" slash character instead of +CP/M Plus's "[" character as an option signifier. For example, to +change to non-document mode the command is now "^KN/N" instead of +"^KN[N". To load a file in ASCII mode (for example) the command +is now "ZDE filename /A". This is more consistent with Z-System +programs as well as the MS-DOS version of VDE. It's also a lot +easier to find the slash character (at least on my keyboard)! + +ZDE allows you to "quote" the slash character in filenames in the +same way you quote it in ZDE find/replace strings, by doubling the +slash to give a literal slash. So for example the command "^KN +12//12//89.FIL" will change the current filename to +"12/12/89.FIL," and "ZDE ////MYFILE" loads //MYFILE for editing. + + 1.5. Cursor Dance Eliminated. + +Several users mentioned that ZDE's cursor often seemed to "dance" +for no apparent reason. Actually, this was due to the fact that +ZDE repaints the screen somewhat more often than larger word +processing programs, but in looking through NewWord's User Area +Listing for version 2.17, I found something I had never noticed +before. It is possible to install NewWord to turn your cursor off +and on by putting short routines in the MORPAT area and installing +jumps to your routines at 77D and 77A hex, respectively. This +reduces eyestrain by telling NW to turn off your cursor while it +repaints your screen. + +It turns out that it was fairly simple to add a similar routine to +ZDE, so this version has cursor on/off routines similar to +NewWord's. To make use of these routines, you must install ZDE +with the cursor on/off codes for your terminal. ZDENSTAL 1.3 +allows up to six bytes each for cursor on and cursor off. I have +only been able to find codes for a few of the terminals in +ZDENSTAL's library. If the codes for your terminal are not +included in ZDENSTAL, perhaps you could leave a short message to +me describing them (see below for address), and I will include +them in the next release. + + 1.6. Other Minor Enhancments. + +The cursor now turns off with the ^OZ command, giving a completely +blank screen if cursor on/off is installed (see 1.5, above). + +Version 1.0 repainted the whole screen after copying a block. Now +ZDE repaints only from the block to the bottom of the screen for +faster operation in most circumstances. I have also optimized +ZDE's general screen output routines somewhat for speed, though +this isn't noticable on terminals operating at less than 38.4 +kbaud. + +File input/output code has also been optimized for slightly +greater speed. + +And, last but not least, "quiet" operations now work up to 300 +percent faster! Both "quiet" macros and the "*" option of +find/replace boast greatly increased speed due to improved program +logic. + + +2. BUGS FIXED. + +If you did a backwards find/replace with ZDE 1.0 and responded to +the "Y/N" prompt with "*", the screen stayed off until you gave an +illegal command. The screen now comes back when replacement is +complete. + +ZCPR's GO restart now works after exitting when ZDE's window (^OW) +is active. Previously restarting with GO caused ZDE to limited +the number of lines of display available by 1/2. + +I fixed a bug with block markers failing to scroll the entire +display if entered in the last screen column. This was apparently +introduced when I made block marker displays more efficient in ZDE +1.0. + +If the cursor is to the left of the left margin, block markers now +move the cursor to the left margin instead of marking the blank +space between cursor and left margin as part of the block. + +For greater safety, ZDE now checks for free memory space before +adding a block marker with ^KK or ^KB. + +Place markers no longer cause words to "wrap" to the next line +when wordwrap is active. + +I fixed a minor bug ZDE 1.0 introduced to the ^OR command. Typing +^OR in non-document mode no longer produces the strange result of +setting the right margin to column seven. + +A display bug which occurred when the cursor was in column one of +a virtual screen with a tab to its left has been fixed. + +Previously, if only a drive letter was given in response to the +^KF command's "Dir:" prompt, ZDE searched user zero of the +specified drive, no matter what the current user number was. ZDE +now searches for files at the current user number of the specified +drive in these circumstances. + +The installed "output filter limit" was allowing one character +higher than the limit to be sent to the screen. Now only +characters equal to or less than the limit are sent. + + +3. INSTALLATION. + +You MUST use ZDENSTAL 1.3 to install this version of ZDE. Older +.ZDK and .ZDP files will still work; due to extra installation +codes, older .ZDE files will not work with this version. + +Version 1.0 of ZDENSTAL skipped the last few bytes of .ZDK key +overlay files, preventing a full 512-byte .ZDK key file overlay +from being installed. ZDENSTAL now installs the full 512 bytes. + +ZDENSTAL now allows four instead of just two default file types. + +Several new terminal definitions have been added to ZDENSTAL, as +well as some additional terminal codes (see 1.5, above). + + +4. FOR MORE INFORMATION. + +Finally, let me take this opportunity to thank the community of +users without whose efforts this version of ZDE would be far less +than what it is. Most of the above improvements and many of the +bugs fixed in this version result from comments and insights by +ZDE enthusiasts from around the country. Please let me know your +thoughts on this most recent version. + +Besides writing computer programs and working on a degree in +political science, I operate the Antelope Freeway Remote Access +Systems for CFOG, Chicago's First Osborne Group. These dual +remote systems are available at the same number, 312/764-5162, +Chicago. Antelope 1 covers CP/M and Z System, while Antelope 2 is +dedicated to MS-DOS support. The best way to contact me is +through this board. Registration is free. If you are not a +registered member, you can leave me a note as part of your +application. + +Alternatively, my mailing address is: + + 1359 W. Greenleaf, #1D + Chicago, IL 60626 + USA + +As I mentioned above, I don't plan to reissue the ZDE manuals with +the library while ZDE is still in development. For those who wish +an updated manual and/or wish to support ZDE, I offer instead a +pre-printed User's Manual and Installation Guide with Table of +Contents and Index for a contribution of $6 or more, postpaid. +The manual is printed in handsome proportional type on both sides +of 8.5 x 11 inch sheets and punched for a three-hole ring binder. +My costs for printing and mailing are about $3. + \ No newline at end of file diff --git a/Source/Images/d_zsdos/u1/ZDE16.COM b/Source/Images/d_zsdos/u1/ZDE16.COM new file mode 100644 index 00000000..55c492a5 Binary files /dev/null and b/Source/Images/d_zsdos/u1/ZDE16.COM differ diff --git a/Source/Images/d_zsdos/u1/ZDE16.DIR b/Source/Images/d_zsdos/u1/ZDE16.DIR new file mode 100644 index 00000000..a6e85025 --- /dev/null +++ b/Source/Images/d_zsdos/u1/ZDE16.DIR @@ -0,0 +1,25 @@ +ZDE16.DIR: +Files in ZDE16.LBR, 01 June 90 by Carson Wilson: + +Filename Purpose +------------ --------------------------------------- +SAMPKEY.DZC Sample key file for use with ZDENSTAL. +SAMPKEY.ZDK +SAMPKEY.ZDT + +ZDE16.CZM ZDE itself. + +ZDE16.DZR This file. + +ZDE16.FOR Short RCP/M description of ZDE. + +ZDE16.NZW New in ZDE version 1.6. + +ZDENST16.CZM Installer for ZDE version 1.6. + +ZDEPROP.DZC Proportional formatting support files. +ZDEPROP.ZZ0 + +ZDKCOM13.DZC Key compiler for ZDENSTAL. +ZDKCOM13.CZM + \ No newline at end of file diff --git a/Source/Images/d_zsdos/u1/ZDE16.FIX b/Source/Images/d_zsdos/u1/ZDE16.FIX new file mode 100644 index 00000000..9b48f026 Binary files /dev/null and b/Source/Images/d_zsdos/u1/ZDE16.FIX differ diff --git a/Source/Images/d_zsdos/u1/ZDE16.FOR b/Source/Images/d_zsdos/u1/ZDE16.FOR new file mode 100644 index 00000000..024183a7 --- /dev/null +++ b/Source/Images/d_zsdos/u1/ZDE16.FOR @@ -0,0 +1,8 @@ +Z System Display Editor, version 1.6. A small, fast, powerful +WordStar-type text editor for all CP/M and compatible systems with +the Z80 processor. In some ways superior to its big brother VDE, +this version of ZDE adds fully functional Auto-Indent capabilities, +Proportional Formatting, Auto-Recall of last response to prompts, +Global Find/Replace, Return to Previous Position (^QP) and more to +what many felt was already the best editor available for CP/M. + \ No newline at end of file diff --git a/Source/Images/d_zsdos/u1/ZDE16.NEW b/Source/Images/d_zsdos/u1/ZDE16.NEW new file mode 100644 index 00000000..a6d3de24 --- /dev/null +++ b/Source/Images/d_zsdos/u1/ZDE16.NEW @@ -0,0 +1,498 @@ + + New in ZDE + + Z-System Display Editor + + Version 1.6 02 Jun 90 + + ZDE is copyright 1990 by Carson Wilson, all rights reserved. + You use ZDE at your own risk. The author accepts no liability + for any damages resulting from its use or misuse. The files in + this library may not be circulated in any incomplete or + modified form without the written permission of Carson Wilson. + Any commercial use of ZDE, defined as any situation where the + duplicator receives revenue by duplicating or distributing ZDE + by itself or in conjunction with any hardware or software + product, is expressly prohibited unless authorized in writing + by Carson Wilson. + + This file describes changes to ZDE since version 1.3. I have + decided not to release document revisions while ZDE is still in + active development. For now, please obtain ZDE10.DOC and + ZDENSTAL.DOC from ZDE10.LBR, and ZDE13.NEW from ZDE13.LBR, + available on Antelope Freeway and many other remote CP/M + systems. Except for the changes detailed below, these text + files provide a thorough explanation of how to use and install + ZDE. Alternatively, send a contribution to support further + work on ZDE, and I will mail you a high quality, up-to-date + printed manual for ZDE version 1.6. See section 6.3 of this + file for details. + + + - CONTENTS - + + 1. FUNCTIONAL ENHANCEMENTS. + 1.1. Proportional Formatting. + 1.2. Last Entry Recall. + 1.3. Enhanced Operation Under CP/M Plus and ZSDOS. + 1.4. Control-U now Aborts "Delete To" Function. + 1.5. Fully Operational Auto-Indent (^OA). + 1.6. Chain to ZCPR Error Handler. + 1.7. Return to Previous Position Command (^QP). + 1.8. Global Find/Replace Option. + 1.9. Semicolons are now Characters in Nondocuments. + + 2. BUGS FIXED. + 2.1. More Robust Space Calculation. + 2.2. ZCPR Line Queue Bug Fixed. + 2.3. Other Minor Nuisances Corrected. + + 3. INSTALLATION. + 3.1. New in ZDENSTAL Version 1.6. + + 4. USAGE HINTS. + 4.1. Saving Macros. + 4.2. Creating a Smaller ZDE. + 4.3. Using GET and GO with ZDE. + + 5. DEVELOPMENT HISTORY. + + 6. FURTHER INFORMATION. + 6.1. About Z System. + 6.2. About the Author. + 6.3. Pre-Printed Manuals. + + +1. FUNCTIONAL ENHANCEMENTS. + + 1.1. Proportional Formatting. + +ZDE can now format your text for use with proportional printer +fonts! See the files ZDEPROP.DOC and ZDEPROP.Z80 (included in +ZDE16.LBR) for full details. + + 1.2. Last Entry Recall. + +The following commands (among others) cause ZDE to prompt +you for information: + + COMMAND PROMPT + ^KD "Load:" + ^KE "Erase:" + ^KN "Name:" + ^KL "Load:" + ^KR "Read:" + ^QF "Find:" + ^QA "Find:" and "Change to:" + ESC-M "Macro:" + +You can often save typing time and prevent errors by using +control-R or your right arrow key [as installed] to Recall your +last response to any of the above prompts. For example, to read +in a file and then erase it, you could enter "^KR, , +RETURN" followed by "^KE, ^R, RETURN". The ^R command will +automatically recalls the filename you entered in response to the +"Read:" prompt. The Find, Replace, and Macro prompts work in a +similar manner. ^R Recalls your entire response; right arrow +recalls your last response one character at a time to allow +further editing. + +The Recall keys can also be used to recall the last used macro, +even if it wasn't entered manually. + + 1.3. Enhanced Operation Under CP/M Plus and Z3PLUS. + +ZDE takes advantage of advanced BDOS error handling to provide +slightly faster file output under CP/M Plus and Z3PLUS. In +addition, file Create stamps are now preserved under CP/M Plus +based systems as well as ZSDOS and ZDDOS systems. + + 1.4. Control-U now Aborts "Delete To" Function. + +Control-U can now be used to abort a "Delete To" (^QT) operation. +Previously, ZDE would attempt to delete up to the next literal ^U +character in the file, often causing frustration. + + 1.5. Fully Operational Auto-Indent (^OA). + +Auto-Indent is useful for typing outlines, structured program +source code, and other text where the "left margin" varies. When +the Insert (^V) toggle is on, Auto-Indent aligns the left margin +with that of the previous line. When Insert is off, it causes +the RETURN key to act differently: RETURN advances the cursor +past any indentation when moving through a file. + +When a new line is begun either by wordwrap or reformat (see +below) or by hitting RETURN with Insert on, Auto-Indent indents +the following line or lines to equal the previous one. It does +this by counting the number of spaces or Hard TABs (whichever +came first) before the text of the previous line, and indenting, +wrapping, or reformatting using this number of spaces or Hard +TABs. This entails certain possible conflicts, most of which ZDE +itself automatically prevents: + +First, if you mix Hard TABs and spaces when indenting a line, AI +may give strange results, as it will count and use the first +character only. For example, if your line is indented as +, AI will indent the next line with +only. + +To avoid conflicts, enabling AI sets the left margin to 1, and +setting the left margin greater than 1 disables AI. Also, Double +Spacing (^O S) is disabled by Auto-Indent, and Auto-Indent +disables Double Spacing. + +Auto-indent status also now defaults to ON in Nondocument mode, +and OFF otherwise. + + 1.6. Chain to ZCPR Error Handler. + +ZDE 1.6 will chain to the installed Error Handler under ZCPR 3.4 +with error #12 (TPA overflow) if there was not enough memory for +ZDE to edit the file you specified on the command line. ZDE does +this because the flow of logic in a multiple command line +sometimes dictates that the editor complete its task before +succeeding commands are carried out. For example, I use the +a multiple command alias similar to the following + + ZDE file;echo erase;if in;era file;else;mv file newdir:;fi + +to process modem capture files. After processing each file with +ZDE I generally want either to erase it (if it contained nothing +worth saving) or move it to another directory. But on occasion +the capture file is too large to process with ZDE. If the script +were allowed to proceed, it would force me either to erase the +capture file or move it elsewhere, neither of which would be +appropriate if I hadn't even looked at its contents yet. + +This is where Z System's error handler comes it handy: it +provides a means of dealing with an offending command line before +unwanted commands cause harm. When ZDE chains to the error +handler, I can _edit_ commands beginning with ZDE's invocation, +either removing unwanted commands or inserting further commands +as the situation may dictate (it is also possible to abort the +command or multiple command script altogether). In the above +case, I would revise my command line to the following: + + NW file;echo erase;if in;era file;else;mv file newdir:;fi + +By substituting the ZDE command with NW I have invoked NewWord +for this instance only of the command, which in turn will edit +the larger file. + +Without question the finest ZCPR error handler now available is +Rob Friefeld's ZERR. There are even provisions within ZERR for +automatically editing LSH scripts and for automatically chaining +to transient versions when resident (RCP, CCP) commands fail; all +of this is described in ZERR13.LBR. + + 1.7. Return to Previous Position Command (^QP). + +This function is basically the same as the WordStar command: +hitting ^Q-P takes you to the position the cursor was at before +the previous command (including the ^QP command itself - try +it!). For instance, the sequence "^B, ^Q-P" reformats beginning +with the current line then restores the cursor to its original +position within the paragraph. ^Q-P is also useful for moving +long distances within a file without losing one's place: for +example you can move to the top of the file with ^Q-R, then come +back with ^Q-P. + +Within Macros, the previous cursor location is preserved: ^Q-P +will always return you to your place in the text BEFORE you +invoked the Macro. This not only allows you to resume editing at +the same location after using Macros; you can also build one or +more ^Q-P commands into the Macro itself. See SAMPKEY.* files +for one example; I'm sure there are lots of other ways to use +^Q-P within a Macro. Thanks to Howard Schwartz for keeping on my +case about this feature until I finally discovered an easy way to +implement it. + + + 1.8. Global Find/Replace Option. + +Besides "B" (backwards search) and "C" (case-sensitive search), +ZDE now sports a THIRD find/replace option: "G" for Global +search. ZDE normally begins your (backward or forward) search at +the current cursor location. Specifying "G" causes ZDE to start +at the BEGINNING of the file when searching forward for strings, +or at the END when searching backwards. Otherwise, the search or +replace command proceeds exactly as it does normally. The "G" +option is used in the same way as the other options, by enclosing +it in slashes as a prefix to the string being searched for (e.g., +to find "Carson" globally, enter "/gc/Carson" in response to +^Q-F's "Find: " prompt). + + 1.9. Semicolons are now Characters in Nondocuments. + +ZDE now counts the ";" (semicolon) character as a normal +character in "N" mode, whereas it counts the semicolon as a +punctuation mark in the "W" and "A" modes. This is mainly of +interest to assembly language programmers, as the semicolon is +often used alone to mark the beginnings of comments in source +code. Treating the semicolon as a normal character in "N" files +simply makes it easier to manipulate assembly language comments. +Thanks to Rob Friefeld for his thoughts on this. + + +2. BUGS FIXED. + + 2.1. More Robust Space Calculation. + +ZDE's calculation of memory space remaining is now more robust. +In past versions of ZDE (and VDE), certain editing commands could +cause a crash when only a few bytes of free memory were +available (thanks to Rob Friefeld). + + 2.2. ZCPR Line Queue Bug Fixed. + +The ^QQ (go to next line in ZCPR queue) command of previous +versions of ZDE failed if ZCPR's 16-byte user-defined buffer was +filled. Version 1.6 fixes this problem, properly returning to +the first line in the queue after the last value in has been +reached. This allows Turbo Pascal-like interactive programming +of Z80 assembly language when used under Z System with Al +Hawley's ZMAC Z80 Macro Compiler ($50 including ZML and ZMLIB; +contact Al Hawley at Ladera Z-Node, 213/670-9465 (modem), Ladera +Heights, CA). + + 2.3. Other Minor Nuisances Corrected. + +ZDE now turns alternate video off when placing the cursor at the +string to change during ^QA prompts. + +A bug which allowed an extra character or hyphen on the last line +of a paragraph if the line's length was one character beyond the +right margin has been fixed (thanks to Larry Schnitger for +spotting these). + +A bug which occurred if you set the block end marker to the last +byte in the file then did a block delete has been fixed (thanks +to Ben Cohen for finding this). + +One or more bug(s) which occurred while printing have been +cleared up. ZDE should now always give a correct left margin +with or without a page header or top margin. The printout is +also slightly faster when left and top margins are used together. + +ZDE now always finds strings located exactly at the end of a +file. + +When searching with the /c/ option, erroneous results occurred if +a non-matching string of the same length as the search string +beginning with the same first character as the search string was +located at the very end of a file. This has been fixed. + +Fixed minor bug in proportional formatting that caused repeated +^B commands to give inconsistent results under some +circumstances. + +Fixed bug which set margins spuriously if an illegal number was +entered. ZDE also now disallows setting margins to zero, which +caused formatting problems. + +^B (reformat) no longer absorbs following commands, allowing ^B +to be embedded in key redefinitions with SmartKey, etc. + + +3. INSTALLATION. + +You MUST use ZDENSTAL 1.6 to install this version of ZDE. Older +ZDK and .ZDP files will still work. Due to extra installation +codes, .ZDE files created with version 1.0 of ZDE will not work +with this version of ZDENSTAL. + + 3.1. New in ZDENSTAL Version 1.6. + +The proportional formatting (^OJ) toggle can be disabled with +ZDENSTAL to allow use of ZDE's proportional spacing table as a +patch area. When proportional spacing is disabled, an additional +96 bytes of space is released for custom user patching. ZDENSTAL +also allows you to set the proportional formatting toggle to ON +or OFF at startup [this can also be toggled while operating ZDE]. + +ZDENSTAL's "R" option now restores ZDE's proportional spacing +table to its default (as distributed) values. + +ZDENSTAL's "F" option now gives the locations of all FIVE key +definition tables (former versions left out the ESC- table). The +"F" option also gives the location of ZDE's proportional spacing +table for use when patching ZDE. + +The Osborne 1 and Vixen terminal definitions are now separate due +to different screen width requirements (thanks to Ben Cohen for +sorting this out). + +ZDENSTAL previously would overwrite .ZDK and .ZDP files with the +wrong information if the .ZDK or .ZDP file had been archived (bit +7 of filetype character 3 set). This problem has been corrected +in version 1.6. + +Some ZDENSTAL messages have been improved. + + +4. USAGE HINTS. + +The following are just some miscellania that I have picked up +from using and working on ZDE. + + 4.1. Saving Macros to Disk. + +Under Z System version 3.4, it is possible to "save your work" +after creating a working Macro. First, be sure to save your new +Macros as Macro Keys using ZDE's ESC-# command (see ZDE Manual). +Then exit ZDE and type + + POKE 310 02 50;ZSAVE 310-490 filename.ZDK + +You have just used Z System's type-4 SAVE program to create a new +.ZDK file, which can now be examined and installed permanently +into ZDE with ZDENSTAL, or further revised using ZDKCOM. [NOTE: +the addresses and values given above will work for ZDE version +1.6, but may change with future versions of ZDE.] + + 4.2. Creating a Smaller ZDE. + +If you are SURE you will never want to enable the Help Menus, you +can create a slightly smaller copy of ZDE. First use ZDENSTAL to +disable ZDE's Help Menus. Then issue the Z System command, + + GET 100 ZDE.COM;ZSAVE 100-3E7F filename.COM + +This creates a copy of ZDE that is about 1 kilobyte smaller than +the distributed package, and loads somewhat faster on slower +systems. This procedure is specific to ZDE 1.6; do NOT assume +that it will work on future versions. + + 4.3. Using GET and GO with ZDE. + +Some users have discovered that the Z System GET and GO commands +can be used to extend ZDE's versatility or save disk space. +Since the .ZDE, .ZDP, and .ZDK files used by ZDENSTAL are simply +binary images of various portions of ZDE, you can achieve the +versatility of having several copies of ZDE without the added +disk overhead by using Z System commands that "install" one or +more of ZDENSTAL's files on the fly. + +For example, if you used ZDE with a particular terminal most of +the time, but occasionally switched to another terminal that was +incompatible with the first one, you might want to create a Z +System Alias which would overlay the default copy of ZDE with +your second terminal's characteristics at startup. You could +achieve this by first using ZDENSTAL to configure ZDE for your +second terminal, saving the terminal's characteristics into a +.ZDE file (see the ZDE Manual for details on this). You would +then ZDE for your normal terminal, and write an Alias to +auto-install this copy of ZDE for your occasional terminal on the +fly: + + GET 100 ZDE.COM;GET 180 term2.ZDE;GO $* + +This command loads the file term2.ZDE over ZDE before proceeding, +providing a temporary copy of ZDE that works with your second +terminal. + +This approach can also be used to generate "virtual" copies of +ZDE with different printer codes and Macro Key definitions. The +addresses of the various overlays for ZDE version 1.6 are: + + 180 hex - Terminal codes (.ZDE files) + 1F0 hex - Printer codes (.ZDP files) + 310 hex - Macro Keys (.ZDK files) + +[These addresses may well change in future versions of ZDE.] + +Obviously, this involves some "homework:" if you get the +addresses wrong or use an incompatible .ZDx file (from a previous +version of ZDE, for example), you will likely cause yourself +grief. For these reasons I generally recommend simply creating +multiple copies of ZDE. But under some circumstances (e.g., +laptop computer with limited disk or ROM space) this approach may +be quite rewarding. + + +5. DEVELOPMENT HISTORY. + +ZDE is a descendant of Eric Meyer's famous VDE (Video Display +Editor) program for CP/M. In 1988, Eric produced his final +version of VDE for CP/M (2.66). Eric now maintains VDE for +MS-DOS only; ZDE continues VDE's evolution on Z80 systems. ZDE +removes many of the CP/M VDE's defects and adds such features as +support for ZCPR, ZSDOS, and CP/M Plus, improved terminal and +disk I/O performance, proportional formatting, file datestamp +support, and improved installation. Meanwhile, the MS-DOS +version of VDE has continued to evolve, now boasting multiple +file buffers, auto-save, auto-number, and much more. VDE for +MS-DOS is available on computer bulletin boards (including +Antelope Freeway; see below) as VDE152.ZIP. + + +6. FURTHER INFORMATION. + + 6.1. About Z System. + +If you are using CP/M equipment, you have probably heard of Z +System. Basically this is a downward compatible replacement for +the system software distributed with Z80 CP/M equipment. By +utilizing Z80 opcodes and advanced design techniques, the authors +of Z System have expanded the versatility of the CP/M operating +system far beyond its original limitations. To learn more about +Z System, log into the bulletin board listed below, or contact +one of the following: + + Plu*Perfect Systems Sage Microsystems East + 410 23rd St. 1435 Centre St. + Santa Monica, CA 90402 Newton Centre, MA 02159-2469 + (213)-393-6105 (eves.) (617)-965-3552 (9am-11pm) + + 6.2. About the Author + +First, let me take this opportunity to thank the community of +users without whose support this version of ZDE would not have +been possible. You have made ZDE what it is through your +generous contributions of time and/or money. Without those who +appreciate (and criticize) my work I doubt I could justify the +time and energy I've spent on ZDE. In addition, many of the +improvements and bug fixes in version 1.6 result directly from +specific comments and insights by users. Please continue to +share your thoughts and experiences with ZDE; I'm listening. + +I am a doctoral student trying to keep from starving while +working on my dissertation in Political Science at Loyola +University of Chicago. I am a coauthor of ZSDOS, a commercial +replacement for the BDOS portion of CP/M, and have authored +numerous CP/M and Z System programs. My interests include +computer programming, telecommunications, the Soviet Union, +Political Theory, and bicycling (not necessarily in that order). +I am also looking for part- or full-time work. To this end I am +skilled in political analysis, know the Russian language and the +Z80, C, BASIC, and Pascal computer languages, and have +comprehensive knowledge of CP/M and Z System and working +knowledge of Unix and MS-DOS. If you know of a gainful opening +in the computer field, I would like to hear from you. + +Among other things I operate the Antelope Freeway Remote Access +Systems for CFOG, Chicago's First Osborne Group. These dual +remote systems are available at the same number, 312/764-5162, +Chicago. Antelope 1 covers CP/M and Z System while Antelope 2 is +dedicated to MS-DOS support. The best way to contact me is +through this board. Registration is free. If you are not a +registered member, you can leave me a note as part of your +application. + +Alternatively, my mailing address is: + + 1359 W. Greenleaf, #1D + Chicago, IL 60626 + USA + + 6.3. Pre-Printed Manuals. + +As I mentioned above, I don't plan to reissue the ZDE manuals +with the library while ZDE is still in development. For those +who wish an updated manual and/or wish to support ZDE, I offer +instead a pre-printed 40-page User's Manual and Installation +Guide with Table of Contents and Index, postpaid, for those who +make contributions of $6 or more to support further development +of ZDE. + \ No newline at end of file diff --git a/Source/Images/d_zsdos/u1/ZDE16A.COM b/Source/Images/d_zsdos/u1/ZDE16A.COM new file mode 100644 index 00000000..3e9fdcc0 Binary files /dev/null and b/Source/Images/d_zsdos/u1/ZDE16A.COM differ diff --git a/Source/Images/d_zsdos/u1/ZDE16A.PAT b/Source/Images/d_zsdos/u1/ZDE16A.PAT new file mode 100644 index 00000000..e4520856 --- /dev/null +++ b/Source/Images/d_zsdos/u1/ZDE16A.PAT @@ -0,0 +1,102 @@ +; This patch file modifies the officially-distributed .COM file +; for ZDE Ver 1.6 (copyright by Carson Wilson) to: +; - Correct a bug which did not preserve create times when +; editing files > 1 extent. +; - Use an apparently 'dead' byte in the configuration area as +; a configuration flag to allow disabling the 'Auto-Indent' +; feature which was always 'on' in ZDE1.6. +; +; With the second change, you may configure the 'Auto-Indent' +; feature to be active (as distributed) or disabled (as this patch +; is configured) by altering the DB at label 'AIDflt' in the +; second part of this patch file below. +; +; Assemble this file to a .HEX file (example uses ZMAC) as: +; +; ZMAC ZDE16A.PAT /H +; +; then overlay the resulting ZDE16.HEX onto ZDE16.COM with MYLOAD +; (or equivalent) as: +; +; MYLOAD ZDE.COM=ZDE.COM,ZDE16.HEX +; +; The resulting ZDE.COM will be identified as 'ZDE 1.6a' in the +; text identification string near the beginning of the .COM file. +; +; Harold F. Bower, 18 July 2001. +; +; CP/M Standard Equates +; +BDOS EQU 0005H +FCB EQU 005CH +DMA EQU 0080H +TPA EQU 0100H +; +SDMA EQU 26 ; CP/M Function to set DMA Address +; +; Needed locations within ZDE 1.6 +; +Fill EQU TPA+0F8BH ; For Date Patch +TimBuf EQU TPA+3B3FH ; " " " +; +VTFlg EQU TPA+3ADAH ; For Auto-Ins Patch +HCRFlg EQU TPA+3AE3H ; " " " " +LfMarg EQU TPA+3AFDH ; " " " " +; +; ----------- Begin Patch File ----------- +; +; --- Fix Create Time Stamp Preservation Error --- + + ORG TPA+0029H + ; was: + DB 'a, (C)' ; DB ', Copr.' + ORG TPA+2461H + ; was: + LD (FCB+13),A ; CALL ClUsrF +; + ORG TPA+2F10H + ; was: + LD B,4 ; CALL ClUsrF + CALL ClUsrF ; LD DE,TimBuf + LD DE,TimBuf ; LD C,SDMA + CALL SetDMA ; CALL BDOS +; + ORG TPA+30AAH + ; was: + LD DE,DMA ; LD C,SDMA +SetDMA: LD C,SDMA ; LD DE,DMA +; + ORG TPA+30B4H + ; was: +ClUsrF: XOR A ; XOR A + EX DE,HL ; LD (FCB+13),A + JP Fill ; RET +; +; --- Usurp Config Flag for Auto-Insert use, sense on startup --- +; + ORG TPA+0057H + ; was: 0FFH +AIDflt: DB 00H ; Set Desired default (0=Off, FF=On) +; + ORG TPA+262AH + ; was: + LD (LfMarg),HL ; LD HL,0101H + XOR A ; LD (LfMarg),HL + LD (VTFlg),A ; XOR A + LD (HCRFlg),A ; LD (VTFlg),A + NOP ; LD (HCRFlg),A + LD A,(AIDflt) ; DEC A +; + ORG TPA+2711H + ; was: + NOP ; LD A,(0157H) {Unknown Use} + NOP ; OR A + NOP ; JP Z,Error2 + NOP + NOP + NOP + NOP +; +;------------ End of Patch File ------------ + END + \ No newline at end of file diff --git a/Source/Images/hd0/s1/u0/ZDENST.COM b/Source/Images/d_zsdos/u1/ZDENST16.COM similarity index 100% rename from Source/Images/hd0/s1/u0/ZDENST.COM rename to Source/Images/d_zsdos/u1/ZDENST16.COM diff --git a/Source/Images/d_zsdos/u1/ZDEPROP.DOC b/Source/Images/d_zsdos/u1/ZDEPROP.DOC new file mode 100644 index 00000000..5d3cd676 --- /dev/null +++ b/Source/Images/d_zsdos/u1/ZDEPROP.DOC @@ -0,0 +1,139 @@ +ZDEPROP.DOC +Proportional Spacing with ZDE +June 2, 1990 +Carson Wilson + + 1. INTRODUCTION. + 2. USING ZDE'S PROPORTIONAL FORMATTING FEATURE + 3. INSTALLATION AND CUSTOMIZATION. + + +1. INTRODUCTION. + +ZDE version 1.6 incorporates an elemental proportional formatting +feature. This option is useful only to those whose printers have +proportional spacing capabilities. Proportional formatting allows +you to format ragged right text properly for proportional output. +Centering and right justification of proportional text are not +available. + + +2. USING ZDE'S PROPORTIONAL FORMATTING FEATURE + +Proportionally spaced printing gives a more professional look, +allowing more characters to be printed on a line without +crowding. This is because less space is allocated to narrow +characters like "i" and more to wide characters like "M." Most +computer printers do NOT allow proportional spacing; only laser +printers, some daisy-wheel equipment, and newer dot-matrix +printers have this ability. Typically, there is a special set of +instructions which when sent to the printer, cause it to use +proportional spacing rather than monospacing. On some models +(the Silver Reed SR550 for example), DIP switches also control +this feature. + +ZDE does not itself perform proportional spaced printing; it +simply allows you to format your text for use with a printer that +has this capability. You must instruct your printer to space +proportionally before sending text to it. If you have tried using +the proportional capability of your printer, you probably have +noticed that LINES WITH LOTS OF WIDE CHARACTERS IN THEM +print out much wider than lines comprised of narrow characters. +ZDE 1.6 allows you to compensate for this by counting the width +of each character when it formats a paragraph (^B command), and +wrapping each line when its width count reaches the equivalent +of the current right margin setting. + +The ^OJ command toggles this feature on and off [the default may +also be installed with ZDENSTAL]. When proportional spacing is +active, the legend "PS" appears in ZDE's status line (this is +overwritten by "MR" when the margins are released). +Proportional spacing occurs only during the ^B reformat command, +not during wordwrap, so to format a document proportionally, you +must use ^B at the beginning of each paragraph after you key it +in. Still, the results are well worth the extra trouble, especially +for material that will be reproduced. + + +3. INSTALLATION AND CUSTOMIZATION. + +ZDE comes preconfigured to format text for proportional spacing +with Epson's new LQ510 dot matrix printer. Since most printers +allocate roughly the same ratios of space to the various +characters, there is a good chance you won't need to install ZDE +for your printer. This file has been formatted for proportional +output by ZDE; try printing it proportionally to see the results. + +If you should decide to change ZDE's proportional spacing to +match your printer, you can patch ZDE's offset values for +individual characters. The offsets are stored in a sequential +ASCII table whose location is given by ZDENSTAL's "F" option. +The table consists of positive and negative (2's compliment) byte +values, one for each character beginning with " " and going +through "~". As distributed, all of the values are either -12, -6, +0, +6, or +12. The negative numbers are expressed as (256+n), +where n is the negative number. So for example, -6 is expressed +as (256+(-6)) or 250 (FA hex). + +ZDE uses its ASCII table as follows. When reformatting a line, +ZDE counts each character as (30+table value)/30 of a column +wide. So if the offset is zero, the character is one column wide +(30/30 = 1) and ZDE increments the current column by one. If +the character's offset is -6, it counts as only (30+(-6)/30) or +24/30 columns wide, and ZDE increments the current column by +only 24/30. If the character's offset is +12, it counts as 42/30 +columns wide, and so on. When a full column is "lost" due to +characters with negative offsets, ZDE adds the next character to +the line without incrementing the column count. Similarly, when +enough "extra" columns are counted to equal a full character, ZDE +increments its column count without actually adding another +character to the line. + +To see the effects of this, try reformatting a paragraph or two, +first with PS on, then with it off. Lines of wide characters are +shortened on screen, while lines of narrow characters are allowed +to extend beyond the right margin. When the text is printed +proportionally, your printer reverses the process. Lines of wide +characters are lengthened and lines of narrow characters are +shortened, the net result being proportionally spaced text with an +even right margin. + +If your printer's proportional output is not properly compensated +for by ZDE, it is possible to install another offset table within +ZDE to suit your needs. The current table's address is given by +ZDENSTAL's "F" option. To find the location of a character's +offset, add its ASCII value to ZDE's table address and subtract +32. For example, the ASCII value for "A" is 65, so the location of +"A"'s offset is (table address+65-32) or (table address+33). + +You may adjust a character's offset to any value between -34 and ++35 decimal, but keep in mind that offsets if less than -29 may +result in lines that _never_ reformat since characters with +offsets of -30 or less take up no virtual space or less! To +calculate the proportional ratios used by your printer, it is best +to print out a line of thirty of each character in proportional +mode and compare the length of each line to the others. Most +characters should result in lines of roughly the same length; this +is your "base" length, and you should set the offsets of these +characters in ZDE to 00. Other lines will probably fall into four +or so other lengths; hopefully these lengths will correspond to an +even xx/30ths of the "base" length. To get the proper offset +value, just subtract the number of characters equal in length to +a line of "base" characters from 30. For example, if only 18 +capital "M"'s take up the same length as 30 base characters, the +offset for "M" is 30-18 or +12. If it takes 42 "i"'s to equal the +length of 30 base characters, the offset for "i" is 30-42 or -12. + +Once you have determined the offsets in 30ths of each +character's width from the base length, just plug these values +into ZDE's proportional table. You can use a patcher to alter a +few characters. For extensive modification, I have provided the +assembly language file ZDEPROP.Z80 which can be assembled to a +.HEX file overlay file for ZDE. Before assembling ZDEPROP.HEX, +you must edit the file and set the equate TABLE to the current +address of ZDE's proportional spacing table as given by +ZDENSTAL. Then overlay ZDE with ZDEPROP.HEX using MLOAD, +SID, or DDT. + +- End of ZDEPROP.DOC - + \ No newline at end of file diff --git a/Source/Images/d_zsdos/u1/ZDEPROP.Z80 b/Source/Images/d_zsdos/u1/ZDEPROP.Z80 new file mode 100644 index 00000000..715adcb0 --- /dev/null +++ b/Source/Images/d_zsdos/u1/ZDEPROP.Z80 @@ -0,0 +1,119 @@ +; +; File: ZDEPROP.Z80 - Default proportional width table for ZDE. +; Author: Carson Wilson +; Date: 02 Jun 90 +; Notes: Assembles to a .HEX file which overlays ZDE's native table. + +; EQUATES + +TABLE equ ????h ; Change "????" to the value given by ZDENSTAL + ; ..for "proportional table." +; + ORG TABLE + +; Offset Corresponding character + + db 0 ; space + db -12 ; ! + db 0 ; " + db 0 ; # + db 0 ; $ + db 6 ; % + db 6 ; & + db -12 ; ' + db -6 ; ( + db -6 ; ) + db 0 ; * + db 0 ; + + db -12 ; , + db 0 ; - + db -12 ; . + db 0 ; / +; + db 0 ; 0 + db 0 ; 1 + db 0 ; 2 + db 0 ; 3 + db 0 ; 4 + db 0 ; 5 + db 0 ; 6 + db 0 ; 7 + db 0 ; 8 + db 0 ; 9 +; + db -12 ; : + db -12 ; ; + db 0 ; < + db 0 ; = + db 0 ; > + db 0 ; ? +; + db 6 ; @ + db 6 ; A + db 6 ; B + db 6 ; C + db 6 ; D + db 6 ; E + db 6 ; F + db 6 ; G + db 6 ; H + db -6 ; I + db 0 ; J + db 6 ; K + db 6 ; L + db 12 ; M + db 6 ; N + db 6 ; O + db 6 ; P + db 6 ; Q + db 6 ; R + db 6 ; S + db 6 ; T + db 12 ; U + db 6 ; V + db 12 ; W + db 6 ; X + db 6 ; Y + db 0 ; Z + db -6 ; [ + db 0 ; \ + db -6 ; ] + db 0 ; ^ + db 0 ; _ + db -12 ; ` +; + db 0 ; a + db 6 ; b + db 0 ; c + db 6 ; d + db 0 ; e + db -6 ; f + db 6 ; g + db 6 ; h + db -12 ; i + db -6 ; j + db 6 ; k + db -12 ; l + db 12 ; m + db 6 ; n + db 0 ; o + db 6 ; p + db 6 ; q + db 0 ; r + db 0 ; s + db -6 ; t + db 6 ; u + db 6 ; v + db 12 ; w + db 0 ; x + db 6 ; y + db 0 ; z + db -6 ; { + db -12 ; | + db -6 ; } + db 0 ; ~ + + end + +; End ZDEPROP.Z80 + \ No newline at end of file diff --git a/Source/Images/d_zsdos/u1/ZDKCOM13.COM b/Source/Images/d_zsdos/u1/ZDKCOM13.COM new file mode 100644 index 00000000..040fd1c4 Binary files /dev/null and b/Source/Images/d_zsdos/u1/ZDKCOM13.COM differ diff --git a/Source/Images/d_zsdos/u1/ZDKCOM13.DOC b/Source/Images/d_zsdos/u1/ZDKCOM13.DOC new file mode 100644 index 00000000..75b83079 --- /dev/null +++ b/Source/Images/d_zsdos/u1/ZDKCOM13.DOC @@ -0,0 +1,209 @@ + + ZDKCOM + + Key File Compiler for ZDE + + Version 1.3 01 Jun 90 + + ZDKCOM and its documentation are adapted with permission from + VDKCOM12.LBR, by Fred Haines, Glendale Littera RCPM/QBBS, 818 + 956-6164. VDKCOM version 1.2 is copyright 1988 by Fred Haines, + and is released by the author to the CP/M community for all + reasonable noncommercial purposes. For the Z80 source code to + ZDKCOM, see ZDKCOM13.LBR. + + + CONTENTS + + 1. How to Use ZDKCOM. + 2. Programmer's Note. + + +1. How to Use ZDKCOM. + + ZDKCOM compiles an ASCII text file into a *.ZDK file of key +macros which ZDENSTAL will install in ZDE. It alsos convert an +existing ZDK file into a text file with the extension ZDT. It +allows you to create, edit, and re-edit complicated key macros +with a full-featured word processor - ZDE itself - instead of the +backspace-only line editor in ZDENSTAL. The syntax is: + + ZDKCOM .ZDT + converts text file to ZDK overlay + + ZDKCOM .ZDK + converts overlay to ZDT text file + + Use ZDE in NONDOCUMENT mode to type a file that looks like +what you see when you use the K option of ZDENSTAL, substituting +lower case "n" or "q" for the No Repeat and Quiet option +indicators and : + + qThis line would be for key 0 in Quiet mode.^M + nThis line would be for key 1 in No Repeat mode.^M + + This line would be key 3, with key 2 unassigned. + n^M^M^M + The line above, key 4, prints with two blank lines following it. + q"This is in quotes, but no carriage return at the end." + "This line is ^P^BMacro Key 7^P^B with embedded boldface codes."^M + q^VThis line turns insert on before printing the line.^A^A^A^A_^F^D_^M + n ^A^A^A^A + +...and so on. Lines correspond to the number keys in 0 to 9 +order, with blank lines for unassigned keys. Do NOT embed control +codes. Type everything in ASCII. For instance, to underline, +type out '^P^Swords underlined^P^S'. It's a good idea to make +hard carriage returns visible by toggling them on with the ^OD +command. ZDKCOM accepts trailing spaces as part of the macro, +even though you can't see them, so, if you don't want them, make +sure each line is terminated by a hard carriage return. + Individual key macro strings may not exceed 127 bytes in the +ZDK file, though they may in the ZDT text file, since ASCII +representations of single-byte control codes require two bytes, +and option flags and slashes don't contribute to the length of the +string. + The total of all strings may not exceed 498 output bytes. +This is two bytes less than allowed by ZDE and ZDENSTAL, but, +under the program's present logic, it would require an inordinate +amount of code to make those last two bytes available. If you +really need them, add them to the ZDK file with ZPATCH or any +other patcher, locate the last string-length byte, and add 2 (in +hex) to it. + For a quick demo, remove the leading spaces from the lines +below, mark them as a block, and write them out to .ZDT. Run +ZDKCOM .ZDT. Then load the resulting ZDK file into ZDE with +the command ZDENSTAL ZDE .ZDK. + + q^C^C^C^E^E^E^E^E^[^E + q^[b^[0^Qs^X^[=^M1^[= 1^S^V ^D^[!0^[1^Qb^B^Ku + ^[=^M0^V^I^V^B^G^[![^[0^X + ^[=^M0^T^B^N^[0^X + q^P^[^P^I^P^K^P^[9 + q^V^KrA0:LH.FMT^M + q^G^G^G^G^X + q^A^A^S + q^A^A^S + q^KrHEADER^M + +Key macro 0 measures off one full page if the top margin is 0 and +the page length is 58 lines. Adjust for different page lengths by +adding or subtracting ^E's. I use this macro to move from line 1 +of a page to line 1 of the next page. I then use key macro 9 to +read in a page header from a separate file that contains nothing +but the properly spaced header text with a # in place of the page +number. I search for # with a ^Qf and replace it with the +appropriate page numbers. + This happens so quickly on a 9 mhz SB180 with hard disk that +it is virtually unnoticeable, but there is a faster way to do it. +Just make up a header at the top of the first page, or, if you +want to start page headers only on page two, at the foot of the +file, and mark it as a block. Use key macro 0 to move from the +top line of a page to the top line of the next, and copy the +header into place with the ^KC command. Don't forget to erase the +original of the header at the foot of the file when you get there. + Key macro 1 softens and reforms a paragraph by removing the +hard carriage returns from the ends of each line. + Key macro 2 changes a document in flush left block format +(like this one) to indented paragraph form, and key macro 3 +changes it back again. + Key macro 4 embeds my DIABLO 1610-compatible printer's code +for setting a left margin in column 11 (^K=0Bh) in the document. +If I want another margin I add or subtract from 0Bh and overwrite +the ^K with the appropriate control character. You may well be +able to substitute your own printer's set left margin code for +this one. + Key macro 5 reads in a previously prepared letterhead stored +as LH.FMT on drive/user A0. + Key macro 6 removes four spaces or characters from the +beginning of each line, used to remove line numbers from the +beginnings of messages in modem capture files. + + +2. Programmer's Note. + + A ZDK file is a data structure four records long. The first +two bytes are an ID number which ZDENSTAL checks to ensure that it +is working with the correct version. The current number, 0250h, +is valid for several recent versions of ZDE. + These two bytes are followed by up to ten strings, each +preceded by a string-length byte which ZDE uses to index the +string's location. Each string is limited by the size of a buffer +in ZDE to 127 bytes. + An empty string is indicated by a string-length byte of 00h. +After the last byte of the last string, the remainder of the 512 +bytes are filled with 00h. + It is a peculiarity of ZDKCOM that it needs to write two null +bytes at the end of the file, which means that it will accept only +498 bytes for the total length of all strings (512, less two ID +bytes, less 10 string-length bytes, less these two more) rather +than the 500 that ZDE and ZDENSTAL allow. + When you look at the key macro buffer of ZDE using the K +option of ZDENSTAL, you see something that looks like this: + + <0>This line would be for key 0 in Quiet mode.^M + <1>This line would be for key 1 in No Repeat mode.^M + <2><> + <3>This line would be key 3, with key 2 unassigned. + <4>^M^M^M + <5>The line above, key 4, prints with two blank lines following it. + <6>"This is in quotes, but no carriage return at the end." + <7>"This line is ^P^BMacro Key 7^P^B with embedded boldface codes."^M + <8><> + <9><> + +The numbers down the side and the option indicators , , and +<> (no option) are supplied by ZDENSTAL. The ZDT text file +version of the same macro keys would omit the string numbers and +no option indicators, and convert the and to simple lower +case 'n' and 'q'. If you need lower case 'n' or 'q' as the first +character of a key macro, just precede it with a slash, which will +be ignored. + ZDKCOM recognizes blank lines as unassigned keys. These +produce an "error" message when invoked by ZDE. + ZDE recognizes a No Repeat option if the the high bit is set +on the first byte of the macro string after the length byte, and a +Quiet option if the high bit is set on both of the first two bytes +of the string. + The translation procedure from text to overlay requires: + + Enter two-byte ID string 02h 50h in output file. + + Enter 00h to save a place for a string length byte. + + Check for "n" or "q" as first three bytes of new string and add + 80h to the next byte or next two bytes input as required. + + Read in the rest of the string byte by byte and output each byte + to the output file. If "^" appears in the string, drop it and + subtract 40h from next byte to make it into a real control + character. If "/" appears, ignore it and print the next + character literally. "/^" prints "^" rather than turn the + character following the carat into a control code, and "//" + prints a single slash. A single input slash is ignored. + + When the CRLF pair is encountered in the input text file, + convert it to a 00h place marker for the length of the next + string. Get the number of bytes output in the last string and + write it to the position of the string-length byte at the head + of the preceding string. + + When the text file's ^Z EOF is encountered, fill the remainder + of the total 512 bytes with 00h and close the files. + +When converting ZDK overlays to ZDT text files, the procedure is +more or less reversed. ZDKCOM converts the string length bytes to +CRLF pairs, the control code bytes to two-byte ASCII strings '^c', +and finishes off the file when all strings are accounted for by +inserting an EOF ^Z. + In addition, ZDKCOM has error checking for file opening and +closing and for individual and overall string length, and it +reports activity to keep the user from thinking the program has +hung, though the counters on the screen are otherwise meaningless. + The structure of the four-sector ZDK file in CP/M is so +similar to the eight-sector MS/DOS version that you can use +ZDKCOM, along with a patcher, to create a VDK file for +installation in VDE12, the MS/DOS version of ZDE. Make up the ZDT +file as you would for CP/M, then patch the second ID byte from 50h +to 60h and add four records of nulls. + \ No newline at end of file diff --git a/Source/Images/fd0/u0/CLRDIR.COM b/Source/Images/fd0/u0/CLRDIR.COM deleted file mode 100644 index d1f2a7d6..00000000 Binary files a/Source/Images/fd0/u0/CLRDIR.COM and /dev/null differ diff --git a/Source/Images/fd0/u0/DDT.COM b/Source/Images/fd0/u0/DDT.COM deleted file mode 100644 index 83f8603f..00000000 Binary files a/Source/Images/fd0/u0/DDT.COM and /dev/null differ diff --git a/Source/Images/fd0/u0/DDTZ.COM b/Source/Images/fd0/u0/DDTZ.COM deleted file mode 100644 index e30a34c0..00000000 Binary files a/Source/Images/fd0/u0/DDTZ.COM and /dev/null differ diff --git a/Source/Images/fd0/u0/DIF.COM b/Source/Images/fd0/u0/DIF.COM deleted file mode 100644 index 87b89d75..00000000 Binary files a/Source/Images/fd0/u0/DIF.COM and /dev/null differ diff --git a/Source/Images/fd0/u0/NULU.COM b/Source/Images/fd0/u0/NULU.COM deleted file mode 100644 index 3d45098a..00000000 Binary files a/Source/Images/fd0/u0/NULU.COM and /dev/null differ diff --git a/Source/Images/fd0/u0/SID.COM b/Source/Images/fd0/u0/SID.COM deleted file mode 100644 index 3b073ba5..00000000 Binary files a/Source/Images/fd0/u0/SID.COM and /dev/null differ diff --git a/Source/Images/fd0/u0/WS.COM b/Source/Images/fd0/u0/WS.COM deleted file mode 100644 index aa028bc3..00000000 Binary files a/Source/Images/fd0/u0/WS.COM and /dev/null differ diff --git a/Source/Images/fd0/u0/WS.OVR b/Source/Images/fd0/u0/WS.OVR deleted file mode 100644 index 5e3c8773..00000000 Binary files a/Source/Images/fd0/u0/WS.OVR and /dev/null differ diff --git a/Source/Images/fd0/u0/WSCHANGE.COM b/Source/Images/fd0/u0/WSCHANGE.COM deleted file mode 100644 index bc85c1fc..00000000 Binary files a/Source/Images/fd0/u0/WSCHANGE.COM and /dev/null differ diff --git a/Source/Images/fd0/u0/WSCHANGE.OVR b/Source/Images/fd0/u0/WSCHANGE.OVR deleted file mode 100644 index 4f707c63..00000000 Binary files a/Source/Images/fd0/u0/WSCHANGE.OVR and /dev/null differ diff --git a/Source/Images/fd0/u0/WSCHHELP.OVR b/Source/Images/fd0/u0/WSCHHELP.OVR deleted file mode 100644 index 49becf77..00000000 Binary files a/Source/Images/fd0/u0/WSCHHELP.OVR and /dev/null differ diff --git a/Source/Images/fd0/u0/WSMSGS.OVR b/Source/Images/fd0/u0/WSMSGS.OVR deleted file mode 100644 index 84625d8e..00000000 Binary files a/Source/Images/fd0/u0/WSMSGS.OVR and /dev/null differ diff --git a/Source/Images/fd0/u0/WSPRINT.OVR b/Source/Images/fd0/u0/WSPRINT.OVR deleted file mode 100644 index 83bef6ea..00000000 Binary files a/Source/Images/fd0/u0/WSPRINT.OVR and /dev/null differ diff --git a/Source/Images/fd0/u0/WSREADME.TXT b/Source/Images/fd0/u0/WSREADME.TXT deleted file mode 100644 index 7a75b22c..00000000 --- a/Source/Images/fd0/u0/WSREADME.TXT +++ /dev/null @@ -1,880 +0,0 @@ - --THE README FILE-- - ------------------------ - -README contains late-breaking news and tips about WordStar, -and information about printers. - - -THE DISKS THAT CAME IN YOUR PACKAGE ------------------------------------ - -The file HOMONYMS.TXT is included on the Speller disk -contrary to what is listed in Appendix D. - - -INSTALLATION ------------- - -WINSTALL and WSCHANGE - - WordStar has two installation programs: - - o WINSTALL contains the basic choices to install WordStar. - It is recommended for all users. - - Be sure and install your valid disk drives since WordStar - running under CP/M cannot recover from attempts to access non- - existent disk drives. - - o WSCHANGE contains every installation and customization - choice. It is designed for advanced users and users who - want to customize WordStar after they're familiar with it. - Use the menu listing below for a directory of the menus - in WSCHANGE. - -Directory of WSCHANGE Menus - - The chart below shows the organization of menus in WSCHANGE. - Print it out and refer to it as you customize WordStar. - - Main Installation Menu - - A Console - A Monitor - A Monitor selection - B Monitor name - C Screen sizing - B Function keys - C Monitor patches - A Special characters - B Cursor control - C Screen control - D Keyboard patches - A Function keys - B Save function keys - E Interface patches - A Console busy handshaking - B Special I/O subroutines - B Printer - A Printer choices - A Printer selection - B Printer name - C Default printer driver - B Printer driver library - A Select library file - B Create smaller library - C Add new printer driver - D Change printer driver data - C WS printer patches - A Custom print controls, printer initialization - - NOTE: Disregard the "CUSTOM & SIMPLE Controls Save CUSTOM/SIMPLE - Controls" option shown. This is not available from this menu. - - D Printing defaults - E Printer interface - A Printer port selection - B Printer busy handshaking - C Printer subroutines - C Computer - A Disk drives - A Valid disk drives - B Maximum valid user number - C Delay disk access if typing - B Operating system - A Single-user system - B Multi-user MP/M - C Multi-user Turbo DOS - D ZCPR3 - C Memory usage - D WordStar files - E Directory display - F Computer patches - D WordStar - A Page layout - A Page sizing & margins - B Headers & footers - C Tabs - B Editing settings - A Edit screen & help level - B Typing - C Paragraph alignment - D Blocks - E Erase & unerase - F Lines & characters - G Find & replace - H WordStar 3.3 compatibility - I Printing defaults - C Other features - A Spelling checks - B Nondocument mode - C Indexing - D Shorthand (key macros) - E Merge printing - F Miscellaneous - E Patching - A Auto patcher - B Save settings - C Reset all settings - -MEMORY USAGE ------------- - - WordStar requires a minimum TPA size of 50 kbytes to run - using the factory defaults. The TPA is the amount of memory - available in your computer for use by programs that have a - file type of COM. To see how big the TPA is in your computer, - press the question mark key (?) at the Opening Menu. - - The amount of memory required by WordStar can be reduced by - approximately 3 kbytes if necessary. Use the WSCHANGE program - to select the minimum memory configuration option. The menu - will show you what capabilities are being reduced. - - WordStar uses a general-purpose buffer for a variety of - tasks. WordStar allocates memory to this buffer for editing, - for merge printing, and at the Opening Menu (see BFSIZE in - PATCH.LST). The buffer used for editing is usually the most - sensitive to a reduced TPA size. (You may be able to use the - Opening Menu and print, but there may be insufficient memory - for editing.) - - The merge print buffer is used only to hold merge print - variable names and data. Increase it if you run out of memory - while merge printing. - - The Opening Menu buffer is used primarily to hold the file - directory, and for miscellaneous tasks. - - -LOW-MEMORY INDICATOR IN STATUS LINE ------------------------------------ - - If the Low-Memory indicator appears in the status line, it - means that WordStar was unable to complete some function. - The most common symptoms are: the line number in the - status line is wrong, or a paragraph alignment could not be - completed. You may correct the line counter by saving your - file, exiting WordStar, and re-loading your file. To correct - the paragraph alignment, move your cursor to the point where - paragraph alignment stopped, and then press ^B again. - - The reason this comes up is that WordStar was not able to fit - a big enough chunk of text into memory at one time. - - When you first begin editing, WordStar uses the value from - EDSIZE in the user area to determine the minimum amount - of memory required for a page of text. The default - is set for approximately a 55 line by 66 column page. If - your page size is routinely larger than this, you may want - to increase EDSIZE. Multiply the number of lines by the - number of columns, and divide by 128. - - If the Low-Memory indicator comes on while printing, it is due - to either the same reasons as for editing, or there is - insufficient memory to print the text proportionally spaced. - The amount of memory required depends on which printer - driver you are using. If you aren't using the .PS ON dot - command to turn proportional spacing on in your document, - low memory won't be a problem. Also, WordStar uses more - memory for merge printing than it does for regular printing - (around 2.5 kbytes more). - - The Low-Memory indicator will also appear when a full disk error - is encountered during editing. Treat the disk-full error as you - would normally. - - -RAM-RESIDENT PROGRAMS ---------------------- - - RAM-resident programs, such as SmartKey, reduce the amount of - working memory (TPA) that WordStar can use. The new features in - WordStar, such as shorthand, may reduce the need for these - RAM-resident programs, thus freeing memory for WordStar. - - -ZCPR3 SUPPORT -------------- - - In order to enable the ZCPR facilities within WordStar, the user - must use the Z3INS utility provided with ZCPR to install the - address of the ZCPR "environment" into WordStar. The environment - contains information that WordStar uses to support ZCPR-specific - functions. - - Generally, the user should log onto the drive containing the file - WS.COM, and issue the command: - - Z3INS SYS.ENV WS.COM - - The user should also run either WINSTALL or WSCHANGE to further - install WordStar for ZCPR. However, this is not mandatory because - the only thing that happens is that the WordStar sign-on says - "ZCPR3," and the LGLUSR location in the user area is changed for a - maximum user number of 31. (The normal default for LGLUSR is 15.) - - Once the user has installed WordStar for use with ZCPR, the user - will be able to use the following ZCPR features: - - - A named directory may be used when logging onto a new drive/user. - - - A named directory may be used instead of a drive/user as part - of any file name. - - - The drive/user always appears above file directories. (For CP/M - only the drive letter is shown if the user number is zero.) - - - The directory name also appears above the directory if one has - been defined for the currently logged drive/user. - - - If WordStar does not find its OVR files on the current drive and - user, it will search the drives and user numbers in the ZCPR - search path rather than using its standard search pattern. - - - WordStar installs itself as a ZCPR "shell" process which lets the - user enter any legal ZCPR command when running a program. (CP/M - can only run programs that are COM files.) - - -OSBORNE USERS -------------- - - The command to change a hard carriage return to a soft carriage - return (document mode) or to turn Auto-indent ON (nondocument - mode) does not function on the Osborne because of a limitation - in its BIOS. The following patch can be applied to change the - command from ^^ to ^- (Ctrl-Hyphen): - - Using DDT or SID in the file WSMSGS.OVR: - - At 02DA replace 1E with a 1D - At 02EF replace 1E with a 1D - At 0359 replace 1E with a 1D - At 06B2 replace 1E with a 1D - At 06C9 replace 1E with a 1D - - At the system prompt type SAVE 53 WSMSGS.OVR - - For more information on how to use SID or DDT, see your CP/M - reference guide. As always, be sure and apply the patch to a - COPY of the file. - - -INSTRUCTIONS FOR TWO FLOPPY DISK COMPUTERS ------------------------------------------- - - Do not remove the Program disk while you are using WordStar. - - The Printer Driver Library file (WSPRINT.OVR) on the WordStar - program disk is much smaller than the Printer Driver Library - file contained on the disk labeled PRINTER. Be sure to read the - section in "Starting" that discusses the printer library file. - - -RUN A PROGRAM -------------- - - Once you press R you can type the drive and user number for the - program you want to run. You may run only .COM files. CCP commands, - such as DIR cannot be used. - - -INDEXING --------- - -Using StarIndex - - StarIndex 1.01 works with files created with this release of - WordStar. - -"Can't Use That Printer" Message - - When WordStar creates an index or table of contents, it uses - the printer drivers $INDEX and $TOC. If you created a smaller - WSPRINT.OVR file, you may have left these drivers out. To - return them to the file, copy the original WSPRINT.OVR file - onto your disk. When you create a smaller file again, be sure - to save these drivers. See Appendix C in the WordStar manual - for a list of other drivers to save. - - -SPELL CHECKING --------------- - - Dual floppy disk users: - - Unless you have sufficient room on your working WordStar program - disk for the files TW.COM, SPELL.COM, MARKFIX.COM, REVIEW.COM and - MAINDICT.CMP you will not be able to run a spell check from the - Opening Menu. You will need to exit WordStar and replace the - working WordStar program disk with the dictionary disk you created - during installation. This disk should contain the files listed - above. Make sure the disk in drive B has the file you want to - spell-check. - - Follow the directions for running a spell check in The WORD Plus - manual. - - -UPGRADING FROM A PREVIOUS RELEASE ---------------------------------- - - This release of WordStar contains many new features and commands. - See the "What's New" booklet for a complete list. The following - changes came in too late to be included in the documentation. - -Printer Patches - - Previous versions of WordStar treat most dot matrix printers - and other non-daisy wheel printers as a DRAFT printer with a - few patchable items. Because of this, many users have used - these patches to be able to use certain features of their - printers. Sometimes the patches have been quite extensive, and - some users have many files that count on them. - - The printer drivers of WordStar Release 4, on the other hand, - are very powerful. Almost every driver recognizes all the print - controls and all the dot commands. In fact, if a document is - written to be printed on one kind of printer, it is likely that - it will also print fine on some other printer. - - However, if you want to use your existing files with WordStar - 4, and those files rely on the user area being patched in a - special way, you can probably do so by moving the patches into - WordStar 4, and using the CUSTOM or SIMPLE printer driver. - - On the INSTALL disk is a program called MOVEPRN.COM that - copies the printer driver portion of the previous release's - user area into files that can be installed into Release 4 with - the "auto patcher" feature. - - Copy the program MOVEPRN.COM onto the disk containing the - WS.COM file for the previous version. Type - - MOVEPRN WS.COM FILE1.PAT FILE2.PAT - - MOVEPRN extracts the proper portions of the user area and - writes them into two files that may then be used with the "auto - patcher" feature of WSCHANGE. - - FILE1.PAT is to be used with the general patching menu - (Choose E "Patching" on the WSCHANGE Main Menu, then A "Auto - Patcher"). FILE2.PAT should be used to install strings first - into the SIMPLE driver, and then into the CUSTOM driver (choose - B "Printer" on the WSCHANGE Main Menu, then B "Printer driver - library", D "Change printer driver data" and D "Driver auto - patcher"). - - Test print your document first with the SIMPLE driver, and then - with the CUSTOM driver to see which one produces the most - satisfactory results. - - Also read Appendix C for more information on using the Auto - Patcher. - - -Hanging Indents - - For WordStar Professional Release 4, MailMerge reformats indented - text created with ^OG to the current margins. If you want the text - to remain indented, use embedded ruler lines or the .RM, .LM, - and .PM commands. See the "Reference Guide" for more information. - - Pressing ^OG to wrap back to the first tab on the ruler line after - having reached the last tab works the same way it did in previous - versions of WordStar, contrary to what is stated in the manual. - - -TERMINALS ---------- - - WordStar comes installed for an "idealized" special terminal. - WINSTALL and WSCHANGE allow you to install many terminals by - name, thus allowing WordStar to take advantage of the special - features that the terminal might support, such as underlining - or the function keys. - - Use either WINSTALL or WSCHANGE to pick your specific terminal - or computer screen from the Monitor menu. If your terminal - isn't on the menu, it probably emulates one of those that is - there. Look in your terminal documentation to find out. - - After you install WordStar for the proper terminal, run - WordStar and open the file PRINT.TST to see which attributes - (such as bold and underline) work on your screen. - WordStar will highlight the following in some way... - - Bold (^PB) - Underline (^PS) - Strike-out (^PX) - Subscript (^PV) - Superscript (^PT) - Doublestrike (^PD) - Italics (^PY) - Blocks (^KB, ^KK) - Error messages - - Most of the time, normal text will be shown in dim intensity, - and highlighted text will be shown in bright intensity. You - may have to use a brightness and/or contrast knob to adjust - your screen the first time you use WordStar this way. - - If your dim intensity is too dim to see well, and you can't - adjust it, you can change the BRITE flag to ON using WSCHANGE. - This will invert bright and dim in your text, so that regular - text is displayed bright, and highlighted text will be - displayed as dim. However, text in the menus is not affected. - - -DISPLAY PROBLEMS WITH TERMINALS -------------------------------- - - Once you have installed WordStar for the proper terminal, you - may still experience display problems. - - If text from the previous screen remains after WordStar - displays a new screenful of text, the most likely cause is - cursor wrap. Basically, WordStar must know what happens to the - cursor when a character is displayed at the rightmost position - of the screen. It can either remain at the right edge, or it - can wrap to the beginning of the next line. The WRAP flag in - WordStar must be set either on or off to correspond to the - way the terminal works. (It is generally set for the - terminal's factory default, but the default can usually be - changed using the terminal's setup mode.) - - Another possible cause for display problems is your terminal's - incomplete emulation of some other terminal. The most - common differences are... - - Line insert (LININS), line delete (LINDEL), - Erase to end of screen (ERAEOS), - Erase to end of line (ERAEOL), - And, erase screen (ERASCR). - - Look in the manual for your terminal and use WSCHANGE to see - if the control sequences match. - - -PRINTERS --------- - -WHAT'S IN THIS SECTION - - This section contains the following information: - - Choosing a Printer - Setting Up Your Printer - Printer Drivers - Proportional Printing - Laser Printers - Information on Specific Printers - -CHOOSING A PRINTER - - WordStar is ready to work with over 100 printers. The printer you - choose during installation becomes your default printer. However, - when you print a document, you can choose any other printer. To - choose a default printer, follow these steps: - - 1. Look at the Printer Information brochure that came in your - package. The first chart shows the printers listed on the - Printer Selection Menus. If your printer is on the menu, - simply choose it during installation. - - 2. If your printer isn't listed on the menu, it may work like a - printer that is. Refer to the second chart in the Printer - Information brochure for a list of printers that work like - printers on the menu. When WordStar asks you to choose a - printer, choose the printer that works like yours. - - 3. If neither chart lists your printer, choose Typewriter Printer - (if your printer can backspace) or Draft Printer (if it can't). - These choices may not take advantage of all your printer's - features, but they will work with almost any printer. - - Note: If you choose Draft or Typewriter, you can modify custom - print controls and printer initialization. - - If you want to make more modifications to take advantage of your - printer's feature, choose the Custom or Simple drivers, then use - the WS Printer Patches section of WSCHANGE to tell WordStar the - codes for your printer. Refer to your printer manual for these - codes. Some printers work better with the Custom driver and some - with the Simple driver. Try using both and see which works better - with your printer. See the "Reference Guide" for more information. - -SETTING UP YOUR PRINTER - -Choosing a Printer Port - - Each printer is connected to a printer port at the back of - the computer. WordStar looks for printers on the LST: port. - If your printer is connected to a different port, use - WSCHANGE to tell WordStar the correct port. - -Testing Your Printer Connection - - At the operating system prompt, type "PIP LST:=READ.ME." This - file should be printed by your printer. If it is not, your printer - may be connected to a different port. See your computer reference - manual, and the section on the STAT command in your CP/M - reference manual for more information. - - -PRINTER DRIVERS - - The WSPRINT.OVR file on the Printers disk contains a printer - driver for each printer on the Printer Selection Menu. The printer - driver for a printer contains all the codes WordStar needs to work - with that printer. - - Each printer driver has a short name. If you choose a printer when - you print a document, you see the names of the printer drivers, not - the names of the printers. - -PROPORTIONAL PRINTING - - WordStar supports proportional printing on a number of printers. - To turn on proportional printing, either install WordStar to - default to proportional printing, or place a ".PS on" command - in your document. At print time, WordStar selects the - appropriate proportional font based on the character width - (.CW) currently in effect. - - The specific printer descriptions later in this section show - recommended character widths for proportional typefaces. - These widths are for a normal mix of upper- and lowercase - letters. If you have many words or phrases all in uppercase - or if you want your text less densely printed, choose a larger - character width. - - While WordStar mostly sets character widths based on the - proportional-width table in the driver, on the more advanced - daisy wheel printers, WordStar uses the printer's proportional- - spacing mode. WordStar determines how much white space is needed - to right-justify the line based on its own proportional width - tables. If the table values don't match the wheel installed, - WordStar won't be able to justify the line correctly. - - WordStar sends standard ASCII characters; if a proportional wheel - uses a different spoke mapping, set up the printer to handle this. - -LASER PRINTERS - - WordStar supports laser printer features such as font changes - and proportional spacing. - - WordStar supports several laser printers: the Canon LPB-8 A1 & A2; - the Hewlett-Packard LaserJet, LaserJet+, and LaserJet 500+; - and the Ricoh LP4080. Refer to the "Specific Printer - Information" section of this file for information on these - printers. General notes about using laser printers are given below. - -Paper Size and Margins - - Laser printers come with preset page margins. You need to - compensate for these margins by changing page length in your - WordStar documents. The chart below shows the recommended - settings for 8 1/2 X 11 inch paper for both portrait and landscape - orientations. These settings allow 55 lines of text for portrait - orientation and 40 lines of text for landscape orientation (at 6 - lines per inch). They also allow for a footer of up to 3 lines - and a one-line header. If you use multiple-line headers, adjust - the top margin accordingly. - - Dot Default Portrait Landscape - Setting Command Value Orientation Orientation - ------- ------- ------- ----------- ----------- - page length .PL 66 62 47 - top margin .MT 3 2 2 - bottom margin .MB 8 5 5 - header margin .HM 2 1 1 - footer margin .FM 2 2 2 - - If the laser printer is your primary printer, you can use WSCHANGE - to make these settings the defaults. - - Because laser printers leave small margins at the left and right - sides of the page, you may want to use a smaller page offset - setting (the default is .PO 8). - -Form Feeds - - When you print with a laser printer, answer Y for yes to the "Use - form feeds (Y/N)?" prompt at print time. (The default is NO.) If - the laser printer is your primary printer, you can use WSCHANGE to - change the default to yes. - -WordStar Commands for Font Selection - - The WordStar dot commands and print control commands listed below - determine the fonts used for printing a document. - - .PR .PR OR=L selects landscape orientation; .PR OR=P (or just - .PR OR) selects portrait orientation (the default). If - either of these commands appears after the first printing - line on a page, the orientation will not change until the - following page. - - .PS .PS ON selects proportionally spaced characters; .PS OFF - (the default) selects fixed-spaced characters. - - .CW The character-width setting (.CW followed by the width in - 120ths of an inch) determines the character pitch and font - selected for fixed-width printing. For proportional fonts, it - determines the point size and proportional-width table - selected. - - .LQ .LQ ON selects near letter quality print (if supported by - your printer). LQ OFF selects draft quality print. Default - is ON. - - ^PY The italic print control toggles between normal and italic - characters when the appropriate italic font is available. - - ^PB The boldface print control toggles between normal and bold - characters when the appropriate bold font is available. - - ^PD The double strike print control used with the laser printers - toggles overprinting with a horizontal offset of 1/120" - between the two character images. This allows a bold effect - where no bold font is available. - - ^PA ^PA turns alternate pitch on. Use .CW to assign different - character widths to normal pitch (see ^PN below) and alternate - pitch so that each pitch accesses a different font. You can - then change fonts by switching between the two pitches. This - is the only way to use two fonts on the same line. - (See "Character width" and "Pitch" in the "Reference Guide.") - - ^PN ^PN turns normal pitch on. You can use it with ^PA as - described above. - - ^P@ When working with columns, if you use alternate and normal - pitch for two fonts, or if you use proportional spacing, you - may need to use ^P@ to make sure the columns line up. - Remember that the column position set with ^P@ is determined - by the normal pitch character width. (See "Columns" and - "Proportional spacing" in the "Reference Guide." - -INFORMATION ON SPECIFIC PRINTERS - - This section describes the capabilities of each printer listed on - the Printer Selection Menu. The printers are listed in alphabetical - order (except for the generic printers such as "Draft," - "Typewriter," "Custom," "Simple," and the various print-to-disk - options, which are listed first). - - There is a chart for each printer explaining how features work and - listing any special notes about the printer. Each printer is - described in the following format: - -PRINTER NAME ----- Driver: (short name) - - ^PY Effect of italics/ribbon color print control - ^PT/V Subscript/superscript information - .CW Information on available character widths and fonts. The - chart shows the .CW, .LQ, and .PS settings required to use - different fonts. - - .LQ OFF .LQ ON .PS ON Font Name - ------- ------ ------ --------- - .cw val .cw val recommended value (range) font 1 - .cw val .cw val recommended value (range) font 2 - - .UL Continuous-underline information (if restrictions) - .UJ Microspace-justification information (if restrictions) - - N/A means a command has no effect on this printer. - - NOTES Switch settings, special features, anomalies. - -DRAFT PRINTER (nonbackspacing) ----- Driver: DRAFT - - ^PD Overprints the line twice - ^PB Overprints the line three times - ^PS Overprints the underscore character in a separate pass - ^PT/V Prints super/subscripts with a full line between - super/subscript and text - .LH Sets line height only in multiples of full lines - .CW N/A - .PS N/A - .LQ N/A - .UJ N/A - - NOTES This driver works with any printer that doesn't automatically - perform a line feed when it receives a carriage return command. All - overprinting is done by returning the carriage and passing over the - line again. - -TYPEWRITER PRINTER (backspacing) ----- Driver: TYPEWR - - ^PD Backspaces and overprints each character twice - ^PB Backspaces and overprints each character three times - ^PS Backspaces and overprints the underscore character - ^PT/V Prints super/subscripts with a full line between - super/subscript and text - .LH Sets line height only in multiples of full lines - .CW N/A - .PS N/A - .LQ N/A - .UJ N/A - - NOTES This driver works with any printer that doesn't automatically - perform a line feed when a it receives a carriage return command, - and responds to a backspace character. Overprinting is done by - backspacing. - -AUTO LINE FEED PRINTER (backspacing) ----- Driver: AUTOLF - - ^PD Backspaces and overprints each character twice - ^PB Backspaces and overprints each character three times - ^PS Backspaces and overprints the underscore character - ^PT/V Prints super/subscripts with a full line between - super/subscript and text - .LH Sets line height only in multiples of full lines - .CW N/A - .PS N/A - .LQ N/A - .UJ N/A - - NOTES This driver works with any printer that automatically - performs a line feed when it receives a carriage return character, - and responds to a backspace command. Overprinting is done by - backspacing. - -SIMPLE CUSTOMIZABLE PRINTERS ----- Driver: SIMPLE - - All print controls cause control strings (on and off) in - the user area to be sent to the printer. These strings - are used by both the SIMPLE and CUSTOM drivers. They can - be installed with the WSCHANGE program. - - .LQ Controlled by user area strings - .PS Controlled by user area strings - .CW N/A - .UJ N/A - .LH N/A - - NOTES This printer driver prints the line in one pass, sending - control strings from the user area to select print enhancements. - -CUSTOMIZABLE PRINTERS ----- Driver: CUSTOM - - All print controls cause control strings (on and off) in - the user area to be sent to the printer. These strings - are used by both the SIMPLE and CUSTOM drivers. They can - be installed with the WSCHANGE program. - - .LQ ON/OFF controlled by user area strings - .PS ON/OFF controlled by user area strings - .LH Sets line height only in multiples of full lines - .UJ N/A - .CW N/A - - NOTES This driver prints the line in multiple passes, sending - control strings from the user area to select print enhancements. - -PREVIEW TO DISK ----- Driver: PRVIEW - - This driver prints documents to the PREVIEW.WS file to allow - you to preview the format and appearance of a document before - printing. Headers, footers, and pagination are shown correctly - and print controls remain in the file to display onscreen - attributes. Dot commands are not printed. - -PRINT TO DISK WITHOUT PRINT CONTROLS ----- Driver: ASCII - - This driver prints to the ASCII.WS file, stripping headers and - footers, high bits, and print controls. - -PRINT TO DISK WITHOUT HEADERS AND FOOTERS ----- Driver: XTRACT - - This driver prints to the XTRACT.WS disk file, stripping headers - and footers, but preserving high bits and print controls. - -ANADEX 9500A, 9500B ----- Driver: 9500 - - ^PY N/A - ^PT/V Even superscript roll - - .CW .CW Font name - --- --------- - 9 13.3 cpi - 10 12 cpi - 12 10 cpi - 18 6.7 cpi - 20 6 cpi - 24 5 cpi - - .LH 1/24" resolution, use even values - .UJ This printer has no incremental horizontal positioning - .PS N/A - .LQ N/A - -ANADEX 9501B, INTEQ 5100B ----- Driver: 9501B - - ^PY N/A - ^PT/V Even superscript roll - - .CW .CW Font name - --- --------- - 7 16.7 cpi - 8 15 cpi - 10 12.5 cpi - 12 10 cpi - 14 8.3 cpi - 16 7.5 cpi - 20 6.2 cpi - 24 5 cpi - - .LH 1/24" resolution, use even values - .UJ This printer has no incremental horizontal positioning - .PS N/A - .LQ N/A - -C. ITOH STARWRITER 1550 AND 8510 ----- Driver: C1550 - - ^PY N/A - ^PT/V Prints full-size characters with roll - - .CW .CW Font Name - --- --------- - 7 compressed - 10 elite - 12 pica - 14 expanded compressed - 20 expanded elite - 24 expanded pica - - .LQ N/A - .PS N/A - .UL Continuous underlining suppresses microspace justification - -C. ITOH F10 STARWRITER ----- Driver: QUME - - See Diablo 630, 1610, 1620 Daisy Wheel. - - Note: Proportional printing was tested with a Theme 10 wheel. - -CANON LBP-8A1 AND LBP-8A2 LASER PRINTER ----- Driver: LBP8 - - ^PY Selects italics if appropriate font installed - ^PT/V Prints full-size characters with roll - .PS .PS - .CW OFF ON Font Name - --- -- --------- - 6 - 20 cpi - 8 - 15 cpi - 9 - 13.3 cpi - 10 - 12 cpi (elite) - 12 - 10 cpi - 20 - 6 cpi - 24 - 5 cpi - 16 - 7.5 cpi - - 7 (0-8) Garland 8 point - - 10 (9-11) Garland 12 point - - 14 (12-17) Expanded 8 point - - 20 (18-30) Expand \ No newline at end of file diff --git a/Source/Images/fd0/u0/WSSHORT.OVR b/Source/Images/fd0/u0/WSSHORT.OVR deleted file mode 100644 index b44f1480..00000000 Binary files a/Source/Images/fd0/u0/WSSHORT.OVR and /dev/null differ diff --git a/Source/Images/fd0/u0/WSU.COM b/Source/Images/fd0/u0/WSU.COM deleted file mode 100644 index 39830fdf..00000000 Binary files a/Source/Images/fd0/u0/WSU.COM and /dev/null differ diff --git a/Source/Images/fd0/u0/ZAP.COM b/Source/Images/fd0/u0/ZAP.COM deleted file mode 100644 index 47ffcbb8..00000000 Binary files a/Source/Images/fd0/u0/ZAP.COM and /dev/null differ diff --git a/Source/Images/fd0/u0/ZDE.COM b/Source/Images/fd0/u0/ZDE.COM deleted file mode 100644 index 9bc493c3..00000000 Binary files a/Source/Images/fd0/u0/ZDE.COM and /dev/null differ diff --git a/Source/Images/fd0/u0/ZDE40.COM b/Source/Images/fd0/u0/ZDE40.COM deleted file mode 100644 index 6a2d37de..00000000 Binary files a/Source/Images/fd0/u0/ZDE40.COM and /dev/null differ diff --git a/Source/Images/fd1/u0/CLOCKS.DAT b/Source/Images/fd1/u0/CLOCKS.DAT deleted file mode 100644 index a78c2d6c..00000000 Binary files a/Source/Images/fd1/u0/CLOCKS.DAT and /dev/null differ diff --git a/Source/Images/fd1/u0/LDDS.COM b/Source/Images/fd1/u0/LDDS.COM deleted file mode 100644 index 9be1d4a4..00000000 Binary files a/Source/Images/fd1/u0/LDDS.COM and /dev/null differ diff --git a/Source/Images/fd1/u0/LDP2D.COM b/Source/Images/fd1/u0/LDP2D.COM deleted file mode 100644 index 40220f17..00000000 Binary files a/Source/Images/fd1/u0/LDP2D.COM and /dev/null differ diff --git a/Source/Images/fd1/u0/STAMPS.DAT b/Source/Images/fd1/u0/STAMPS.DAT deleted file mode 100644 index 23cd9bd7..00000000 Binary files a/Source/Images/fd1/u0/STAMPS.DAT and /dev/null differ diff --git a/Source/Images/fd1/u0/ZCAL.COM b/Source/Images/fd1/u0/ZCAL.COM deleted file mode 100644 index a5add241..00000000 Binary files a/Source/Images/fd1/u0/ZCAL.COM and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/DDT.COM b/Source/Images/hd0/s0/u0/DDT.COM deleted file mode 100644 index 83f8603f..00000000 Binary files a/Source/Images/hd0/s0/u0/DDT.COM and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/DDTZ.COM b/Source/Images/hd0/s0/u0/DDTZ.COM deleted file mode 100644 index e30a34c0..00000000 Binary files a/Source/Images/hd0/s0/u0/DDTZ.COM and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/DIF.COM b/Source/Images/hd0/s0/u0/DIF.COM deleted file mode 100644 index 87b89d75..00000000 Binary files a/Source/Images/hd0/s0/u0/DIF.COM and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/HDIR.COM b/Source/Images/hd0/s0/u0/HDIR.COM deleted file mode 100644 index 17096622..00000000 Binary files a/Source/Images/hd0/s0/u0/HDIR.COM and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/NULU.COM b/Source/Images/hd0/s0/u0/NULU.COM deleted file mode 100644 index 3d45098a..00000000 Binary files a/Source/Images/hd0/s0/u0/NULU.COM and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/R.COM b/Source/Images/hd0/s0/u0/R.COM deleted file mode 100644 index fecaa4ac..00000000 Binary files a/Source/Images/hd0/s0/u0/R.COM and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/RSETSIMH.COM b/Source/Images/hd0/s0/u0/RSETSIMH.COM deleted file mode 100644 index e055f07a..00000000 Binary files a/Source/Images/hd0/s0/u0/RSETSIMH.COM and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/SID.COM b/Source/Images/hd0/s0/u0/SID.COM deleted file mode 100644 index 3b073ba5..00000000 Binary files a/Source/Images/hd0/s0/u0/SID.COM and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/TIMER.COM b/Source/Images/hd0/s0/u0/TIMER.COM deleted file mode 100644 index 83e6e688..00000000 Binary files a/Source/Images/hd0/s0/u0/TIMER.COM and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/UNARC.COM b/Source/Images/hd0/s0/u0/UNARC.COM deleted file mode 100644 index 8cc90746..00000000 Binary files a/Source/Images/hd0/s0/u0/UNARC.COM and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/VIDATT.Z80 b/Source/Images/hd0/s0/u0/VIDATT.Z80 deleted file mode 100644 index 073bb84f..00000000 --- a/Source/Images/hd0/s0/u0/VIDATT.Z80 +++ /dev/null @@ -1,69 +0,0 @@ - title WordStar 4.0 Video Attributes Driver - -strngout equ 0283h - -esc equ 1bh -dim equ 1 -blink equ 2 -invert equ 4 -uline equ 8 - - aseg - org 03c1h - -vidatt: - xor a - ld hl,funtbl - ld b,8 -getloop: - rr c - jr nc,getnext - or a,(hl) -getnext: - inc hl - djnz getloop -; - ld hl,string+2 ; attribute #1 on/off indicator - push hl - ld b,4 - ld de,'?!' ; ? = attribute off, ! = attribute on -setloop: - rra - jr nc,attroff - ld (hl),e ; attribute on - jr setnext -attroff: - ld (hl),d ; attribute off -setnext: - inc hl - inc hl - inc hl ; advance to next on/off indicator - djnz setloop -; - pop hl ; hl --> dim on/off - ld a,d ; attribute off - cp (hl) ; dim off? - jr nz,setdim - ld a,e ; attribute on -setdim: - ld (hl),a - ld hl,string - jp strngout ; ws string routine -; -; -funtbl: - defb dim ; strike out - defb invert or blink ; warnings & errors - defb invert ; block - defb uline ; underline - defb blink ; subscript - defb blink or uline ; superscript - defb invert ; menu, headline, bold, double - defb invert or uline ; italics, RET, backspace -; -string: - defb 12,esc,' 2',esc,' 3',esc,' 4',esc,' 5' -; -finis equ $ - end - \ No newline at end of file diff --git a/Source/Images/hd0/s0/u0/W.COM b/Source/Images/hd0/s0/u0/W.COM deleted file mode 100644 index 2e5ab12a..00000000 Binary files a/Source/Images/hd0/s0/u0/W.COM and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/ZAP.COM b/Source/Images/hd0/s0/u0/ZAP.COM deleted file mode 100644 index 47ffcbb8..00000000 Binary files a/Source/Images/hd0/s0/u0/ZAP.COM and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/ZDE.COM b/Source/Images/hd0/s0/u0/ZDE.COM deleted file mode 100644 index 9bc493c3..00000000 Binary files a/Source/Images/hd0/s0/u0/ZDE.COM and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/initdir.com b/Source/Images/hd0/s0/u0/initdir.com deleted file mode 100644 index fd46387a..00000000 Binary files a/Source/Images/hd0/s0/u0/initdir.com and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/ldds.com b/Source/Images/hd0/s0/u0/ldds.com deleted file mode 100644 index f4b235fc..00000000 Binary files a/Source/Images/hd0/s0/u0/ldds.com and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/ldnzt.com b/Source/Images/hd0/s0/u0/ldnzt.com deleted file mode 100644 index 521fc3aa..00000000 Binary files a/Source/Images/hd0/s0/u0/ldnzt.com and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/ldp2d.com b/Source/Images/hd0/s0/u0/ldp2d.com deleted file mode 100644 index 502032c2..00000000 Binary files a/Source/Images/hd0/s0/u0/ldp2d.com and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/path.com b/Source/Images/hd0/s0/u0/path.com deleted file mode 100644 index 5c0aa503..00000000 Binary files a/Source/Images/hd0/s0/u0/path.com and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/putds.com b/Source/Images/hd0/s0/u0/putds.com deleted file mode 100644 index c0ceba64..00000000 Binary files a/Source/Images/hd0/s0/u0/putds.com and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/z34.rel b/Source/Images/hd0/s0/u0/z34.rel deleted file mode 100644 index 0e9d5236..00000000 Binary files a/Source/Images/hd0/s0/u0/z34.rel and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/z41.zrl b/Source/Images/hd0/s0/u0/z41.zrl deleted file mode 100644 index ed10dfad..00000000 Binary files a/Source/Images/hd0/s0/u0/z41.zrl and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/zcpr33.rel b/Source/Images/hd0/s0/u0/zcpr33.rel deleted file mode 100644 index 7c0a2567..00000000 Binary files a/Source/Images/hd0/s0/u0/zcpr33.rel and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/zs203.zrl b/Source/Images/hd0/s0/u0/zs203.zrl deleted file mode 100644 index 11be02dd..00000000 Binary files a/Source/Images/hd0/s0/u0/zs203.zrl and /dev/null differ diff --git a/Source/Images/hd0/s0/u0/zsconfig.com b/Source/Images/hd0/s0/u0/zsconfig.com deleted file mode 100644 index 2cc26074..00000000 Binary files a/Source/Images/hd0/s0/u0/zsconfig.com and /dev/null differ diff --git a/Source/Images/hd0/s0/u14/initdir.cfg b/Source/Images/hd0/s0/u14/initdir.cfg deleted file mode 100644 index 96baa986..00000000 Binary files a/Source/Images/hd0/s0/u14/initdir.cfg and /dev/null differ diff --git a/Source/Images/hd0/s0/u14/zxd.cfg b/Source/Images/hd0/s0/u14/zxd.cfg deleted file mode 100644 index f202ae59..00000000 Binary files a/Source/Images/hd0/s0/u14/zxd.cfg and /dev/null differ diff --git a/Source/Images/hd0/s0/u2/WSHELP.OVR b/Source/Images/hd0/s0/u2/WSHELP.OVR deleted file mode 100644 index 02634675..00000000 Binary files a/Source/Images/hd0/s0/u2/WSHELP.OVR and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Attack.pt3 b/Source/Images/hd0/s0/u3/Attack.pt3 deleted file mode 100644 index dc9d04a9..00000000 Binary files a/Source/Images/hd0/s0/u3/Attack.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Backup.pt3 b/Source/Images/hd0/s0/u3/Backup.pt3 deleted file mode 100644 index e9141fe4..00000000 Binary files a/Source/Images/hd0/s0/u3/Backup.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/BadMice.pt3 b/Source/Images/hd0/s0/u3/BadMice.pt3 deleted file mode 100644 index aa78a8d2..00000000 Binary files a/Source/Images/hd0/s0/u3/BadMice.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Demo.mym b/Source/Images/hd0/s0/u3/Demo.mym deleted file mode 100644 index 255fc160..00000000 Binary files a/Source/Images/hd0/s0/u3/Demo.mym and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Demo1.mym b/Source/Images/hd0/s0/u3/Demo1.mym deleted file mode 100644 index b224f321..00000000 Binary files a/Source/Images/hd0/s0/u3/Demo1.mym and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Demo3.mym b/Source/Images/hd0/s0/u3/Demo3.mym deleted file mode 100644 index 808db891..00000000 Binary files a/Source/Images/hd0/s0/u3/Demo3.mym and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Demo3mix.mym b/Source/Images/hd0/s0/u3/Demo3mix.mym deleted file mode 100644 index b5981848..00000000 Binary files a/Source/Images/hd0/s0/u3/Demo3mix.mym and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Demo4.mym b/Source/Images/hd0/s0/u3/Demo4.mym deleted file mode 100644 index ed2e8a85..00000000 Binary files a/Source/Images/hd0/s0/u3/Demo4.mym and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/HowRU.pt3 b/Source/Images/hd0/s0/u3/HowRU.pt3 deleted file mode 100644 index 47fb464e..00000000 Binary files a/Source/Images/hd0/s0/u3/HowRU.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Iteratn.pt3 b/Source/Images/hd0/s0/u3/Iteratn.pt3 deleted file mode 100644 index 575cccf4..00000000 Binary files a/Source/Images/hd0/s0/u3/Iteratn.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/LookBack.pt3 b/Source/Images/hd0/s0/u3/LookBack.pt3 deleted file mode 100644 index 1dd15ac7..00000000 Binary files a/Source/Images/hd0/s0/u3/LookBack.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Louboutn.pt3 b/Source/Images/hd0/s0/u3/Louboutn.pt3 deleted file mode 100644 index 8b4b0b15..00000000 Binary files a/Source/Images/hd0/s0/u3/Louboutn.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Namida.pt3 b/Source/Images/hd0/s0/u3/Namida.pt3 deleted file mode 100644 index 2a2d8562..00000000 Binary files a/Source/Images/hd0/s0/u3/Namida.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Recoll.pt3 b/Source/Images/hd0/s0/u3/Recoll.pt3 deleted file mode 100644 index 5a2744ab..00000000 Binary files a/Source/Images/hd0/s0/u3/Recoll.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Sanxion.pt3 b/Source/Images/hd0/s0/u3/Sanxion.pt3 deleted file mode 100644 index 25a77aab..00000000 Binary files a/Source/Images/hd0/s0/u3/Sanxion.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Synch.pt3 b/Source/Images/hd0/s0/u3/Synch.pt3 deleted file mode 100644 index 16149d1a..00000000 Binary files a/Source/Images/hd0/s0/u3/Synch.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/ToStar.pt3 b/Source/Images/hd0/s0/u3/ToStar.pt3 deleted file mode 100644 index 9c77ed4f..00000000 Binary files a/Source/Images/hd0/s0/u3/ToStar.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Tune.com b/Source/Images/hd0/s0/u3/Tune.com deleted file mode 100644 index d1a12818..00000000 Binary files a/Source/Images/hd0/s0/u3/Tune.com and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Victory.pt3 b/Source/Images/hd0/s0/u3/Victory.pt3 deleted file mode 100644 index d2c6f3e7..00000000 Binary files a/Source/Images/hd0/s0/u3/Victory.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Wicked.pt3 b/Source/Images/hd0/s0/u3/Wicked.pt3 deleted file mode 100644 index 840a58d1..00000000 Binary files a/Source/Images/hd0/s0/u3/Wicked.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/YeOlde.pt3 b/Source/Images/hd0/s0/u3/YeOlde.pt3 deleted file mode 100644 index ba99371b..00000000 Binary files a/Source/Images/hd0/s0/u3/YeOlde.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s0/u3/Yeovil.pt3 b/Source/Images/hd0/s0/u3/Yeovil.pt3 deleted file mode 100644 index 694de685..00000000 Binary files a/Source/Images/hd0/s0/u3/Yeovil.pt3 and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/CLOCKS.DAT b/Source/Images/hd0/s1/u0/CLOCKS.DAT deleted file mode 100644 index a78c2d6c..00000000 Binary files a/Source/Images/hd0/s1/u0/CLOCKS.DAT and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/CLRDIR.COM b/Source/Images/hd0/s1/u0/CLRDIR.COM deleted file mode 100644 index d1f2a7d6..00000000 Binary files a/Source/Images/hd0/s1/u0/CLRDIR.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/COPY.CFG b/Source/Images/hd0/s1/u0/COPY.CFG deleted file mode 100644 index 3d5310ac..00000000 Binary files a/Source/Images/hd0/s1/u0/COPY.CFG and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/COPY.COM b/Source/Images/hd0/s1/u0/COPY.COM deleted file mode 100644 index 87c0c2fb..00000000 Binary files a/Source/Images/hd0/s1/u0/COPY.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/DATSWEEP.COM b/Source/Images/hd0/s1/u0/DATSWEEP.COM deleted file mode 100644 index 5d298c0b..00000000 Binary files a/Source/Images/hd0/s1/u0/DATSWEEP.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/DDT.COM b/Source/Images/hd0/s1/u0/DDT.COM deleted file mode 100644 index 83f8603f..00000000 Binary files a/Source/Images/hd0/s1/u0/DDT.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/DDTZ.COM b/Source/Images/hd0/s1/u0/DDTZ.COM deleted file mode 100644 index e30a34c0..00000000 Binary files a/Source/Images/hd0/s1/u0/DDTZ.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/DIF.COM b/Source/Images/hd0/s1/u0/DIF.COM deleted file mode 100644 index 87b89d75..00000000 Binary files a/Source/Images/hd0/s1/u0/DIF.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/DSCONFIG.COM b/Source/Images/hd0/s1/u0/DSCONFIG.COM deleted file mode 100644 index b77dd008..00000000 Binary files a/Source/Images/hd0/s1/u0/DSCONFIG.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/FA16.CFG b/Source/Images/hd0/s1/u0/FA16.CFG deleted file mode 100644 index b7974d36..00000000 Binary files a/Source/Images/hd0/s1/u0/FA16.CFG and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/FILEATTR.COM b/Source/Images/hd0/s1/u0/FILEATTR.COM deleted file mode 100644 index 4527d090..00000000 Binary files a/Source/Images/hd0/s1/u0/FILEATTR.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/FILEDATE.CFG b/Source/Images/hd0/s1/u0/FILEDATE.CFG deleted file mode 100644 index da1ca45f..00000000 Binary files a/Source/Images/hd0/s1/u0/FILEDATE.CFG and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/FILEDATE.COM b/Source/Images/hd0/s1/u0/FILEDATE.COM deleted file mode 100644 index 11b83616..00000000 Binary files a/Source/Images/hd0/s1/u0/FILEDATE.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/LDDS.COM b/Source/Images/hd0/s1/u0/LDDS.COM deleted file mode 100644 index 9be1d4a4..00000000 Binary files a/Source/Images/hd0/s1/u0/LDDS.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/LDNZT.COM b/Source/Images/hd0/s1/u0/LDNZT.COM deleted file mode 100644 index 535d8418..00000000 Binary files a/Source/Images/hd0/s1/u0/LDNZT.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/LDP2D.COM b/Source/Images/hd0/s1/u0/LDP2D.COM deleted file mode 100644 index 40220f17..00000000 Binary files a/Source/Images/hd0/s1/u0/LDP2D.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/NULU.COM b/Source/Images/hd0/s1/u0/NULU.COM deleted file mode 100644 index 3d45098a..00000000 Binary files a/Source/Images/hd0/s1/u0/NULU.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/RELOG.COM b/Source/Images/hd0/s1/u0/RELOG.COM deleted file mode 100644 index 13ffc62e..00000000 Binary files a/Source/Images/hd0/s1/u0/RELOG.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/SETTERM.COM b/Source/Images/hd0/s1/u0/SETTERM.COM deleted file mode 100644 index eca19bf9..00000000 Binary files a/Source/Images/hd0/s1/u0/SETTERM.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/SETUPZST.COM b/Source/Images/hd0/s1/u0/SETUPZST.COM deleted file mode 100644 index 35e4b589..00000000 Binary files a/Source/Images/hd0/s1/u0/SETUPZST.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/SID.COM b/Source/Images/hd0/s1/u0/SID.COM deleted file mode 100644 index 3b073ba5..00000000 Binary files a/Source/Images/hd0/s1/u0/SID.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/SUBMIT.COM b/Source/Images/hd0/s1/u0/SUBMIT.COM deleted file mode 100644 index 2e788827..00000000 Binary files a/Source/Images/hd0/s1/u0/SUBMIT.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/SUPERSUB.COM b/Source/Images/hd0/s1/u0/SUPERSUB.COM deleted file mode 100644 index a25d60a6..00000000 Binary files a/Source/Images/hd0/s1/u0/SUPERSUB.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/TD.CFG b/Source/Images/hd0/s1/u0/TD.CFG deleted file mode 100644 index ab44bab9..00000000 Binary files a/Source/Images/hd0/s1/u0/TD.CFG and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/TD.COM b/Source/Images/hd0/s1/u0/TD.COM deleted file mode 100644 index 552aba67..00000000 Binary files a/Source/Images/hd0/s1/u0/TD.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/TERMBASE.DAT b/Source/Images/hd0/s1/u0/TERMBASE.DAT deleted file mode 100644 index 358d61c0..00000000 Binary files a/Source/Images/hd0/s1/u0/TERMBASE.DAT and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/TESTCLOK.COM b/Source/Images/hd0/s1/u0/TESTCLOK.COM deleted file mode 100644 index d547e2b8..00000000 Binary files a/Source/Images/hd0/s1/u0/TESTCLOK.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/UNARC.COM b/Source/Images/hd0/s1/u0/UNARC.COM deleted file mode 100644 index 8cc90746..00000000 Binary files a/Source/Images/hd0/s1/u0/UNARC.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/ZAP.COM b/Source/Images/hd0/s1/u0/ZAP.COM deleted file mode 100644 index 47ffcbb8..00000000 Binary files a/Source/Images/hd0/s1/u0/ZAP.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/ZCAL.COM b/Source/Images/hd0/s1/u0/ZCAL.COM deleted file mode 100644 index a5add241..00000000 Binary files a/Source/Images/hd0/s1/u0/ZCAL.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/ZDE.COM b/Source/Images/hd0/s1/u0/ZDE.COM deleted file mode 100644 index 9bc493c3..00000000 Binary files a/Source/Images/hd0/s1/u0/ZDE.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/ZDE40.COM b/Source/Images/hd0/s1/u0/ZDE40.COM deleted file mode 100644 index 6a2d37de..00000000 Binary files a/Source/Images/hd0/s1/u0/ZDE40.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/ZSVSTAMP.COM b/Source/Images/hd0/s1/u0/ZSVSTAMP.COM deleted file mode 100644 index 026d5d7a..00000000 Binary files a/Source/Images/hd0/s1/u0/ZSVSTAMP.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/ZSVSTAMP.DOC b/Source/Images/hd0/s1/u0/ZSVSTAMP.DOC deleted file mode 100644 index 2a8a7c1e..00000000 --- a/Source/Images/hd0/s1/u0/ZSVSTAMP.DOC +++ /dev/null @@ -1,118 +0,0 @@ - ZSVSTAMP USAGE NOTES - - - -1.0 INTRODUCTION - - ZSVSTAMP is a utility program that saves the create -date/time stamp of a given file in memory. At a later time, -ZSVSTAMP can restore the create stamp to a file. This can be -useful when modifying a file with an editor that actually creates -a new copy of the file rather than modifying it in place. -ZSVSTAMP allows the original creation date of the document to be -retained. - - In order to support all types of date stamping under ZSDOS, -ZSVSTAMP uses the get/set stamp functions of ZSDOS (or ZDDOS) and -thus may only be run under these operating systems. It also -requires ZCPR3 with multiple command line, external FCB and -message buffer. If an attempt is made to run ZSVSTAMP on a -system that does not meet these requirements, the program will -abort with an error message. - - - -2.0 OPERATION - - ZSVSTAMP has two modes of operation -- Manual and Automatic. - - - -2.1 Automatic Mode - - This mode is the most commonly used. It is extremely well -suited for use in alias scripts and can make the whole process of -saving and restoring date stamps virtually transparent to the -user. The syntax is: - - ZSVSTAMP approg [ufn] - -where "approg" is the program, such as an editor, that you wish -to run and "ufn" is an unambiguous file name to be modified by -"approg." When a command of this type is given, ZSVSTAMP saves -"ufn"'s creation date in protected memory. It then causes -"approg" to be run, and when "approg" finishes, ZSVSTAMP is -automatically run again to restore the original create stamp to -"ufn." If "ufn" does not exist, or if the disk does not support -date stamping, ZSVSTAMP displays a warning message and -immediately passes control to "approg" without attempting to save -a stamp. In this situation, ZSVSTAMP is not rerun when "approg" -completes execution. - - Some editors allow a syntax such as - - EDIT oldfile newfile - -When ZSVSTAMP is invoked with more than one parameter following -the application name, no stamps are saved or restored. This -feature can be disabled if desired, (see the section on -customization). - - - -2.2 Manual Mode - - Manual mode can be used to save or restore a file's create -stamp. The syntax is: - - ZSVSTAMP ufn /G or /S - -to Get or Save a file's create stamp, or: - - ZSVSTAMP ufn /P or /R - -to Put or Restore a saved stamp back onto a file. - - - -3.0 CUSTOMIZING ZSVSTAMP - -There are five configuration flags located near the beginning of -the program. Each flag is preceeded by an ASCII string to help -identify its function. These flags may be patched to customize -ZSVSTAMP as desired. - - The first four flags determine whether or not various -warning messages can be displayed when ZSVSTAMP is being run in -Automatic Mode. Setting a flag to 0 disables its associated -message; any other value enables the message. The flags are -labeled "NOSTMP," "NOFILE," "READERR," and "UPDTERR," and they -affect the "Disk has no time/date stamps," "File not found," -"Can't read time/date stamp," and "Can't update time/date stamp" -messages respectively. The program is distributed with all four -messages enabled. Please note that these flags have no effect in -Manual Mode where error messages are always enabled. - - The fifth and final configuration flag is labeled -"REPLALWS." It determines what ZSVSTAMP will do when more than -one parameter follows the application name in an Automatic Mode -command. If the byte following the "REPLALWS" label equals 0, -ZSVSTAMP checks to see if there is more than one parameter -following the application program name. If there is, ZSVSTAMP -simply exits to the application without saving or updating any -stamps. (This is the default setting in the distributed -version.) If the "REPLALWS" flag is non-zero, no such check is -made. - - - -4.0 CONTACTING THE AUTHOR - - Howard Goldstein may be contacted at: - - Newton Centre Z-Node, (Z-Node 3): 617/965-7259 - - Ladera Z-Node, (Z-Node 2): 213/670-9465 - - Home phone, (voice): 203/787-1918 - \ No newline at end of file diff --git a/Source/Images/hd0/s1/u0/ZXD.COM b/Source/Images/hd0/s1/u0/ZXD.COM deleted file mode 100644 index 20395673..00000000 Binary files a/Source/Images/hd0/s1/u0/ZXD.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/ALIAS.CMD b/Source/Images/hd0/s2/u0/ALIAS.CMD deleted file mode 100644 index 4e5e0850..00000000 Binary files a/Source/Images/hd0/s2/u0/ALIAS.CMD and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/ARUNZ.COM b/Source/Images/hd0/s2/u0/ARUNZ.COM deleted file mode 100644 index e36a3be6..00000000 Binary files a/Source/Images/hd0/s2/u0/ARUNZ.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/CMD.COM b/Source/Images/hd0/s2/u0/CMD.COM deleted file mode 100644 index 9ac106c1..00000000 Binary files a/Source/Images/hd0/s2/u0/CMD.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/COMP.COM b/Source/Images/hd0/s2/u0/COMP.COM deleted file mode 100644 index 194c0a97..00000000 Binary files a/Source/Images/hd0/s2/u0/COMP.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/COPY.COM b/Source/Images/hd0/s2/u0/COPY.COM deleted file mode 100644 index 693fa09d..00000000 Binary files a/Source/Images/hd0/s2/u0/COPY.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/CPSET.COM b/Source/Images/hd0/s2/u0/CPSET.COM deleted file mode 100644 index eaf91fa2..00000000 Binary files a/Source/Images/hd0/s2/u0/CPSET.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/CRUNCH.COM b/Source/Images/hd0/s2/u0/CRUNCH.COM deleted file mode 100644 index d3a121a9..00000000 Binary files a/Source/Images/hd0/s2/u0/CRUNCH.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/DFA.COM b/Source/Images/hd0/s2/u0/DFA.COM deleted file mode 100644 index 2b008416..00000000 Binary files a/Source/Images/hd0/s2/u0/DFA.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/DFA.NOT b/Source/Images/hd0/s2/u0/DFA.NOT deleted file mode 100644 index ef50301a..00000000 --- a/Source/Images/hd0/s2/u0/DFA.NOT +++ /dev/null @@ -1,66 +0,0 @@ - Ne÷ Display/Definå Filå Attributeó Program - bù Joå Wright - -Filå  attributeó  arå  maintaineä  bù thå BDOÓ iî  thå  filespeã  portioî  oæ -directorù  entrieó foò eacè filå oî thå disk® Eacè filespeã containó  eleveî -ascié characteró comprisinç thå eighô characteò filenamå anä threå  characteò -type® Ascié characteró requirå onlù seveî oæ thå eighô bitó iî eacè oæ theså -bytes¬  thå  eightè biô ió thereforå availablå foò  assigninç  aî  attribute® -Theså eleveî 'high§ bitó arå referreä tï aó follows: - - f± f² f³ f´ fµ f¶ f· f¸ . t± t² t3 - -CP/Í  assignó t± aó thå Reaä Onlù attributå anä t² aó thå  Systeí  attribute® -MP/Í furtheò assigneä t³ aó thå Archiveä attribute® ZRDOÓ furtheò assignó f¸ -aó  thå Wheeì Protecô attribute® ZSDOÓ assignó f² aó thå  Publiã  attribute® -BackGroundeò  ié useó f± tï inhibiô itó keù-bindinç functioî anä  DateStampeò -useó f³ tï inhibiô stampinç oæ certaiî files® Thå resô arå unassigneä aó faò -aó É kno÷ anä maù bå useä aó yoõ wish. - - - Displaù syntaxº DFÁ [dir:][filespec] - -Displaù  modå takeó á singlå tokeî oò nï tokens® Botè dirº anä filespeã  arå -optional® Defaulô dirº ió thå currenô directorù anä defaulô filespeã ió wilä -(*.*)®   Useò ió giveî á sorteä filå lisô showinç thå  attributeó  associateä -witè each¬ sixteeî aô á time® - -DFÁ Displaù attributeó oæ alì fileó iî thå currenô directory. -DFÁ B2º Displaù foò alì fileó iî directorù B2: -DFÁ *.REÌ Displaù foò alì .REÌ fileó iî thå currenô directory. -DFÁ B2:*.REÌ Displaù foò alì .REÌ fileó iî B2: - - - Definå syntaxº DFÁ [dir:][filspecÝ - -Definitioî  modå  ió  defineä  bù á  seconä  anä  perhapó  subsequenô  tokenó -representinç  thå  variouó  attributes®   Thå tokeî  'f1§  wilì  seô  thå  f± -attributå foò alì filespeã files¬ §-f1§ wilì reseô it® Nameó associateä witè -thå variouó attributeó arå alsï accepteä aó tokens: - - f± oò nkâ Nï Keù Bindinç (BGii) - f² oò puâ Publiã File - f³ oò dsï DateStampeò Off - f´ - fµ - f¶ - f· - f¸ oò whì Wheeì Protected - . - t± or r/ï Reaä Only - t² or syó System - t³ or arã Archived - -Iæ  thå firsô letteò oæ á tokeî ió noô 'f§ oò 't'¬ thaô letteò ió  sufficienô -tï  identifù thå token® 'S'¬ 'sys§ oò 'system§ alì identifù t2® Anù oò  alì Šoæ thå eleveî attributeó maù bå defineä witè á singlå command. - -DFÁ *.TYÐ F± Seô f± oæ alì fileó .TYÐ iî thå currenô directory. -DFÁ A1:*.REÌ F² Ò Seô f² anä t1 oæ alì .REÌ fileó iî A1: -DFÁ *.Z8° -Ò Reseô t± oæ alì .Z8° fileó iî thå currenô directory. -DFÁ Aº -SYÓ Reseô t² oæ alì fileó iî A:¬ currenô user. -DFÁ ROOT:*.COÍ × Seô thå Wheeì attributå (f8© oæ alì .COÍ files - iî thå directorù nameä ROOT: - - - -end- - \ No newline at end of file diff --git a/Source/Images/hd0/s2/u0/DIR.COM b/Source/Images/hd0/s2/u0/DIR.COM deleted file mode 100644 index 84b03395..00000000 Binary files a/Source/Images/hd0/s2/u0/DIR.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/EASE.COM b/Source/Images/hd0/s2/u0/EASE.COM deleted file mode 100644 index 5a95e78e..00000000 Binary files a/Source/Images/hd0/s2/u0/EASE.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/EASECMD.COM b/Source/Images/hd0/s2/u0/EASECMD.COM deleted file mode 100644 index 410848a4..00000000 Binary files a/Source/Images/hd0/s2/u0/EASECMD.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/EDITNDR.COM b/Source/Images/hd0/s2/u0/EDITNDR.COM deleted file mode 100644 index e0c84d5f..00000000 Binary files a/Source/Images/hd0/s2/u0/EDITNDR.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/ERA.COM b/Source/Images/hd0/s2/u0/ERA.COM deleted file mode 100644 index 35f9653f..00000000 Binary files a/Source/Images/hd0/s2/u0/ERA.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/FF.COM b/Source/Images/hd0/s2/u0/FF.COM deleted file mode 100644 index 933b39e3..00000000 Binary files a/Source/Images/hd0/s2/u0/FF.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/FINDF.COM b/Source/Images/hd0/s2/u0/FINDF.COM deleted file mode 100644 index 4a143711..00000000 Binary files a/Source/Images/hd0/s2/u0/FINDF.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/HELP.COM b/Source/Images/hd0/s2/u0/HELP.COM deleted file mode 100644 index 162d3250..00000000 Binary files a/Source/Images/hd0/s2/u0/HELP.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/IF.COM b/Source/Images/hd0/s2/u0/IF.COM deleted file mode 100644 index 1b26ef65..00000000 Binary files a/Source/Images/hd0/s2/u0/IF.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/LDIR.COM b/Source/Images/hd0/s2/u0/LDIR.COM deleted file mode 100644 index 763f2a81..00000000 Binary files a/Source/Images/hd0/s2/u0/LDIR.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/LGET.COM b/Source/Images/hd0/s2/u0/LGET.COM deleted file mode 100644 index 29929e94..00000000 Binary files a/Source/Images/hd0/s2/u0/LGET.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/LPUT.COM b/Source/Images/hd0/s2/u0/LPUT.COM deleted file mode 100644 index 81d9f4ff..00000000 Binary files a/Source/Images/hd0/s2/u0/LPUT.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/LX.COM b/Source/Images/hd0/s2/u0/LX.COM deleted file mode 100644 index 285aea41..00000000 Binary files a/Source/Images/hd0/s2/u0/LX.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/MKZCM.COM b/Source/Images/hd0/s2/u0/MKZCM.COM deleted file mode 100644 index ccf4b11d..00000000 Binary files a/Source/Images/hd0/s2/u0/MKZCM.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/NZCOM.COM b/Source/Images/hd0/s2/u0/NZCOM.COM deleted file mode 100644 index f9d67d16..00000000 Binary files a/Source/Images/hd0/s2/u0/NZCOM.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/NZCOM.LBR b/Source/Images/hd0/s2/u0/NZCOM.LBR deleted file mode 100644 index 2ed5afbd..00000000 Binary files a/Source/Images/hd0/s2/u0/NZCOM.LBR and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/NZCPR.LBR b/Source/Images/hd0/s2/u0/NZCPR.LBR deleted file mode 100644 index 74d412a1..00000000 Binary files a/Source/Images/hd0/s2/u0/NZCPR.LBR and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/NZFCP.LBR b/Source/Images/hd0/s2/u0/NZFCP.LBR deleted file mode 100644 index 9426dc12..00000000 Binary files a/Source/Images/hd0/s2/u0/NZFCP.LBR and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/NZRCP.LBR b/Source/Images/hd0/s2/u0/NZRCP.LBR deleted file mode 100644 index da47328e..00000000 Binary files a/Source/Images/hd0/s2/u0/NZRCP.LBR and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/NZSUB.COM b/Source/Images/hd0/s2/u0/NZSUB.COM deleted file mode 100644 index 2c4c4ba2..00000000 Binary files a/Source/Images/hd0/s2/u0/NZSUB.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/NZSUB.SUB b/Source/Images/hd0/s2/u0/NZSUB.SUB deleted file mode 100644 index d3614775..00000000 --- a/Source/Images/hd0/s2/u0/NZSUB.SUB +++ /dev/null @@ -1,67 +0,0 @@ - -; Batch File: NZSUB.SUB -; Author: Joe Wright -; Date: 8 November 1988 - -; This file demonstrates NZSUB's ability to handle 'formatted' -; input files. The programmer will note the similarity to -; annotated assembly source files. - -; This file will also serve as the documentation of NZSUB's -; features and function. - -; As these lines suggest, any line that begins with a ; is considered -; a full-line comment and is ignored. - -û         Thå  lefô squigglù brackeô '{§ aó  thå  firsô -          characteò  oæ  á  linå  wilì  puô  NZSU   iî -          'comment§  mode® Alì  subsequenô  characteró -          untiì  á  righô  squigglù  brackeô  wilì   bå -          ignored® Checë thió out® } - -; The ; character is usually the command separator for a Z3 -; multiple command line. It is therefore a 'normal' character -; unless it is the first one and may appear freely in the command -; line preceded by anything except a space. - -; Blank lines are ignored in any case. - -{ -Alì oæ thå abovå lineó anä theså lineó arå someho÷ commenteä  ouô -oò  arå blanë anä thereforå wilì noô appeaò iî thå  $$$.SU  filå -whicè wilì bå thå resulô oæ alì this® - -NZSU  ió  completelù CP/Í compatible® Nonetheless¬  iæ  runninç -undeò  Ú-System¬  DUº anä DIRº formó maù bå useä tï  declarå  thå -sourcå file® Thå sourcå filå ió assumeä tï havå typå .SU unlesó -otherwiså declared® Iî anù case¬ $$$.SU wilì bå writteî tï  thå -currenô directory® -} - -» Ok¬ dowî tï business. The main point of all this is to relax -; some of the constraints on command line formatting so that -; a batch file becomes more legible and therefore maintainable. - -; Command lines may be indented with any combination of spaces -; and tabs. A tab within a command line will be converted to -; a space. Spaces between the last command character and the -; in-line comment ';' or ';;' will be removed and the comment -; ignored. The ';;' ZEX form may be used but is unnecessary -; if the ';' is preceded by at least one space (or tab). - -;; -;; NZSUB Demo Program -;; - $; nzsub demonstration ;; Use $; for literal ; - DIR *.COM ;; Let's see what we've got - - echo display source files? (yes or no) - - IF INPUT ; Ask the user (indented) - DIR *.Z80 - FI ; endif -;; -;; End of NZSUB Demo Program -; -; End of NZSUB.SUB - \ No newline at end of file diff --git a/Source/Images/hd0/s2/u0/P.COM b/Source/Images/hd0/s2/u0/P.COM deleted file mode 100644 index b71218b8..00000000 Binary files a/Source/Images/hd0/s2/u0/P.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/PATH.COM b/Source/Images/hd0/s2/u0/PATH.COM deleted file mode 100644 index 89b73ede..00000000 Binary files a/Source/Images/hd0/s2/u0/PATH.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/POKE.COM b/Source/Images/hd0/s2/u0/POKE.COM deleted file mode 100644 index c9d5f763..00000000 Binary files a/Source/Images/hd0/s2/u0/POKE.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/RELEASE.NOT b/Source/Images/hd0/s2/u0/RELEASE.NOT deleted file mode 100644 index 9cdf40fc..00000000 --- a/Source/Images/hd0/s2/u0/RELEASE.NOT +++ /dev/null @@ -1,78 +0,0 @@ - -- RELEASE.NOT -- - - January 20, 1989 - - This file contains last-minute information about NZ-COM. - - ----------- NZ-COM RELEASE 1.2d January 20, 1989 - -NZCOM.COM version 1.2d fixes yet another minor bug by forcing a warm boot -whenever a new DOS module is loaded by itself. The progress report is also -a little less verbose unless declaring the /T(est) option. If the /Q(uiet) -option is declared, NZCOM is now completely silent. - -New versions of SAVE.COM, ERA.COM, REN.COM, P.COM, POKE.COM and their Type -3 counterparts are included in this release. - -NZSUB version 1.0 is released. Unlike SUB.COM, NZSUB is not ZCPR3 specific -and runs as well under CP/M. Its batch files can be formatted and -commented much like those of ZEX4. See NZSUB.SUB for an example. - -TCSELECT version 1.2 can now be run successfully under CP/M. - - ----------- NZ-COM RELEASE 1.2 November 1, 1988 - -NZCOM.COM version 1.2 includes two changes of significance. First, due to -an oversight, when version 1.0 was told to load a new ZCM or ENV file for -which no new modules had to be loaded, it also failed to update data in the -environment, such as printer characteristics, maximum drives and users, and -so on. It will now do so. - -The second change was in the safety checking performed by NZCOM.COM to make -sure that it was being requested to load a version that was consistent with -the underlying CP/M system. This check turned out to be stricter than -necessary and was preventing NZ-COM from running on some systems (e.g., -Lobo Max) that, in fact, could support it quite nicely. - - ----------- NEW FILES: NZCPR.LBR, NZFCP.LBR, NZRCP.LBR - -The main NZCOM.LBR now contains only the standard configurations of the -CPR, FCP, and RCP modules to minimize disk space requirements. Alternative -versions of these modules are now supplied in separate libraries. Modules -can be loaded directly from these libraries, or individual files can be -extracted and put into NZCOM.LBR to replace the default files. Each -library has a brief DOC file describing the modules (one of which is the -default version included in NZCOM.LBR). - - ----------- NEW PROGRAM: ZEX TYPE 4 - -Named ZEX4.COM in this release, this is a new version of ZEX with many -features not seen before (See ZEX4.DOC). This program is brand new and may -well exhibit one or two un-planned 'features'. Please report any problems. - - ----------- EASE PROBLEMS - -There are unfortunately serious defects in the coding of EASE. On many -computers EASE appears to work without problems, on some it crashes -completely, and on others it works but causes other strange behavior to -occur. We tried using it on a Kaypro with the K-ROM (?), and as soon as we -loaded ease we started to get BIOS errors on disk reads. We hope that the -author of EASE (or someone else) will discover and correct these problems. -The program is so nice conceptually. Use it with caution. - - ----------- VLU VERSIONS - -We forgot when writing the manual that VLU, like ZFILER, has two quite -different versions depending on whether your terminal uses reverse or dim -video for highlighting. Therefore, you will find on the release disk VLU- -REV.COM and VLU-DIM.COM. Copy the appropriate one to your working disk -under the name VLU.COM. - - - End of RELEASE.NOT - - \ No newline at end of file diff --git a/Source/Images/hd0/s2/u0/SALIAS.COM b/Source/Images/hd0/s2/u0/SALIAS.COM deleted file mode 100644 index 1b0b13e9..00000000 Binary files a/Source/Images/hd0/s2/u0/SALIAS.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/SAVE.COM b/Source/Images/hd0/s2/u0/SAVE.COM deleted file mode 100644 index 76254cd7..00000000 Binary files a/Source/Images/hd0/s2/u0/SAVE.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/SAVENDR.COM b/Source/Images/hd0/s2/u0/SAVENDR.COM deleted file mode 100644 index 4dcfd14e..00000000 Binary files a/Source/Images/hd0/s2/u0/SAVENDR.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/SHOW.COM b/Source/Images/hd0/s2/u0/SHOW.COM deleted file mode 100644 index 3fbe6069..00000000 Binary files a/Source/Images/hd0/s2/u0/SHOW.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/SHSET.COM b/Source/Images/hd0/s2/u0/SHSET.COM deleted file mode 100644 index a351d9e5..00000000 Binary files a/Source/Images/hd0/s2/u0/SHSET.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/SPOP.COM b/Source/Images/hd0/s2/u0/SPOP.COM deleted file mode 100644 index 471d9383..00000000 Binary files a/Source/Images/hd0/s2/u0/SPOP.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/TCSELECT.COM b/Source/Images/hd0/s2/u0/TCSELECT.COM deleted file mode 100644 index 2fb5f322..00000000 Binary files a/Source/Images/hd0/s2/u0/TCSELECT.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/UNCR.COM b/Source/Images/hd0/s2/u0/UNCR.COM deleted file mode 100644 index 0dfe8947..00000000 Binary files a/Source/Images/hd0/s2/u0/UNCR.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/VARPACK.COM b/Source/Images/hd0/s2/u0/VARPACK.COM deleted file mode 100644 index 5760a8c7..00000000 Binary files a/Source/Images/hd0/s2/u0/VARPACK.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/VIEW.COM b/Source/Images/hd0/s2/u0/VIEW.COM deleted file mode 100644 index d1a17acb..00000000 Binary files a/Source/Images/hd0/s2/u0/VIEW.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/Z3LOC.COM b/Source/Images/hd0/s2/u0/Z3LOC.COM deleted file mode 100644 index 563ad51b..00000000 Binary files a/Source/Images/hd0/s2/u0/Z3LOC.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/Z3TCAP.TCP b/Source/Images/hd0/s2/u0/Z3TCAP.TCP deleted file mode 100644 index 98896073..00000000 Binary files a/Source/Images/hd0/s2/u0/Z3TCAP.TCP and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/ZEX.COM b/Source/Images/hd0/s2/u0/ZEX.COM deleted file mode 100644 index b4cccec4..00000000 Binary files a/Source/Images/hd0/s2/u0/ZEX.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/ZEX.RSX b/Source/Images/hd0/s2/u0/ZEX.RSX deleted file mode 100644 index ef10b0cf..00000000 Binary files a/Source/Images/hd0/s2/u0/ZEX.RSX and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/ZEX4.COM b/Source/Images/hd0/s2/u0/ZEX4.COM deleted file mode 100644 index d6058440..00000000 Binary files a/Source/Images/hd0/s2/u0/ZEX4.COM and /dev/null differ diff --git a/Source/Images/hd0/s2/u0/ZEX4.DOC b/Source/Images/hd0/s2/u0/ZEX4.DOC deleted file mode 100644 index 514f417c..00000000 --- a/Source/Images/hd0/s2/u0/ZEX4.DOC +++ /dev/null @@ -1,329 +0,0 @@ - - ZEØ Typå ´ ö 0.4 - bù Joå Wright - ¹ Noö 88 - -Linå  formattinç  haó beeî furtheò relaxeä bù allowinç  á  singlå -semicoloî ';§ tï denotå á commenô iæ iô ió thå firsô characteò oî -thå  linå  oò  iæ iô ió precedeä bù á  spacå  oò  tab®   Multiplå -Commanä syntaø ERÁ *.BAK;ERÁ *.PRN;DIÒ *.Z8° stilì workó  becauså -thå  characteò prioò tï » ió noô á space® Thå ';;§  ZEØ  commenô -forí  stilì  workó buô ió unnecessary® Iæ » ió requireä  aô  thå -beginninç  oæ á linå oò afteò á space¬ uså thå $»  literaì  form® -Alsï  addeä  ió  thå 'field§ comment®   - -     û Á lefô 'curly§ brackeô '{§ aó thå firsô characteò  oæ -     á particulaò linå wilì causå alì subsequenô  characteró -     anä  lineó  tï  bå treateä  aó  commenô  anä  otherwiså -     ignoreä  untiì  thå correspondinç closinç  brackeô   ió -     encountered® Thió entirå paragrapè woulä bå treateä aó -     á 'field§ comment® } - - - ZEØ Typå ´ ö 0.3 - bù Joå Wright - 2· Ocô 88 - -Thå formidablå Howarä Goldsteiî haó strucë again® Thå  followinç -changeó aô hió suggestion® Thankó Howard. - -1® Thå '¤ § literaì describeä belo÷ ió actuallù implementeä now. - -2®   Thå  senså oæ thå ^¡ aborô controì ió changeä tï  bå  activå -wheî thå flo÷ statå ió TRUÅ ratheò thaî FALSE® Morå logical. - -3®   Thå  ^º Rå-executå controì ió changeä  tï  rå-establisè  thå -defaulô flagó ratheò thaî settinç theí tï 0. - - - ZEØ Typå ´ ö 0.2 - bù Joå Wright - 24 Ocô 88 - -Alì thió starteä becauså É inadvertantlù lefô ZEX.COÍ ouô oæ  thå -originaì  NÚ-COÍ  release®   Afteò  somå  complaintó  abouô  thió -oversight¬ É pickeä uð Jay'ó NZEØ-Ä froí Ú-Nodå Centraì anä trieä -iô out® Althougè admittedlù stilì undeò development¬ NZEØ didn'ô -seeí tï worë right® É theî examineä thå ZEØ 3.² releaså  versioî -anä founä iô eveî worse® Mù buttoî waó pushed. - -Oveò  thå pasô fouò weekó oò sï É havå rå-writteî ZEØ morå tï  mù -liking®   ZEØ ió no÷ á Typå ´ utilitù foò NÚ-COÍ anä  Z3PLUS®   É -havå addeä thå controló Jaù mentionó iî NZEØ-D.DOà anä onå oò twï -oæ mù owî iî thå meantime. - -ZCPR3´  ió  changeä  sï thaô intrinsiã (GET¬  JUMÐ  anä  GO©  anä -residenô (POKE¬ PEEK¬ etc.© commandó caî geô theiò argumentó froí -ZEØ  script® Previously¬ ZCPR3ø turneä ofæ ZEØ  redirectioî  foò -alì  buô transienô (.COM© commands® No÷ Z3´ enableó ZEØ foò  alì -CPR¬  FCÐ anä RCÐ commandó aó welì aó transients®   NZCPR.ZRÌ  iî -thió  packagå ió thå latesô ZCPR3´ anä shoulä bå  'installed§  oî -youò  NÚ-COÍ systeí beforå attemptinç tï ruî Ne÷ ZEØ Typå  ´  anä -itó demonstratioî .ZEØ files. - -Thå  majoò  changå  tï ZEØ waó iî thå  CONST¬  CONIÎ  anä  CONOUÔ Šdepartmentó  sï  thaô ZEØ maù no÷ bå useä tï  'drive§  dBASÅ  II¬ -WordStaò  anä MultiPlan® Theså programó (anä others© attempô  tï -flusè  keyboarä inpuô froí timå tï timå anä reallù gavå  thå  olä -ZEØ á fit® Theså programó ruî correctlù witè ZEØ Typå 4. - -Anotheò probleí witè olä ZEØ waó thaô iô requireä á calì tï CONIÎ -tï dï anything® Manù programó calì CONSÔ tï seå iæ á keù ió dowî -and¬ iæ not¬ gï abouô theiò business® Witè olä ZEX¬ thå ^"ß useò -inpuô  commanä woulä noô gï intï effecô untiì CONIÎ  waó  called® -Thå  ^"ß  commanä haó nï datá tï return® Whaô tï  do¿   Olä  ZEØ -waiteä foò thå useò tï presó á key® Buô whaô iæ hå doesn't¿ Olä -ZEØ simplù hunç uð anä waiteä foò it¬ eveî iæ iô wasn'ô necessarù -foò thå program® Sï mucè foò unattendeä operation¡ - -ZEØ  Typå ´ haó á looë-aheaä featurå tï finä thå ^"ß commanä  anä -executå  iô beforå returninç thå previouó character®   Subsequenô -calló  tï  CONSÔ  yielä  reaì  keyboarä  statuó  (ZEØ  inpuô   ió -suspended)® Wheî ZEØ ió turneä oî agaiî (witè thå 'trigger§ oò á -ne÷ command© iô wilì picë uð witè thå characteò followinç thå ^"ß -commanä  anä continuå normally® Notå thaô almosô  anù  characteò -maù bå useä aó thå Useò Inpuô Triggeò excepô 'space§ anä 'tab'. - -ZCPR3´ anä ZEØ Typå ´ arå verù closelù coupleä anä leavå messageó -tï  eacè other® ZCPR3´ controló ZEØ througè thå ZEXINÐ  flaç  aô -Z3MSG+7®   ZEØ checkó anä maù changå thå ZEXINÐ flaç foò itó  owî -purposeó aó well® ZEØ maintainó pointeró tï thå currenô bytå  oæ -thå  ZEØ  inpuô  strinç anä tï thå beginninç  oæ  thå  strinç  aô -Z3MSG+¹  anä  Z3MSG+11¬ respectively® - -ZEØ  caî alsï controì thå operatioî oæ ZCPR3´ bù manipulatioî  oæ -thå  QUIEÔ  flag®   ZCPR3´ ió assembleä witè  itó  ZEXNOISÅ  (anä -SUBNOISE© equaì 1® Thió meanó thå Z3´ commanä prompô  (A0:BASE>© -maù  bå suppresseä undeò ZEØ (oò SUB© bù settinç thå QUIEÔ  flag® -Thió begó á ne÷ commanä foò ZEØ (Þ-)® - -Iæ thå QUIEÔ flaç ió set¬ ZEØ wilì alsï suppresó thå echï oæ  thå -commanä  linå  froí  Z34®   Further¬  iæ  thå  ^£  (Suppresó  ZEØ -messages©  commanä ió alsï iî force¬ ZEØ anä Z3´  arå  completelù -silenô  anä  thå batcè commandó executå aó iæ froí  thå  multiplå -commanä  linå oò aliaó scripô witè nï extraneouó reportó  tï  thå -console. - -Therå  arå á numbeò oæ 'flag§ optionó whicè telì ZEØ ho÷  tï  acô -whilå executing® - - ^½ XSU Enablå ZEØ inpuô tï COMmand - ^­ QUIEÔ Seô ZCPR³ Quieô flag - ^£ MSUÐ Suppresó ZEØ messages - ^® PSUÐ Suppresó alì Consolå output - ^¦ IPSUÐ Suppresó Console iæ false - -ZEØ  Typå ´ defaultó tï alì flagó (excepô XSUB© OFÆ  anä  inviteó -thå useò tï specifù hió optionó oî thå commanä linå oò tï specifù -theí iî thå batcè filå (Theså flags¬ anä others¬ maù bå 'patched§ -OÎ  witè  ZPATCÈ  oò bù otheò meanó tï customizå ZEØ  iæ  yoõ  sï -desire® Seå PATCHEÓ below.)® - - zeø batcè parm± parm² ^­ ^£ ^. - -ZEØ  Typå ´ wilì picë uð thå twï parameteró anä thå ZEØ  controló -beforå  processinç thå batcè file¬ thuó establishinç  'defaults'® -Iî  thió case¬ Þ­ setó thå QUIEÔ flag¬ ^£ turnó ZEØ messageó  ofæ -anä  ^® suppresseó consolå output® Controló oî thå commanä  taiì -arå processeä firsô anä becomå thå initiaì characteró oæ thå  ZEØ -string®   Thå batcè filå ió theî appendeä tï them® Iî thió  way¬ -thå ^º re-executå controì wilì alsï re-executå them. - -Notå thaô ZEØ Typå ´ treató alì 'flag§ commandó aó 'toggles§ sucè Šthaô thå firsô ^£ wilì suppresó ZEØ messageó anä thå nexô ^£ wilì -turî theí oî again® Á thirä onå wilì turî theí off¬ etc. - -Yoõ  caî  probablù  uså ZEØ Typå ´ witè mosô  oæ  youò  olä  NZEØ -scriptó witè littlå oò nï changå (^Û anä ^Ý controló oæ NZEØ  arå -noô supporteä becauså therå ió nï discerniblå neeä foò them). - -É  havå addeä thå XSU (^=© togglå tï helð controì ZEØ  inpuô  tï -thå  runninç  program®   ^½ anä ^¥ arå  useä  iî  combinatioî  tï -controì  ZEØ input® Notå thaô theù arå complementary®   XSU  OÎ -wilì  allo÷  ZEØ inpuô foò thå remaindeò oæ thå filå  excepô  foò -thoså  commandó whicè begiî witè ^%® Conversely¬ XSU  OFÆ  wilì -turî  ZEØ Inpuô OFÆ afteò eacè commanä excepô foò thoså  precedeä -bù ^%. - -Notå  thaô thå normaì statå oæ ZEØ Inpuô ió ON® ZCPR3´ setó  ZEØ -Statuó tï 0± wheî promptinç anä tï 0° wheî executinç thå COMmand® -Iî  botè cases¬ ZEØ Inpuô ió ON® Iô ió uð tï ZEX¬ anä nï  simplå -matter¬  tï seô ZEØ Statuó tï 0² tï suspenä ZEØ Inpuô aô thå  enä -oæ  thå commanä oò aó thå situatioî maù require® Iô ió safeò  tï -assumå  ZEØ Inpuô OÎ anä turî iô ofæ selectivelù thaî tï turî  iô -OFÆ witè XSUB=° anä assumå thaô iô stayó off® - -Consideò  thaô ZEØ maù executå aî aliaó anä thå programmeò  wantó -Useò  Inpuô  aô  thå  enä oæ itó  execution®   ZEØ  wilì  executå -alias.COÍ  anä  politelù turî itselæ off®   Thå  alias¬  however¬ -causeó Z3´ tï executå yeô anotheò commanä whicè ZEØ knowó nothinç -about®   Z3´  theî turnó ZEØ Inpuô bacë on® Noô whaô  yoõ  mighô -expect®   Foò thió reason¬ É havå defaulteä thå XSU flaç OÎ  foò -mù  purposes®   É havå donå extensivå testinç witè XSU  ofæ  anä -finä thaô iô workó perfectlù welì excepô foò thå caså oæ multiplå -commanä aliaseó anä sucè aó explaineä above® - -ZEØ  Typå ´ startó uð witè ZEØ Inpuô (XSUB© activå  anä  requireó -thå  ZEØ OFÆ commanä ^¥ aô thå beginninç oæ thå linå oò thå  Useò -Inpuô  commanä  ^"ß  tï  suspenä ZEX® Iæ  ZEØ  inpuô  shoulä  bå -suspendeä foò thå entirå command... - - ^%ddô [parms] - -wilì  turî  ZEØ ofæ untiì DDÔ quits® Morå elaboratå  ZEØ  on/ofæ -controló  uså thå Useò Inpuô commanä anä itó trigger®   Therå  ió -onå 'gotcha§ witè thå ^"ß however® Thå Useò Inpuô commanä cannoô -immediatelù  follo÷ á COMmanä invocation® Thió ió  becauså  eveî -thougè ZEØ haó turneä itselæ off¬ thå commanä processoò wilì turî -ZEØ bacë oî whilå executinç thå command® Therå musô bå aô  leasô -onå  interveninç characteò oò controì betweeî thå CÒ  whicè  wilì -executå  thå  commanä anä thå ^"ß whicè wilì suspenä  ZEØ  input® -Consideò thå ZEØ script: - - ddt|d100,17f|^"~g0 - Þ ^ -ZEØ  Typå ´ findó thå Useò Inpuô command¬ executeó iô  anä  moveó -itó  pointeò  pasô iô beforå returninç thå CÒ tï DDT®   Wheî  thå -useò  typeó thå triggeò (þ iî thió case© ZEØ resumeó witè thå  g° -commanä causinç DDÔ tï quit. - - ddt|^"~d100... - Þ ^ -Thió forí wilì noô work® Aó before¬ ZEØ doeó turî oî Useò  Inpuô -buô  Z3´ wilì turî iô ofæ aó iô executeó DDT.COM® Therå musô  bå -aô  leasô onå interveninç character® Knowinç thaô DDÔ useó  BDOÓ -functioî 1° tï reaä itó commandó anä knowinç thaô á backspacå  ió -ignoreä aó thå firsô characteò oæ thå line.. - - ddt|^h^"~d100... - -workó  perfectly® Somå programó (WordStaò foò example©  continuå -testinç  thå  keyboarä  eveî  aó  theù  quit®   Iô  ió  thereforå -necessarù  tï  seô  Useò Inpuô  immediatelù  followinç  thå  quiô -commandó  sï  thaô WordStaò won'ô trù tï reaä  thå  ZEØ  commandó -whilå quitting.. Š - wó file.ws|^qfDear||^"~^kx^" - Þ ^ -Thå firsô UÉ commanä suspendó ZEØ untiì thå useò typeó '~'®   Thå -seconä UÉ suspendó ZEØ untiì WordStaò actuallù quits. - -Commanä  linå  parsinç haó changeä witè respecô tï  thå  TA  anä -SPACÅ characters® Tabó anä Spaceó aô thå beginninç oæ á  commanä -linå  arå  ignored®   Subsequenô tabó arå  converteä  tï  spaces® -Subsequenô  spaceó prioò tï thå ZEØ commenô oò CÒ thaô  endó  thå -linå arå removed® - -Iæ  yoõ musô precedå á commanä witè á Space¬ uså thå ZEØ  literaì -'¤ § tï dï so® Thió allowó morå freedoí tï formaô thå inpuô filå -foò readability® ZEØ Commentó ';;§ caî bå useä oî anù line® Foò -example: - -;» Takå á letteò Misó Jones - - wó $1.ltò ;» Creatå thå letteò file - ^krform.ltò ;» Reaä iî thå standarä form - ^kfDearü ;» Finä thå salutation - -;» Positioî thå cursoò anä starô UÉ untiì '~'¬ resumå ZEØ -;» tï telì WordStaò tï savå thå letteò anä starô UÉ again. - ^qd^"~^kx^" - -;» Enä oæ thió letter - -Wå caî alsï makå logiã flowó morå readablå thió way: - -» Assemblå $± Sourcå Filå ;» Echï froí Z3´ controlleä bù QUIET - - ^-^£ ;» Z3´ Quiet¬ nï ZEØ messages - iæ eø $1.z8° ;» Tesô .Z8° first - ^%z80así $± ;» ZEØ ofæ foò z80asm - else - iæ eø $1.maã »» Theî .MAC - m8° =$1 - elså ;» Assumå .ASM - así $1 - fi - fi - ^­ ;» Cleaò Quiet - -» Enä oæ $± Assembly - -Notå agaiî thaô Z80ASÍ ió onå oæ thoså programó thaô periodicallù -checë  foò keyboarä inpuô sï ZEØ musô bå turneä ofæ untiì  Z80ASÍ -quits® M80.COÍ anä ASM.COÍ don'ô seeí tï asë anything. - -Therå arå generallù threå sourceó foò Consolå Output® - -1® Thå Z3´ prompô anä commanä echo. -2® ZEØ witè itó variouó messages. Š3® Thå runninç program. - -Witè  ZEXNOISÅ eqõ ± (Z34HDR.LIB© ZCPR3´ useó thå QUIEÔ  flaç  tï -determinå  whetheò tï prinô thå commanä prompô beforå  requestinç -thå nexô commanä (QUIEÔ ½ nï prompt)® ZEØ wilì alsï suppresó thå -echï  oæ  thå commanä taiì froí Z3´ iæ QUIET® Thå statå  oæ  thå -QUIEÔ flaç ió toggleä bù thå ne÷ Þ­ ZEØ command® ZEØ Typå ´ wilì -restorå thå QUIEÔ flaç tï itó originaì valuå wheî Done. - -ZEØ  haó á numbeò oæ messageó oæ itó own¬ ZEX:¬ Done¬  etc®   ZEØ -messageó arå controlleä bù thå ^£ (MSUP© toggle® Thå ^£  controì -suppresseó ZEØ messageó untiì thå nexô ^#. - -Anù  Consolå Outpuô maù bå suppresseä durinç á falså  flo÷  statå -witè  thå ^¦ (IPSUP© toggle® Alì Consolå Outpuô  maù  suppresseä -maù bå suppresseä witè thå ^® (PSUP© toggle. - -Iî  variouó  combination¬  theså toggleó  shoulä  givå  thå  .ZEØ -programmeò fulì controì oæ whaô mighô bå printeä oî thå screen. - - ­ PATCHEÓ - - -ZEØ  Typå  ´  haó á patcè areá righô afteò thå  Z3ENÖ  headeò  aô -0200h® Eacè patchablå bytå ió precedeä bù itó namå anä á '>§ foò -easù identification. - - KDEL¾ 05 - CDEL¾ 0F - -Thå reasoî thaô ZEØ caî successfullù 'drive§ programó thaô  woulä -otherwiså  'eat§ thå keyboarä inpuô string¬ ió thaô wå liå tï  iô -abouô   keyboarä  status®   Havinç  delivereä  (oò   printed©   á -character¬  ZEØ wilì reporô CONSÔ falså á numbeò oæ timeó  beforå -shippinç  thå nexô character® Thió allowó 'smart§ programó  likå -dBASÅ IÉ tï thinë theù havå flusheä thå keyboarä oò thaô á  humaî -ió typing. - -KDEÌ  representó  thå numbeò oæ falså  responseó  betweeî  normaì -characteró anä CDEÌ thå numbeò oæ lieó afteò Carriagå Return® Iî -thå schemå oæ things¬ theså valueó arå decrementeä beforå testinç -sï  thaô á valuå oæ ± woulä returî gooä statuó immediatelù anä  á -valuå   oæ  °  woulä  returî  falså  statuó  25µ  timeó   betweeî -characters®   Theså  values¬ µ anä 15¬ werå choseî bù  triaì  anä -erroò  anä seeí tï worë fine® ³ anä ¹ alsï worë aô mù houså  buô -loweò valueó DÏ NOÔ WORË iî manù cases® Sticë witè thå defaults. - -Foò thå followinç flags¬ 0° ió falså anä FÆ ió true. - - XSUB¾ 0° oò FÆ ZEØ Inpuô Modå (FF) ^= - MSUP¾ 0° oò FÆ Suppresó ZEØ messageó ^# - PSUP¾ 0° oò FÆ Suppresó Consolå Outpuô ^. - IPSIP¾ 0° oò FÆ Suppresó Conouô IF falså ^& - QUIET¾ 0° oò FÆ Initiaì Z³ quieô flaç ^- - -Thå  nexô followinç havå tï dï witè findinç thå ZEØ  inpuô  file® ŠIæ  nï  expliciô DIRº ió invoked¬ ZEØ wilì  checë  thå  followinç -flagó tï determinå wherå tï looë foò it. - -ZEØ maù bå forceä tï searcè á specifiã directorù foò inpuô  fileó -bù  declarinç  iô  here®   Ordeò ió  significant®   Iæ  ZEXDÕ  ió -declared¬  nonå  oæ ROOT¬ CURDÕ oò Patè wilì eveî bå  tried®   Iæ -ROOÔ ió set¬ neitheò CURDÕ noò Patè wilì bå tried. - - ZEXDU¾ 0° 0° Nï ZEØ directorù declareä or.. - ZEXDU¾ 0³ 0± Searcè directorù A3º only - - ROOT¾ 0° oò FÆ Searcè Rooô directorù only - CURDU¾ 0° oò FÆ Searcè Currenô directorù only - -Iæ  nonå  oæ thå above¬ ZEØ wilì searcè foò thå  filå  alonç  thå -ZCPR3´  Externaì Path® ZEØ Typå ´ wilì invokå thå  ZCPR3´  Erroò -Handleò appropriatelù foò alì detecteä errors. - - -30- - \ No newline at end of file diff --git a/Source/Makefile b/Source/Makefile new file mode 100644 index 00000000..166bb4c1 --- /dev/null +++ b/Source/Makefile @@ -0,0 +1,15 @@ +# +# order is actually important, because of build dependencies +# +SUBDIRS = Prop +SUBDIRS += Apps +SUBDIRS += CBIOS +SUBDIRS += Forth +SUBDIRS += Fonts +SUBDIRS += CPM22 ZCPR ZCPR-DJ ZSDOS +SUBDIRS += HBIOS CPM3 +SUBDIRS += ZPM3 +SUBDIRS += BPBIOS +SUBDIRS += Images +TOOLS = ../Tools +include $(TOOLS)/Makefile.inc diff --git a/Source/Prop/Makefile b/Source/Prop/Makefile new file mode 100644 index 00000000..ceb364ca --- /dev/null +++ b/Source/Prop/Makefile @@ -0,0 +1,4 @@ +SUBDIRS = Spin +TOOLS = ../../Tools +OTHERS = *.list +include $(TOOLS)/Makefile.inc diff --git a/Source/Prop/ReadMe.txt b/Source/Prop/ReadMe.txt index f22cf1ea..460c4077 100644 --- a/Source/Prop/ReadMe.txt +++ b/Source/Prop/ReadMe.txt @@ -1,12 +1,18 @@ -The Prop directory contains the files used to build the EEPROM -firmware images for the Propeller based boards supported by -RomWBW. +*********************************************************************** +*** *** +*** R o m W B W *** +*** *** +*** Z80/Z180 System Software *** +*** *** +*********************************************************************** -The build process places the resulting ROM image files in the -Binary directory. The firmware images are intended to integrate -with a host CPU board running RomWBW. The following images are -created: +This directory contains the files used to build the EEPROM firmware +images for the Propeller based boards supported by RomWBW. -PropIO.eeprom for use with original PropIO -PropIO2.eeprom for use with PropIO V2 -ParPortProp.eeprom for use with Zeta ParPortProp +The build process places the resulting ROM image files in the Binary +directory. The firmware images are intended to integrate with a host +CPU board running RomWBW. The following images are created: + + - PropIO.eeprom for use with original PropIO + - PropIO2.eeprom for use with PropIO V2 + - ParPortProp.eeprom for use with Zeta ParPortProp diff --git a/Source/Prop/Spin/Makefile b/Source/Prop/Spin/Makefile new file mode 100644 index 00000000..fccfb5fe --- /dev/null +++ b/Source/Prop/Spin/Makefile @@ -0,0 +1,7 @@ +OBJECTS = PropIO.eeprom PropIO2.eeprom ParPortProp.eeprom +OTHERS = *.list +DEST = ../../../Binary +TOOLS = ../../../Tools +include $(TOOLS)/Makefile.inc + +DIFFPATH := $(DIFFTO)/Binary diff --git a/Source/ReadMe.txt b/Source/ReadMe.txt index 81990b2e..1bffa121 100644 --- a/Source/ReadMe.txt +++ b/Source/ReadMe.txt @@ -26,16 +26,19 @@ Thought not necessary, advanced users can easily modify any of the software including the operating systems. A cross-platform approach is used to build the RomWBW firmware. -The software is built using a Microsoft Windows computer, then the -resulting firmware image is programmed into the ROM of your -RetroBrew Computer CPU board. +The software is built using a modern Windows, Linux, or Mac +computer, then the resulting firmware image is programmed into +the ROM of your RetroBrew Computer CPU board. Build System Requirements ------------------------- -All that is required to build the firmware is a computer running -Microsoft Windows and the RomWBW distribution zip archive file. -The zip archive includes all of the required source code +For Linux/Mac computers, refer to the ReadMe.unix file in the +top directory of the distribution. + +For Microsoft Windows computers, All that is required to build the +firmware is the RomWBW distribution zip archive file. The zip +archive package includes all of the required source code (including the operating systems) and the programs required to run the build. @@ -60,17 +63,20 @@ The basic steps to create a custom ROM are: 4) Program the resultant ROM image and try it. -It is *not* necessary to perform steps 1 or 2 before running a -build. In fact, I strongly recommend that you perform steps 3 and -4 initially to make sure that you have no issues building and -programming a ROM that works the same as a pre-built ROM. +Note that steps 1 and 2 are performed to customize your ROM as +desired. If you want to simply build a standard configuration, it is +*not* necessary to perform steps 1 or 2 before running a build. In +fact, I strongly recommend that you skip steps 1 and 2 initially and +just perform perform steps 3 and 4 using the standard configuration to +make sure that you have no issues building and programming a ROM that +works the same as a pre-built ROM. Each of the 4 steps above is described in more detail below. 1. Create/Update Configuration File ----------------------------------- -The options for a build are primarily controled by a configuration +The options for a build are primarily controlled by a configuration file that is included in the build process. In order to customize your settings, it is easiest to make a copy of an existing configuration file and make your changes there. @@ -81,7 +87,7 @@ series of files named _.asm where refers to the CPU board in your system and is used to name the specific configuration so you can maintain multiple configurations. -You will notice that there is initially one configuration file for +You will notice that there is generally one configuration file for each CPU platform with a name of "std". For example, you there is a file called MK4_std.asm. This is the standard ("std") configuration for a Mark IV CPU board. @@ -90,19 +96,23 @@ The platform names are predefined. Refer to the following table to determine the component of the configuration filename: SBC V1/V2 SBC_std.rom + SBC SimH SBC_simh.rom Zeta V1 ZETA_std.rom Zeta V2 ZETA2_std.rom N8 N8_std.rom Mark IV MK4_std.rom - RC2014 RC_std.rom + RC2014 w/ Z80 RCZ80_std.rom + RC2014 w/ Z180 RCZ180_nat.rom (native Z180 memory addressing) + RC2014 w/ Z180 RCZ180_ext.rom (external 512K RAM/ROM module) + SCZ180 SC126, SC130, SC131 + Easy Z80 EZZ180_std.rom + Dyno DYNO_std.rom You can use any name you choose for the component of the configuration filename. So, let's say you want to create a custom ROM for the Mark IV. You would simply copy "MK4_std.asm" to -something like "MK4_cust.asm". - -Now, just edit the new file ("MK4_cust.asm" in this example) as -desired. +something like "MK4_cust.asm". Now, just edit the new file +("MK4_cust.asm" in this example) as desired. You will see that the file already has lines for all of the common options and there is a comment after each option indicating the @@ -138,7 +148,7 @@ These directories are already populated in the distribution. You do not need to do anything unless you want to change the files that are included in the ROM Disk. -In summary, the ROM Disk imbedded in the ROM firmware you build, +In summary, the ROM Disk embedded in the ROM firmware you build, will include the files from the ROM_512KB directory (or the ROM_1024KB directory if building a 1024KB firmware). Additionally, files will be added from the directory associated @@ -147,9 +157,18 @@ with the platform specified in the ROM Build. There is a ReadMe.txt document in the \Source\RomDsk directory with a more detailed description of this process. +Note that the standard 512K ROM disk is absolutely full. So, if +you want to add files to it, you will need to delete other files +to free up some space. + 3. Run the Build Process ------------------------ +This section describes the build process for Microsoft Windows +computers. The build process for Linux/Mac computers is described +in the ReadMe.unix file in the top level directory of the +distribution. + The build involves running commands at the command prompt. Open a command prompt window for the Source directory. If you unzipped the distribution to "C:\", then your command prompt should look @@ -179,7 +198,7 @@ This command will prompt you twice as it runs. These prompts determine the platform and configuration to be built. The first prompt is for the platform, as shown below: - Platform [SBC|ZETA|ZETA2|N8|MK4|UNA]: + Platform [SBC|ZETA|ZETA2|RCZ80|EZZ80|UNA|N8|MK4|RCZ180|SCZ180|DYNO]: Enter the option corresponding to the platform of the ROM firmware you are building. If you enter something other than one of the @@ -226,12 +245,25 @@ used: SD occupies 2191 bytes. HBIOS space remaining: 21434 bytes. +Optionally, you can run one more command that will create the +RomWBW disk images that can be subsequently written to actual +disk media. + + C:\RomWBW\Source> BuildImages + +After running this command, you will find the resultant +disk image file in the Binary directory with names in the +format fd_xxx.img for floppy media or hd_xxx.img for +hard disk media. Refer to the DiskList.txt file in the +Binary directory for more information on using the disk +image files. + 4. Deploy the ROM ----------------- Upon completion of a successful build, you should find the resulting firmware in the Binary directory. These output files -will have names that match the config filename, but will different +will have names that match the config filename, but with different extensions. Three output files will be created for a single BuildROM run: @@ -240,9 +272,6 @@ Three output files will be created for a single BuildROM run: _.com - executable version of the system image that can be copied via X-Modem to a running system to test the build. - _.img - system image that can be written to an - SD/CF Card and loaded via the UNA FS FAT - loader. The actual ROM image is the file ending in .rom. It should be exactly 512KB. Simply burn the .rom image to your ROM and install @@ -263,15 +292,17 @@ For example: C:\RomWBW\Source> BuildROM MK4 cust -In this case, you will not be prompted. This is useful if you -wish to automate your build process. +In this case, you will not be prompted. This is useful if you wish +to automate your build process. There is a third parameter that you can specify to the BuildROM -command via a command line. If you want to build a 1024K (1MB) -ROM, you can add "1024" to the end of the line, like this: +command via a command line. If you want to build a 1024K (1MB) ROM, +you can add "1024" to the end of the line, like this: C:\RomWBW\Source> BuildROM MK4 cust 1024 +You must ensure that your system actually supports a 1024K ROM. + Special Build Commands ---------------------- @@ -292,16 +323,12 @@ BuildImages: RomWBW has the ability to create floppy disk and hard and will turn them into a writable disk image. Refer to the ReadMe.txt document in the Source\Images directory for a detailed description of this process. + N.B., BuildShared must be run prior to BuildImages. BuildBP: This command builds another OS variant called BPBIOS. It is a work in progress and should not be used at this time without contacting Wayne Warthen. -BuildDoc: This command is used to build a new generation of RomWBW - documentation based on LaTeX. This is also a work in - progress and requires LaTeX be installed on your Windows - computer. It is not intended for use at this time. - Example BuildShared Run ----------------------- diff --git a/Source/RomDsk/ROM_1024KB/CLOCKS.DAT b/Source/RomDsk/ROM_1024KB/CLOCKS.DAT new file mode 100644 index 00000000..e64c7e05 Binary files /dev/null and b/Source/RomDsk/ROM_1024KB/CLOCKS.DAT differ diff --git a/Source/RomDsk/ROM_1024KB/CLRDIR.COM b/Source/RomDsk/ROM_1024KB/CLRDIR.COM index d1f2a7d6..c6b05cc9 100644 Binary files a/Source/RomDsk/ROM_1024KB/CLRDIR.COM and b/Source/RomDsk/ROM_1024KB/CLRDIR.COM differ diff --git a/Source/RomDsk/ROM_1024KB/COMPARE.COM b/Source/RomDsk/ROM_1024KB/COMPARE.COM new file mode 100644 index 00000000..29fa41e6 Binary files /dev/null and b/Source/RomDsk/ROM_1024KB/COMPARE.COM differ diff --git a/Source/Images/hd0/s1/u0/COPY.UPD b/Source/RomDsk/ROM_1024KB/COPY.UPD similarity index 100% rename from Source/Images/hd0/s1/u0/COPY.UPD rename to Source/RomDsk/ROM_1024KB/COPY.UPD diff --git a/Source/RomDsk/ROM_1024KB/CR.COM b/Source/RomDsk/ROM_1024KB/CR.COM new file mode 100644 index 00000000..8a824bcc Binary files /dev/null and b/Source/RomDsk/ROM_1024KB/CR.COM differ diff --git a/Source/RomDsk/ROM_1024KB/DDT.COM b/Source/RomDsk/ROM_1024KB/DDT.COM index 83f8603f..70e4ebfe 100644 Binary files a/Source/RomDsk/ROM_1024KB/DDT.COM and b/Source/RomDsk/ROM_1024KB/DDT.COM differ diff --git a/Source/RomDsk/ROM_1024KB/DDTZ.COM b/Source/RomDsk/ROM_1024KB/DDTZ.COM index e30a34c0..4f6eca6b 100644 Binary files a/Source/RomDsk/ROM_1024KB/DDTZ.COM and b/Source/RomDsk/ROM_1024KB/DDTZ.COM differ diff --git a/Source/RomDsk/ROM_1024KB/DDTZ.DOC b/Source/RomDsk/ROM_1024KB/DDTZ.DOC new file mode 100644 index 00000000..e4470528 --- /dev/null +++ b/Source/RomDsk/ROM_1024KB/DDTZ.DOC @@ -0,0 +1,564 @@ + + DDTZ v2.7 + by C.B. Falconer + edited by George A. Havach + +Introduction: +============ +DDTZ v2.7 is a complete replacement for DDT, Digital Research's +famous Dynamic Debugging Tool, with improved functionality, bug +extermination, and full Z80 support. In general, DDTZ is fully +compatible with the original utility, but it has extra and +extended commands and many fewer quirks. All Z80-specific +instructions can be (dis)assembled, though in Intel rather then +Zilog format. Furthermore, DDTZ will correctly trace ('T' and 'U' +commands) both 8080 and Z80 instructions, depending on which CPU +is operating. On startup, the program announces which CPU it is +running on. + +DDTZ v2.7 now handles the 64180 added opcodes. It does NOT test +for a 64180 CPU, since this cannot be done without executing +illegal Z80 instructions, which in turn will crash some +simulators. However v2.7 does not execute any 64180 instructions +internally, only in the subject program. + +This issue supplies the "M" version assembled, to avoid errors +when switching between MSDOS and CPM systems. The command table +is updated accordingly. Most CPM users are also MSDOS users, but +not vice-versa. + +The program is invoked by typing + + ddtz +or + ddtz [d:]filespec + +In the second form, DDTZ will load the specified file into +memory starting at 0100H, unless it's a .HEX file that sets its +own load address. Besides reporting the NEXT free address and +the PC (program counter) after a successful load, DDTZ also shows +the number of memory pages needed for a SAVE. Instead of having +to write all this down, just use the 'X' command at any time to +redisplay these three values for the current application. + +NOTE: loading more code above the NEXT pointer revises these + values. + +As in DDT, when a program is loaded above the area holding the +'A' and 'U' (and now 'W') command code, these commands are +disabled, and the extra memory is released to the user. Thus, +DDTZ can occupy as little as 3K total memory space. Unlike DDT, +however, DDTZ will not overwrite itself or the system on program +loads (except .HEX files). + +At initialization, the stack pointer (SP) points to a return to +DDTZ, just like for the CCP. Thus, programs that normally return +to the CCP will be returned to DDTZ. The 'B' command +reinitializes this condition. + + +The intercept vector copies the BDOS version number, etc., so +an object program does not know that DDTZ is running (except +for BIOS-BDOS vector size). Thus, programs that check the version +number should execute correctly under DDTZ. + +All input parameters can now be entered in any of three formats: + + (1) hexadecimal (as in DDT), + (2) decimal, by adding a leading '#' character, + (3) ASCII, by enclosing between either single or double + quotes; either one or two characters are allowed. + +Leading blanks in command lines and parameters are absorbed. +Either a comma or a (single) space is a valid delimiter. +Either uppercase or lowercase input is accepted. + +The default command (for anything not otherwise recognizable) +is 'H'. This allows convenient calculation, along with the other +features described below. So, to convert a number, just enter +it! + +As in DDT, the prompt character is '-', and the only error +message is the query ('?'), which generally kicks you back to +command mode. + +New Commands (Over DDT): +======================= + +NOTE: letters in parenthesis, e.g. "(U)", show the equivalent + command for DDTZM version (compatible with MSDOS debug). + + @ Sets or shows (with no parameter) the internally stored + "base" value. Also used with the 'S' and 'D' commands as + an optional parameter (though without the '@') to display + memory from an arbitrary base marker (offset). When set to + zero (the default), it does not affect any screen displays. + + B B)egin: resets the USER stack pointer to its initial value, + such that any program that exits by an RET will return to + DDTZ. DDTZ provides a default stack space of + approximately 24 bytes for user programs. + + C C)ompare first_address,last_address,against_address: shows + all the byte differences between two memory areas, in the + format + + XXXX aa YYYY bb + + where XXXX and YYYY are the comparative memory addresses, + and aa and bb are the corresponding byte values. Can be + used to verify the identity of two files by first + loading them into different memory areas with the 'R' + command (see below). + + + W Write: stores the modified memory area to disk under the + (K) filename specified by the 'I' command, overwriting the + original file from which it was loaded (the user is queried + before doing so). By default, the image of memory from + 0100H through the "NEXT" value -1 is saved. "K first_addr, + last_address" overrides this and allows writing ANY memory + area to a file. Almost a necessity for CPM 3.0 (no SAVE!). + K)eep on DDTZ + + X eXamine: redisplays the "NEXT PC SAVE" report at any time. + (Q) Q)uery size on DDTZ. + + S S)earch first_address, last_addr, value: searches the + (W) specified memory area for the value (a 16-bit word, not a + byte) and shows the locations of all such. Very useful for + finding CALL's or JMP's to a particular address, etc. + W)here on DDTZ + + Y Y)our_option parm1,parm2,address: executes an arbitrary + routine at the specified address, with the BC and DE + registers set to parm1 and parm2, respectively. + + Z Displays (but does not alter) the Z80's alternate register + set, including the index registers (disabled if running on + an 8080). On Z80's, automatically included as the last + part of the display by the 'X' command. + + +Based (Offset) Displays: +======================= + +The 'D' and 'E' commands can use a stored base value (offset), +as set by the '@' command. The current @ value may be +overridden for a single execution of these commands by adding the +base as an extra parameter in the command line. The effect is +to add this value to the first/last address and display +accordingly. The address listing on the left becomes XXXX:YYYY, +where XXXX is the offset address and YYYY is the actual memory +address being displayed. For example, if you have a data area +located at 42B7H and wish to preserve easy access, just enter +"@42b7". Now, "d0,3f" will dump memory starting at 4237H. + + +Further Changes from DDT: +======================== + + A A)ssemble now accepts the full Z80 as well as 8080 + instruction set, although it expects them in Intel rather + than Zilog format (see notes below under the 'L' + command). When in doubt, see the mnemnonic list below. + + D D)isplay or D)ump will accept an optional third parameter + to set the base value for a single execution only. Format + has been cleaned up. + + H H)ex_arithmetic on two values also shows their + difference in decimal. With only one value, converts to + hexadecimal, decimal, and ASCII (low-order byte only). + + + N N)ame now allows drive specification (d:...) and sets up + (I) the complete command line, including both FCB's (at + addresses 005CH and 006CH). The tail (stored at 0081H up) + is NOT upshifted. + I)nput on DDTZ + + U U)nassemble now displays the raw hexcode, especially handy + (L) when examining non-code areas. Intel (8080 style) mnemonics + are used, so some disassembled instructions may look + strange. E.g., the Z80's 'IN B,(C)' and 'OUT (C),B' become + 'INP B' and 'OUTP B', respectively; 'LD (nnnn),BC' becomes + 'SBCD nnnn', 'ADD IX, BC' becomes 'DADX B', and 'JP (IX)' + becomes 'PCIX'. + L)ist on DDTZ + + L L)oad now permits loading a file into memory with an + (R) offset, which is added to the default load address of + 0100H. When reading in a .HEX file with a preset bias, + the 'R' command will not transfer control to an invalid + execution point. Another execution of the 'R' command will + reread the input file, e.g.: + + n blah + l + ...modify the code and generally mess about... + l + + The original file is reloaded, and the modifications are + removed. + R)ead on DDTZ + + E E)nter, like D)isplay, now accepts an optional second + (S) parameter to set the base value for a single execution + only. + S)ubstitute or S)et on DDTZ + + T T)rap/trace on termination now shows the complete CPU + state. Traps and traces no longer lock up when a user RST + 7 instruction is executed. Tracing of BDOS/BIOS calls is + heavily trun cated, avoiding clutter and preventing system + crashes. + +NOTE: Most of the UNDOCUMENTED Z80 op-codes are handled. Others + can crash the system. + + R R)egisters also shows what two-byte values the HL and SP + (X) registers are actually pointing to. On Z80's, displays the + alternate register set. + eX)amine on DDTZ + +NOTE: Any use of the 'W' or 'L' command resets the system DMA + transfer address to the standard default value of 0080H. + + +; This is the output of DDTZ when disassembling OPTYPE.TRY +NOP LDA 06A4 MOV M,H +LXI B,06A4 DCX SP MOV M,L +STAX B INR A HLT +INX B DCR A MOV M,A +INR B MVI A,20 MOV A,B +DCR B CMC MOV A,C +MVI B,20 MOV B,B MOV A,D +RLC MOV B,C MOV A,E +EXAF MOV B,D MOV A,H +DAD B MOV B,E MOV A,L +LDAX B MOV B,H MOV A,M +DCX B MOV B,L MOV A,A +INR C MOV B,M ADD B +DCR C MOV B,A ADD C +MVI C,20 MOV C,B ADD D +RRC MOV C,C ADD E +DJNZ 0134 MOV C,D ADD H +LXI D,06A4 MOV C,E ADD L +STAX D MOV C,H ADD M +INX D MOV C,L ADD A +INR D MOV C,M ADC B +DCR D MOV C,A ADC C +MVI D,20 MOV D,B ADC D +RAL MOV D,C ADC E +JR 0134 MOV D,D ADC H +DAD D MOV D,E ADC L +LDAX D MOV D,H ADC M +DCX D MOV D,L ADC A +INR E MOV D,M SUB B +DCR E MOV D,A SUB C +MVI E,20 MOV E,B SUB D +RAR MOV E,C SUB E +JRNZ 0134 MOV E,D SUB H +LXI H,06A4 MOV E,E SUB L +SHLD 06A4 MOV E,H SUB M +INX H MOV E,L SUB A +INR H MOV E,M SBB B +DCR H MOV E,A SBB C +MVI H,20 MOV H,B SBB D +DAA MOV H,C SBB E +JRZ 0134 MOV H,D SBB H +DAD H MOV H,E SBB L +LHLD 06A4 MOV H,H SBB M +DCX H MOV H,L SBB A +INR L MOV H,M ANA B +DCR L MOV H,A ANA C +MVI L,20 MOV L,B ANA D +CMA MOV L,C ANA E +JRNC 0134 MOV L,D ANA H +LXI SP,06A4 MOV L,E ANA L +STA 06A4 MOV L,H ANA M +INX SP MOV L,L ANA A +INR M MOV L,M XRA B +DCR M MOV L,A XRA C +MVI M,20 MOV M,B XRA D +STC MOV M,C XRA E +JRC 0134 MOV M,D XRA H +DAD SP MOV M,E XRA L + + +XRA M JPE 06A4 SLAR M +XRA A XCHG SLAR A +ORA B CPE 06A4 SRAR B +ORA C XRI 20 SRAR C +ORA D RST 5 SRAR D +ORA E RP SRAR E +ORA H POP PSW SRAR H +ORA L JP 06A4 SRAR L +ORA M DI SRAR M +ORA A CP 06A4 SRAR A +CMP B PUSH PSW SLLR B +CMP C ORI 20 SLLR C +CMP D RST 6 SLLR D +CMP E RM SLLR E +CMP H SPHL SLLR H +CMP L JM 06A4 SLLR L +CMP M EI SLLR M +CMP A CM 06A4 SLLR A +RNZ CPI 20 SRLR B +POP B RST 7 SRLR C +JNZ 06A4 RLCR B SRLR D +JMP 06A4 RLCR C SRLR E +CNZ 06A4 RLCR D SRLR H +PUSH B RLCR E SRLR L +ADI 20 RLCR H SRLR M +RST 0 RLCR L SRLR A +RZ RLCR M BIT 0,B +RET RLCR A BIT 0,C +JZ 06A4 RRCR B BIT 0,D +CZ 06A4 RRCR C BIT 0,E +CALL 06A4 RRCR D BIT 0,H +ACI 20 RRCR E BIT 0,L +RST 1 RRCR H BIT 0,M +RNC RRCR L BIT 0,A +POP D RRCR M BIT 1,B +JNC 06A4 RRCR A BIT 1,C +OUT 20 RALR B BIT 1,D +CNC 06A4 RALR C BIT 1,E +PUSH D RALR D BIT 1,H +SUI 20 RALR E BIT 1,L +RST 2 RALR H BIT 1,M +RC RALR L BIT 1,A +EXX RALR M BIT 2,B +JC 06A4 RALR A BIT 2,C +IN 20 RARR B BIT 2,D +CC 06A4 RARR C BIT 2,E +SBI 20 RARR D BIT 2,H +RST 3 RARR E BIT 2,L +RPO RARR H BIT 2,M +POP H RARR L BIT 2,A +JPO 06A4 RARR M BIT 3,B +XTHL RARR A BIT 3,C +CPO 06A4 SLAR B BIT 3,D +PUSH H SLAR C BIT 3,E +ANI 20 SLAR D BIT 3,H +RST 4 SLAR E BIT 3,L +RPE SLAR H BIT 3,M +PCHL SLAR L BIT 3,A + + +BIT 4,B RES 3,D SET 2,H +BIT 4,C RES 3,E SET 2,L +BIT 4,D RES 3,H SET 2,M +BIT 4,E RES 3,L SET 2,A +BIT 4,H RES 3,M SET 3,B +BIT 4,L RES 3,A SET 3,C +BIT 4,M RES 4,B SET 3,D +BIT 4,A RES 4,C SET 3,E +BIT 5,B RES 4,D SET 3,H +BIT 5,C RES 4,E SET 3,L +BIT 5,D RES 4,H SET 3,M +BIT 5,E RES 4,L SET 3,A +BIT 5,H RES 4,M SET 4,B +BIT 5,L RES 4,A SET 4,C +BIT 5,M RES 5,B SET 4,D +BIT 5,A RES 5,C SET 4,E +BIT 6,B RES 5,D SET 4,H +BIT 6,C RES 5,E SET 4,L +BIT 6,D RES 5,H SET 4,M +BIT 6,E RES 5,L SET 4,A +BIT 6,H RES 5,M SET 5,B +BIT 6,L RES 5,A SET 5,C +BIT 6,M RES 6,B SET 5,D +BIT 6,A RES 6,C SET 5,E +BIT 7,B RES 6,D SET 5,H +BIT 7,C RES 6,E SET 5,L +BIT 7,D RES 6,H SET 5,M +BIT 7,E RES 6,L SET 5,A +BIT 7,H RES 6,M SET 6,B +BIT 7,L RES 6,A SET 6,C +BIT 7,M RES 7,B SET 6,D +BIT 7,A RES 7,C SET 6,E +RES 0,B RES 7,D SET 6,H +RES 0,C RES 7,E SET 6,L +RES 0,D RES 7,H SET 6,M +RES 0,E RES 7,L SET 6,A +RES 0,H RES 7,M SET 7,B +RES 0,L RES 7,A SET 7,C +RES 0,M SET 0,B SET 7,D +RES 0,A SET 0,C SET 7,E +RES 1,B SET 0,D SET 7,H +RES 1,C SET 0,E SET 7,L +RES 1,D SET 0,H SET 7,M +RES 1,E SET 0,L SET 7,A +RES 1,H SET 0,M DADX B +RES 1,L SET 0,A DADX D +RES 1,M SET 1,B LXI X,06A4 +RES 1,A SET 1,C SIXD 06A4 +RES 2,B SET 1,D INX X +RES 2,C SET 1,E DADX X +RES 2,D SET 1,H LIXD 06A4 +RES 2,E SET 1,L DCX X +RES 2,H SET 1,M INR [X+05] +RES 2,L SET 1,A DCR [X+05] +RES 2,M SET 2,B MVI [X+05],20 +RES 2,A SET 2,C DADX SP +RES 3,B SET 2,D MOV B,[X+05] +RES 3,C SET 2,E MOV C,[X+05] + + +MOV D,[X+05] DSBC B DADY B +MOV E,[X+05] SBCD 06A4 DADY D +MOV H,[X+05] NEG LXI Y,06A4 +MOV L,[X+05] RETN SIYD 06A4 +MOV [X+05],B IM0 INX Y +MOV [X+05],C LDIA DADY Y +MOV [X+05],D INP C LIYD 06A4 +MOV [X+05],E OUTP C DCX Y +MOV [X+05],H DADC B INR [Y+05] +MOV [X+05],L LBCD 06A4 DCR [Y+05] +MOV [X+05],A RETI MVI [Y+05],2 +MOV A,[X+05] LDRA DADY SP +ADD [X+05] INP D MOV B,[Y+05] +ADC [X+05] OUTP D MOV C,[Y+05] +SUB [X+05] DSBC D MOV D,[Y+05] +SBB [X+05] SDED 06A4 MOV E,[Y+05] +ANA [X+05] IM1 MOV H,[Y+05] +XRA [X+05] LDAI MOV L,[Y+05] +ORA [X+05] INP E MOV [Y+05],B +CMP [X+05] OUTP E MOV [Y+05],C +POP X DADC D MOV [Y+05],D +XTIX LDED 06A4 MOV [Y+05],E +PUSH X IM2 MOV [Y+05],H +PCIX LDAR MOV [Y+05],L +SPIX INP H MOV [Y+05],A +RLCR [X+05] OUTP H MOV A,[Y+05] +RRCR [X+05] DSBC H ADD [Y+05] +RALR [X+05] shld 06A4 ADC [Y+05] +RARR [X+05] RRD SUB [Y+05] +SLAR [X+05] INP L SBB [Y+05] +SRAR [X+05] OUTP L ANA [Y+05] +SRLR [X+05] DADC H XRA [Y+05] +BIT 0,[X+05] lhld 06A4 ORA [Y+05] +BIT 1,[X+05] RLD CMP [Y+05] +BIT 2,[X+05] INP M POP Y +BIT 3,[X+05] OUTP M XTIY +BIT 4,[X+05] DSBC SP PUSH Y +BIT 5,[X+05] SSPD 06A4 PCIY +BIT 6,[X+05] INP A SPIY +BIT 7,[X+05] OUTP A RLCR [Y+05] +RES 0,[X+05] DADC SP RRCR [Y+05] +RES 1,[X+05] LSPD 06A4 RALR [Y+05] +RES 2,[X+05] LDI RARR [Y+05] +RES 3,[X+05] CCI SLAR [Y+05] +RES 4,[X+05] INI SRAR [Y+05] +RES 5,[X+05] OTI SRLR [Y+05] +RES 6,[X+05] LDD BIT 0,[Y+05] +RES 7,[X+05] CCD BIT 1,[Y+05] +SET 0,[X+05] IND BIT 2,[Y+05] +SET 1,[X+05] OTD BIT 3,[Y+05] +SET 2,[X+05] LDIR BIT 4,[Y+05] +SET 3,[X+05] CCIR BIT 5,[Y+05] +SET 4,[X+05] INIR BIT 6,[Y+05] +SET 5,[X+05] OTIR BIT 7,[Y+05] +SET 6,[X+05] LDDR RES 0,[Y+05] +SET 7,[X+05] CCDR RES 1,[Y+05] +INP B INDR RES 2,[Y+05] +OUTP B OTDR RES 3,[Y+05] + + +RES 4,[Y+05] SET 0,[Y+05] SET 4,[Y+05] +RES 5,[Y+05] SET 1,[Y+05] SET 5,[Y+05] +RES 6,[Y+05] SET 2,[Y+05] SET 6,[Y+05] +RES 7,[Y+05] SET 3,[Y+05] SET 7,[Y+05] + +; These are the result of disassembling 64180OPS.TRY +; These opcodes are available ONLY on the 64180 CPU +; DDTZ will both assemble and disassemble these. +IN0 B,20 TST E MLT B +OUT0 20,B IN0 H,20 MLT D +TST B OUT0 20,H TSTI 20 +IN0 C,20 TST H MLT H +OUT0 20,C IN0 L,20 TSIO 20 +TST C OUT0 20,L SLP +IN0 D,20 TST L MLT SP +OUT0 20,D TST M OTIM +TST D IN0 A,20 OTDM +IN0 E,20 OUT0 20,A OIMR +OUT0 20,E TST A ODMR + +; The following are UNDOCUMENTED z80 opcodes from XTDOPS.TRY. +; DDTZ will disassemble these, but will not assemble them. +; They use xh/xl (or yh/yl) as separate byte registers. +; Use these at your own risk. +INRX H ACXR H MOVY H,B +DCRX H ACXR L MOVY H,C +MVIX H,20 SUXR H MOVY H,D +INRX L SUXR L MOVY H,E +DCRX L SBXR H MOVY H,A +MVIX L,20 SBXR L MOVY L,B +MOVX B,H NDXR H MOVY L,C +MOVX B,L NDXR L MOVY L,D +MOVX C,H XRXR H MOVY L,E +MOVX C,L XRXR L MOVY L,A +MOVX D,H ORXR H MOVY A,H +MOVX D,L ORXR L MOVY A,L +MOVX E,H CPXR H ADYR H +MOVX E,L CPXR L ADYR L +MOVX H,B INRY H ACYR H +MOVX H,C DCRY H ACYR L +MOVX H,D MVIY H,20 SUYR H +MOVX H,E INRY L SUYR L +MOVX H,A DCRY L SBYR H +MOVX L,B MVIY L,20 SBYR L +MOVX L,C MOVY B,H NDYR H +MOVX L,D MOVY B,L NDYR L +MOVX L,E MOVY C,H XRYR H +MOVX L,A MOVY C,L XRYR L +MOVX A,H MOVY D,H ORYR H +MOVX A,L MOVY D,L ORYR L +ADXR H MOVY E,H CPYR H +ADXR L MOVY E,L CPYR L + + +Command Summary: +=============== + +DDTZM command DDTZ command +============= ============ +@ (base) +A)ssemble first_address A +B)egin {i.e., initialize stack and return} B +C)ompare first_address,last_address,against_address C +D)ump first_address[,last_address[,base]] D +E)nter_in_memory first_address[,base] S)ubstitute +F)ill first_address,last_address,value F +G)o_to [address][,trap1[,trap2]] G +H)ex_arithmetic value1(,value2) H +L)oad_file (offset) R)ead +M)ove first_address,last_address,destination M +N)nput FCBs_command_line I)nput +Q)uit (not avail) +R)egister examine/change [register|flag] X)amine +S)earch first_address,last_address,word W)hereis +T)race_execution [count] T + Untrace_execution [count] (i.e. do count instr) U)ntrace +U)nassemble_code first_address[,last_address] L)ist code +W)rite [first_address,last_address] K)eep +X)amine {i.e. display memory parameters for application} Q)uery +Y)our_option BC:=parm1,DE:=parm2,call_address Y +Z)80_register_display Z + + +If you find this program useful, contributions will be gratefully +accepted and will encourage further development and release of +useful CPM programs. My practice is to include source. + +C.B. Falconer +680 Hartford Turnpike, +Hamden, Conn. 06517 (203) 281-1438 + +DDTZ and its associated documentation and other files are +copyright (c) 1980-1988 by C.B. Falconer. They may be freely +copied and used for non-commercial purposes ONLY. + diff --git a/Source/RomDsk/ROM_1024KB/DIF.COM b/Source/RomDsk/ROM_1024KB/DIF.COM deleted file mode 100644 index 87b89d75..00000000 Binary files a/Source/RomDsk/ROM_1024KB/DIF.COM and /dev/null differ diff --git a/Source/Images/hd0/s1/u0/FA16.DOC b/Source/RomDsk/ROM_1024KB/FA16.DOC similarity index 100% rename from Source/Images/hd0/s1/u0/FA16.DOC rename to Source/RomDsk/ROM_1024KB/FA16.DOC diff --git a/Source/Images/hd0/s1/u0/FA16A.FOR b/Source/RomDsk/ROM_1024KB/FA16A.FOR similarity index 100% rename from Source/Images/hd0/s1/u0/FA16A.FOR rename to Source/RomDsk/ROM_1024KB/FA16A.FOR diff --git a/Source/Images/hd0/s1/u0/FA16CFG.TXT b/Source/RomDsk/ROM_1024KB/FA16CFG.TXT similarity index 100% rename from Source/Images/hd0/s1/u0/FA16CFG.TXT rename to Source/RomDsk/ROM_1024KB/FA16CFG.TXT diff --git a/Source/RomDsk/ROM_1024KB/FAT.COM b/Source/RomDsk/ROM_1024KB/FAT.COM new file mode 100644 index 00000000..1dd7dcca Binary files /dev/null and b/Source/RomDsk/ROM_1024KB/FAT.COM differ diff --git a/Source/RomDsk/ROM_1024KB/FDISK80.COM b/Source/RomDsk/ROM_1024KB/FDISK80.COM index 00ac10f7..0d73cef2 100644 Binary files a/Source/RomDsk/ROM_1024KB/FDISK80.COM and b/Source/RomDsk/ROM_1024KB/FDISK80.COM differ diff --git a/Source/RomDsk/ROM_1024KB/LDDS.COM b/Source/RomDsk/ROM_1024KB/LDDS.COM index 9be1d4a4..357f1360 100644 Binary files a/Source/RomDsk/ROM_1024KB/LDDS.COM and b/Source/RomDsk/ROM_1024KB/LDDS.COM differ diff --git a/Source/RomDsk/ROM_1024KB/LDNZT.COM b/Source/RomDsk/ROM_1024KB/LDNZT.COM new file mode 100644 index 00000000..87cf0b47 Binary files /dev/null and b/Source/RomDsk/ROM_1024KB/LDNZT.COM differ diff --git a/Source/RomDsk/ROM_1024KB/LDP2D.COM b/Source/RomDsk/ROM_1024KB/LDP2D.COM index 40220f17..a89e03d8 100644 Binary files a/Source/RomDsk/ROM_1024KB/LDP2D.COM and b/Source/RomDsk/ROM_1024KB/LDP2D.COM differ diff --git a/Source/RomDsk/ROM_1024KB/MAC.COM b/Source/RomDsk/ROM_1024KB/MAC.COM new file mode 100644 index 00000000..f49e835a Binary files /dev/null and b/Source/RomDsk/ROM_1024KB/MAC.COM differ diff --git a/Source/RomDsk/ROM_1024KB/NULU.COM b/Source/RomDsk/ROM_1024KB/NULU.COM index 3d45098a..fc5594b1 100644 Binary files a/Source/RomDsk/ROM_1024KB/NULU.COM and b/Source/RomDsk/ROM_1024KB/NULU.COM differ diff --git a/Source/RomDsk/ROM_1024KB/PMARC.COM b/Source/RomDsk/ROM_1024KB/PMARC.COM new file mode 100644 index 00000000..59bd3ef3 Binary files /dev/null and b/Source/RomDsk/ROM_1024KB/PMARC.COM differ diff --git a/Source/RomDsk/ROM_1024KB/PMEXT.COM b/Source/RomDsk/ROM_1024KB/PMEXT.COM new file mode 100644 index 00000000..d3a51ca3 Binary files /dev/null and b/Source/RomDsk/ROM_1024KB/PMEXT.COM differ diff --git a/Source/Images/hd0/s1/u0/PUTBG.COM b/Source/RomDsk/ROM_1024KB/PUTBG.COM similarity index 100% rename from Source/Images/hd0/s1/u0/PUTBG.COM rename to Source/RomDsk/ROM_1024KB/PUTBG.COM diff --git a/Source/RomDsk/ROM_1024KB/SID.COM b/Source/RomDsk/ROM_1024KB/SID.COM deleted file mode 100644 index 3b073ba5..00000000 Binary files a/Source/RomDsk/ROM_1024KB/SID.COM and /dev/null differ diff --git a/Source/RomDsk/ROM_1024KB/STAMPS.DAT b/Source/RomDsk/ROM_1024KB/STAMPS.DAT index 23cd9bd7..a312ae58 100644 Binary files a/Source/RomDsk/ROM_1024KB/STAMPS.DAT and b/Source/RomDsk/ROM_1024KB/STAMPS.DAT differ diff --git a/Source/RomDsk/ROM_1024KB/UNCR.COM b/Source/RomDsk/ROM_1024KB/UNCR.COM new file mode 100644 index 00000000..42385ddd Binary files /dev/null and b/Source/RomDsk/ROM_1024KB/UNCR.COM differ diff --git a/Source/RomDsk/ROM_1024KB/VIDATT.Z80 b/Source/RomDsk/ROM_1024KB/VIDATT.Z80 deleted file mode 100644 index 073bb84f..00000000 --- a/Source/RomDsk/ROM_1024KB/VIDATT.Z80 +++ /dev/null @@ -1,69 +0,0 @@ - title WordStar 4.0 Video Attributes Driver - -strngout equ 0283h - -esc equ 1bh -dim equ 1 -blink equ 2 -invert equ 4 -uline equ 8 - - aseg - org 03c1h - -vidatt: - xor a - ld hl,funtbl - ld b,8 -getloop: - rr c - jr nc,getnext - or a,(hl) -getnext: - inc hl - djnz getloop -; - ld hl,string+2 ; attribute #1 on/off indicator - push hl - ld b,4 - ld de,'?!' ; ? = attribute off, ! = attribute on -setloop: - rra - jr nc,attroff - ld (hl),e ; attribute on - jr setnext -attroff: - ld (hl),d ; attribute off -setnext: - inc hl - inc hl - inc hl ; advance to next on/off indicator - djnz setloop -; - pop hl ; hl --> dim on/off - ld a,d ; attribute off - cp (hl) ; dim off? - jr nz,setdim - ld a,e ; attribute on -setdim: - ld (hl),a - ld hl,string - jp strngout ; ws string routine -; -; -funtbl: - defb dim ; strike out - defb invert or blink ; warnings & errors - defb invert ; block - defb uline ; underline - defb blink ; subscript - defb blink or uline ; superscript - defb invert ; menu, headline, bold, double - defb invert or uline ; italics, RET, backspace -; -string: - defb 12,esc,' 2',esc,' 3',esc,' 4',esc,' 5' -; -finis equ $ - end - \ No newline at end of file diff --git a/Source/RomDsk/ROM_1024KB/WS.COM b/Source/RomDsk/ROM_1024KB/WS.COM deleted file mode 100644 index aa028bc3..00000000 Binary files a/Source/RomDsk/ROM_1024KB/WS.COM and /dev/null differ diff --git a/Source/RomDsk/ROM_1024KB/WS.OVR b/Source/RomDsk/ROM_1024KB/WS.OVR deleted file mode 100644 index 5e3c8773..00000000 Binary files a/Source/RomDsk/ROM_1024KB/WS.OVR and /dev/null differ diff --git a/Source/RomDsk/ROM_1024KB/WSCHANGE.COM b/Source/RomDsk/ROM_1024KB/WSCHANGE.COM deleted file mode 100644 index bc85c1fc..00000000 Binary files a/Source/RomDsk/ROM_1024KB/WSCHANGE.COM and /dev/null differ diff --git a/Source/RomDsk/ROM_1024KB/WSCHANGE.OVR b/Source/RomDsk/ROM_1024KB/WSCHANGE.OVR deleted file mode 100644 index 4f707c63..00000000 Binary files a/Source/RomDsk/ROM_1024KB/WSCHANGE.OVR and /dev/null differ diff --git a/Source/RomDsk/ROM_1024KB/WSCHHELP.OVR b/Source/RomDsk/ROM_1024KB/WSCHHELP.OVR deleted file mode 100644 index 49becf77..00000000 Binary files a/Source/RomDsk/ROM_1024KB/WSCHHELP.OVR and /dev/null differ diff --git a/Source/RomDsk/ROM_1024KB/WSHELP.OVR b/Source/RomDsk/ROM_1024KB/WSHELP.OVR deleted file mode 100644 index 02634675..00000000 Binary files a/Source/RomDsk/ROM_1024KB/WSHELP.OVR and /dev/null differ diff --git a/Source/RomDsk/ROM_1024KB/WSMSGS.OVR b/Source/RomDsk/ROM_1024KB/WSMSGS.OVR deleted file mode 100644 index 84625d8e..00000000 Binary files a/Source/RomDsk/ROM_1024KB/WSMSGS.OVR and /dev/null differ diff --git a/Source/RomDsk/ROM_1024KB/WSPRINT.OVR b/Source/RomDsk/ROM_1024KB/WSPRINT.OVR deleted file mode 100644 index 83bef6ea..00000000 Binary files a/Source/RomDsk/ROM_1024KB/WSPRINT.OVR and /dev/null differ diff --git a/Source/RomDsk/ROM_1024KB/WSSHORT.OVR b/Source/RomDsk/ROM_1024KB/WSSHORT.OVR deleted file mode 100644 index b44f1480..00000000 Binary files a/Source/RomDsk/ROM_1024KB/WSSHORT.OVR and /dev/null differ diff --git a/Source/RomDsk/ROM_1024KB/WSU.COM b/Source/RomDsk/ROM_1024KB/WSU.COM deleted file mode 100644 index 39830fdf..00000000 Binary files a/Source/RomDsk/ROM_1024KB/WSU.COM and /dev/null differ diff --git a/Source/RomDsk/ROM_1024KB/ZAP.COM b/Source/RomDsk/ROM_1024KB/ZAP.COM index 47ffcbb8..be4dbf38 100644 Binary files a/Source/RomDsk/ROM_1024KB/ZAP.COM and b/Source/RomDsk/ROM_1024KB/ZAP.COM differ diff --git a/Source/RomDsk/ROM_1024KB/ZCAL.COM b/Source/RomDsk/ROM_1024KB/ZCAL.COM index a5add241..f239e952 100644 Binary files a/Source/RomDsk/ROM_1024KB/ZCAL.COM and b/Source/RomDsk/ROM_1024KB/ZCAL.COM differ diff --git a/Source/RomDsk/ROM_1024KB/ZDE.COM b/Source/RomDsk/ROM_1024KB/ZDE.COM index 9bc493c3..f58310ba 100644 Binary files a/Source/RomDsk/ROM_1024KB/ZDE.COM and b/Source/RomDsk/ROM_1024KB/ZDE.COM differ diff --git a/Source/RomDsk/ROM_1024KB/ZSID.COM b/Source/RomDsk/ROM_1024KB/ZSID.COM new file mode 100644 index 00000000..686b3775 Binary files /dev/null and b/Source/RomDsk/ROM_1024KB/ZSID.COM differ diff --git a/Source/RomDsk/ROM_512KB/CLRDIR.COM b/Source/RomDsk/ROM_512KB/CLRDIR.COM index d1f2a7d6..c6b05cc9 100644 Binary files a/Source/RomDsk/ROM_512KB/CLRDIR.COM and b/Source/RomDsk/ROM_512KB/CLRDIR.COM differ diff --git a/Source/RomDsk/ROM_512KB/COMPARE.COM b/Source/RomDsk/ROM_512KB/COMPARE.COM new file mode 100644 index 00000000..29fa41e6 Binary files /dev/null and b/Source/RomDsk/ROM_512KB/COMPARE.COM differ diff --git a/Source/RomDsk/ROM_512KB/DDT.COM b/Source/RomDsk/ROM_512KB/DDT.COM index 83f8603f..70e4ebfe 100644 Binary files a/Source/RomDsk/ROM_512KB/DDT.COM and b/Source/RomDsk/ROM_512KB/DDT.COM differ diff --git a/Source/RomDsk/ROM_512KB/DDTZ.COM b/Source/RomDsk/ROM_512KB/DDTZ.COM index e30a34c0..4f6eca6b 100644 Binary files a/Source/RomDsk/ROM_512KB/DDTZ.COM and b/Source/RomDsk/ROM_512KB/DDTZ.COM differ diff --git a/Source/RomDsk/ROM_512KB/DIF.COM b/Source/RomDsk/ROM_512KB/DIF.COM deleted file mode 100644 index 87b89d75..00000000 Binary files a/Source/RomDsk/ROM_512KB/DIF.COM and /dev/null differ diff --git a/Source/RomDsk/ROM_512KB/FDISK80.COM b/Source/RomDsk/ROM_512KB/FDISK80.COM index 00ac10f7..0d73cef2 100644 Binary files a/Source/RomDsk/ROM_512KB/FDISK80.COM and b/Source/RomDsk/ROM_512KB/FDISK80.COM differ diff --git a/Source/RomDsk/ROM_512KB/LDDS.COM b/Source/RomDsk/ROM_512KB/LDDS.COM index 9be1d4a4..357f1360 100644 Binary files a/Source/RomDsk/ROM_512KB/LDDS.COM and b/Source/RomDsk/ROM_512KB/LDDS.COM differ diff --git a/Source/RomDsk/ROM_512KB/LDP2D.COM b/Source/RomDsk/ROM_512KB/LDP2D.COM index 40220f17..a89e03d8 100644 Binary files a/Source/RomDsk/ROM_512KB/LDP2D.COM and b/Source/RomDsk/ROM_512KB/LDP2D.COM differ diff --git a/Source/RomDsk/ROM_512KB/NULU.COM b/Source/RomDsk/ROM_512KB/NULU.COM index 3d45098a..fc5594b1 100644 Binary files a/Source/RomDsk/ROM_512KB/NULU.COM and b/Source/RomDsk/ROM_512KB/NULU.COM differ diff --git a/Source/RomDsk/ROM_512KB/ZAP.COM b/Source/RomDsk/ROM_512KB/ZAP.COM index 47ffcbb8..be4dbf38 100644 Binary files a/Source/RomDsk/ROM_512KB/ZAP.COM and b/Source/RomDsk/ROM_512KB/ZAP.COM differ diff --git a/Source/RomDsk/ROM_512KB/ZCAL.COM b/Source/RomDsk/ROM_512KB/ZCAL.COM index a5add241..f239e952 100644 Binary files a/Source/RomDsk/ROM_512KB/ZCAL.COM and b/Source/RomDsk/ROM_512KB/ZCAL.COM differ diff --git a/Source/RomDsk/ROM_512KB/ZDE.COM b/Source/RomDsk/ROM_512KB/ZDE.COM index 9bc493c3..f58310ba 100644 Binary files a/Source/RomDsk/ROM_512KB/ZDE.COM and b/Source/RomDsk/ROM_512KB/ZDE.COM differ diff --git a/Source/RomDsk/ReadMe.txt b/Source/RomDsk/ReadMe.txt index 37cf4faf..05f0f22d 100644 --- a/Source/RomDsk/ReadMe.txt +++ b/Source/RomDsk/ReadMe.txt @@ -14,15 +14,6 @@ first grabs all of the "standard" files for the size of ROM being built. So, if you are building a normal 512KB ROM, all of the files in 512KB directory will be pulled in. -After adding all of the standard files for the size of ROM being -built, the build process will add the files from the appropriate -platform directory. So, if you are building a ROM for the Zeta -platform, all of the files in the zeta directory will be added. - -The reason for the platform directories is that some programs are -specific to a platform. The platform directories provide a mechanism -to add platform specific programs. - You may freely add/delete/update the files in these directories to change the contents of the ROM Disk of your ROM firmware. diff --git a/Source/UBIOS/ubios.inc b/Source/UBIOS/ubios.inc index 580ec42b..546c671d 100644 --- a/Source/UBIOS/ubios.inc +++ b/Source/UBIOS/ubios.inc @@ -11,27 +11,27 @@ BF_CIOOST .EQU BF_CIO + 4 ; CHARACTER OUTPUT STATUS BF_DIO .EQU $40 BF_DIOREAD .EQU BF_DIO + 2 ; DISK READ BF_DIOWRITE .EQU BF_DIO + 3 ; DISK WRITE +;; +;; MEMORY BANK CONFIGURATION +;; +;ROMSIZE .EQU 512 +;RAMSIZE .EQU 512 ; -; MEMORY BANK CONFIGURATION +;BID_ROM0 .EQU $0000 +;BID_ROMN .EQU (BID_ROM0 + ((ROMSIZE / 32) - 1)) +;BID_RAM0 .EQU $8000 +;BID_RAMN .EQU (BID_RAM0 + ((RAMSIZE / 32) - 1)) ; -ROMSIZE .EQU 512 -RAMSIZE .EQU 512 - -BID_ROM0 .EQU $0000 -BID_ROMN .EQU (BID_ROM0 + ((ROMSIZE / 32) - 1)) -BID_RAM0 .EQU $8000 -BID_RAMN .EQU (BID_RAM0 + ((RAMSIZE / 32) - 1)) - -BID_BOOT .EQU BID_ROM0 ; BOOT BANK -BID_BIOSIMG .EQU BID_ROM0 + 1 ; BIOS IMAGE BANK -BID_OSIMG .EQU BID_ROM0 + 2 ; ROM LOADER AND IMAGES BANK -BID_FSFAT .EQU BID_ROM0 + 3 ; FAT FILESYSTEM DRIVER BANK -BID_ROMD0 .EQU BID_ROM0 + 4 ; FIRST ROM DRIVE BANK -BID_ROMDN .EQU BID_ROMN ; LAST ROM DRIVE BANK - -BID_RAMD0 .EQU BID_RAM0 ; FIRST RAM DRIVE BANK -BID_RAMDN .EQU BID_RAMN - 4 ; LAST RAM DRIVE BANK -BID_AUX .EQU BID_RAMN - 3 ; AUX BANK (BPBIOS, ETC.) -BID_BIOS .EQU BID_RAMN - 2 ; BIOS BANK -BID_USR .EQU BID_RAMN - 1 ; USER BANK (CP/M TPA, ETC.) -BID_COM .EQU BID_RAMN ; COMMON BANK, UPPER 32K +;BID_BOOT .EQU BID_ROM0 ; BOOT BANK +;BID_BIOSIMG .EQU BID_ROM0 + 1 ; BIOS IMAGE BANK +;BID_OSIMG .EQU BID_ROM0 + 2 ; ROM LOADER AND IMAGES BANK +;BID_FSFAT .EQU BID_ROM0 + 3 ; FAT FILESYSTEM DRIVER BANK +;BID_ROMD0 .EQU BID_ROM0 + 4 ; FIRST ROM DRIVE BANK +;BID_ROMDN .EQU BID_ROMN ; LAST ROM DRIVE BANK +; +;BID_RAMD0 .EQU BID_RAM0 ; FIRST RAM DRIVE BANK +;BID_RAMDN .EQU BID_RAMN - 4 ; LAST RAM DRIVE BANK +;BID_AUX .EQU BID_RAMN - 3 ; AUX BANK (BPBIOS, ETC.) +;BID_BIOS .EQU BID_RAMN - 2 ; BIOS BANK +;BID_USR .EQU BID_RAMN - 1 ; USER BANK (CP/M TPA, ETC.) +;BID_COM .EQU BID_RAMN ; COMMON BANK, UPPER 32K diff --git a/Source/ZCCP/ccp.com b/Source/ZCCP/ccp.com new file mode 100644 index 00000000..72a01c68 Binary files /dev/null and b/Source/ZCCP/ccp.com differ diff --git a/Source/ZCCP/diskinfo.com b/Source/ZCCP/diskinfo.com new file mode 100644 index 00000000..068cc97a Binary files /dev/null and b/Source/ZCCP/diskinfo.com differ diff --git a/Source/ZCCP/loadseg.com b/Source/ZCCP/loadseg.com new file mode 100644 index 00000000..503d7679 Binary files /dev/null and b/Source/ZCCP/loadseg.com differ diff --git a/Source/ZCCP/read.me b/Source/ZCCP/read.me new file mode 100644 index 00000000..83dd11e6 --- /dev/null +++ b/Source/ZCCP/read.me @@ -0,0 +1,103 @@ + + + + + Z C C P by Simeon Cran + ======================= + + +February 1993. +ZCCP has been improved slightly from the original release. It +should run much the same as before however modifications have +been made in an attempt to allow it to work successfully from +systems which boot from other than the A: drive. Also, the +terminal dimensions are now automatically taken from the SCB (as +set up by GENCPM). Special thanks go to Randy Winchester who is +sharing his easy to follow instructions about ZCCP. Read ZCCP.TXT +in this package for the expert help. + + + + +ZCCP Quick Instructions +======================= + +ZCCP is a direct replacement for the CP/M 3.0 CCP. It requires +ZPM3 and provides a ZCPR compatible system. There are some minor +incompatibilities with ZCPR however the advantages of ZCCP more +than outweigh these. + +You must replace your current CCP with the ZCCP.COM. How you do +this will depend on the setup of your computer so no details are +given here. + +When ZCCP starts, it reads ZINSTAL.ZPM (which must be on the +default boot drive (usually A:) in user area 0). This file can +be patched to set initial values for MHZ (speed of the computer), +MAXU (maximum user number) and MAXD (highest disk drive). These +values are set near the start of the ZINSTAL.ZPM file and there +are ASCII pointers to them (which can be seen with a debugger +such as SID.COM). + +After ZINSTAL.ZPM has been read, ZCCP attempts to run the command +STARTZPM.COM. This is usually an "alias" containing a number of +commands used to put the system into a useful state. Typically +STARTZPM.COM would contain commands to load a TCAP and named +directory file, to set the path, and to set an error handler. + +LOADSEG.COM is a ZCCP utility used to load RSXes, TCAPs and Named +Directory files. It contains some online help. + +RSXDIR.COM is a ZCPR utility which displays RSXes in memory. It +has been included to help you understand how LOADSEG places +various segments in high memory. + +DISKINFO.COM is a ZCPR utility which gives information about your +disks. It has been included for your interest. + +Note that ZCCP has very few resident commands. If you are +switching from a standard CP/M 3.0 CCP you will find that you are +unable to perform certain simple functions (such as directory +displays!). You should therefore obtain replacements for the CCP +resident commands before installing ZCCP. The Z-Node bulletin +boards in Australia and the USA are a good source. Note that ZCCP +will not load Type-4 ZCPR programs. + +If you are familiar with ZCPR systems you should have little +trouble in getting the most from ZCCP. If you are new to ZCPR, I +suggest that you learn as much as you can. ZCPR is a complex and +powerful system which does very little without proper setup. + +RCP's and IOP's are not implemented (which saves TPA space) and +FCP's are not implemented although flow control is. To use flow +control you must have an IF.COM program to handle IF commands. +ZCCP handles the standard flow control commands internally (XIF, +FI, ELSE, AND, OR). There are two other internal commands +implemented: NOTE (a do-nothing command) and CLS (to clear the +screen). + + +The ZCCP package presented here may be freely distributed and +used for your own private use. No commercial or institutional use +is allowed without prior written agreement. + + +The ZCCP package is copyright 1992,1993 by Simeon Cran, Brisbane, +Australia. + + +The Z-Node 62 BBS in Perth, Western Australia may be contacted +from Australia on 09 450 0200 or from overseas on +619 4500200. +Z-Node 62 also is a distribution point for ZPM3 and MYZ80. MYZ80 +is a high performance Z80 emulator for IBM AT (and better) +computers. MYZ80 is able to run ZPM3. + + +No support for ZCCP is being offered at this stage. Use at your +own risk. + + +Simeon Cran. +February 1993. + + \ No newline at end of file diff --git a/Source/ZCCP/rsxdir.com b/Source/ZCCP/rsxdir.com new file mode 100644 index 00000000..dd880a61 Binary files /dev/null and b/Source/ZCCP/rsxdir.com differ diff --git a/Source/ZCCP/startzpm.com b/Source/ZCCP/startzpm.com new file mode 100644 index 00000000..721b588b Binary files /dev/null and b/Source/ZCCP/startzpm.com differ diff --git a/Source/ZCCP/zccp.txt b/Source/ZCCP/zccp.txt new file mode 100644 index 00000000..fb4efce9 --- /dev/null +++ b/Source/ZCCP/zccp.txt @@ -0,0 +1,308 @@ +The following is used by permission and remains copyright Randy Winchester. +=========================================================================== + +ZCCP Documentation, Version 1.0 +by Randy Winchester + +ZCCP Features + +This documentation is provided to assist the user in getting a +ZCCP system up and running. It is not an exhaustive course on Z- +System or ZCPR. The following list details which ZCPR features +are provided with ZCCP, and which ones aren't. + +* ZCPR 3.3 compatibility. ZCCP can run a wide range of utilities + an applications created for ZCPR 3.3 and ZCPR 3.4. + +* TCAP. A Z3T termcap file describing terminal characteristics + can be loaded into the system. Z-System programs make use of the + TCAP for output to the screen - a big improvement over the old + method of patching individual programs with terminal control + codes. TCAP files are loaded by the ZCCP LOADSEG command. + +* Named directories. Up to 12 user areas can be assigned names. + Named Directory Registers (*.NDR files) are loaded by the ZCCP + LOADSEG command. + +* Command Search Path. ZCCP will search for commands along a + user defined search path. Up to six path elements + (directories) can be defined. + +* Environment block. Contains TCAP, Named Directory, and Path + information. Also includes a map of active disk drives and + other system information. The environment block can be viewed + with the Z-System SHOW utility. + +* Flow control. Conditional processing for batch files. Relies + on Z-System IF.COM for setting the flow state. Other flow + control commands (FI, ELSE, XIF, OR, AND) are resident. + +* Multiple commands can be entered on the command line. The + command line buffer will hold up to 225 characters. Commands + should be separated by semicolons. + +* Extended Command Processor. If a command is not a built-in + flow command, resident command, or located on disk along the + search path, the command line is passed to an extended command + processor. A typical extended command processor is ARUNZ, a + sophisticated batch file executor with alias features. To use + a program as an extended command processor, rename it to + CMDRUN.COM and place it in the ROOT directory of your boot disk. + +* Error handler. In the event that the extended command + processor can't handle a command, control is passed to an error + handler. Error handlers give information about the error + (instead of the useless CP/M "?" message) and allow the command + line to be edited and reused. + +* Resident commands. The following commands are built in: + CLS - clears the screen + NOTE - text following the NOTE command is treated as a + comment. + FI - Flow control: terminate the current IF level + ELSE - Flow control: toggle the flow state + XIF - Flow control: exit all pending IF levels + OR - Flow control: OR IF tests to set flow state + AND - Flow control: AND IF tests to set flow state + +* Shell stack. Up to four shell levels can be defined. Z-System + provides a choice of several different shells. Applications such + as terminal programs and word processors can also be assigned + shell status. + +* ZCCP uses the LOADSEG command for direct loading of RSX files + that have not been GENCOMed. Example: LOADSEG SAVE.RSX loads + SAVE.RSX. + +There are some things that Z3Plus will do that ZCCP won't do. + +- ZCCP does not support a Flow Command Package (FCP). It relies + on the transient IF command. Other flow commands (FI, ELSE, XIF, + OR, AND) are resident in ZCCP. + +- A Resident Command Package (RCP) is not implemented. CLS and + NOTE are resident in ZCCP. All other commands must be loaded + from disk. This isn't as much of a handicap as it might sound + if you have a fast RAM drive to store frequently used commands. + +- ZCCP can not load type 4 programs (used with ZCPR 3.4). It + loads standard COM files at 100H, and type 3 programs that load + in high memory. Most type 4 programs have type 3 or COM + equivalents, so this should not be a problem. + +- ZCCP can not reexecute loaded programs. This trick is usually + performed on Z-Systems with a GO command that jumps to 100H. + Since ZCCP also loads at 100H, a GO command would only restart + ZCCP. + +ZCCP Files + +Three files are included in ZCCP.ARK: + + File name Size Description + ============ ==== ========================================== + CCP .COM 3k ZCCP replacement for CCP.COM + LOADSEG .COM 3k Loader for named directories and termcaps + ZINSTAL .ZPM 1k Segment containing environment information + +Getting Started - Preparing a Boot Disk + +Format a system boot disk using the same proceedure that you normally +would. + +Copy the files from ZCCP.ARK to user area 0 of the newly +formatted disk. + +Copy CPM+.SYS (some systems may use a slightly different name for this +file) to user 0 of the boot disk. The CPM+.SYS must include the BDOS +segments from ZPM3. Use the ZPM3 MAKEDOS utility to overlay your +system file with ZPM3. (Commodore 128 users must generate a new +system using the ZPM3 BDOS segments. The MAKEDOS utility does not +work properly on a C128.) + +Locate a copy of a Z-System alias utility. A good one is +SALIAS16, although others should work also. Copy it to user 0 of +the boot disk. + +At this point, reboot the system with the new system disk. After the +system boots, you won't be able to do much with it. The only resident +commands are CLS and NOTE, and ZCCP can only locate commands if they +are prefixed with the drive and user number. + +The next step is to create a startup alias. When ZCCP boots, it +looks for a file named STARTZPM.COM and executes commands from +it. STARTZPM.COM is created with a ZCPR alias utility. Here is +a listing of a STARTZPM.COM created with SALIAS: + + ============================================================= + + A0>SALIAS STARTZPM + + 15: ; Logs the ROOT directory (A15) on the + ; current drive. + + LOADSEG NAMES.NDR TCAP.Z3T + ; LOADSEG loads the Named Directory Register + ; and TCAP. + ; Directories can now be referred to by + ; name, as in the next command: + + SETPTH10 /C COMMANDS RAM2 WORK $$$$ $$0 ROOT + ; SETPTH sets the command search path. + ; The /c option first clears any existing path. + ; Directories are then listed in the + ; order searched. In this case, COMMANDS + ; is a 64K ramdisk (drive/user F0) where + ; frequently used commands are stored. RAM2 is + ; an additional RAM disk. (drive/user M0). + ; WORK is a standard 3.5" floppy disk + ; drive, (drive/user C15) where some 700K + ; of utilities and applications are + ; located. $$$$ refers to the currently + ; logged drive and user area. $$0 refers + ; to user area 0 of the current drive. + ; The ROOT directory is on drive A, user + ; 15, where startup utilities and system + ; files can be found. + + AUTOTOG ON ; Turns on keyboard control of ZPM3 Auto + ; Command Prompting. Auto Command + ; Prompting is toggled by entering CTRL-Q. + + COMMANDS: ; Logs the commands directory. + + IF ~EXIST CP.* ; Test to see if commands are loaded. + ; This line reads: "If the CP command + ; does not exist . . ." and sets the flow + ; state to true if the file doesn't exist. + C1:CP C1:*.* F0: + ; ". . . copy all of the commands in + ; drive/user C1 to the commands (F0) + ; directory . . ." + FI ; ". . . end if." + + ROOT: ; Log the root directory (A15). + + CP C:ZF*.* M0: ; Copy ZFILER.COM and ZFILER.CMD to the + ; REU2 directory (M0). + + VERROR ; Install VERROR error handler. + + DATE S ; Set the system time and date. + + ZF ; Invoke ZFILER as a shell. + + ============================================================= + +Of course, your STARTZPM alias will vary depending on the +hardware you need to support, your software preferences, and your +work habits. This alias is close to the upward size limit that +ZCCP can handle based on the capacity of the multiple command +buffer. At the very least, I recommend an alias that will set up +a search path and load a TCAP. + +Actually, I put the cart before the horse in this example. If +you try to reboot your system with the LOADSEG command as listed, +you'll notice that you don't have a NAMES.NDR file. There isn't +one distributed with ZCCP either. Z-System utilities won't let +you edit the NDR either, since the buffer for it hasn't been +created yet. This turned out to be a nasty chicken/egg +situation, hopefully solved by the inclusion of a sample +NAMES.NDR file containing simply A0:SYSTEM and A15:ROOT. + +At this point, you should have a mostly functioning ZCCP system disk. +Reboot the system with the new disk. You might want to correct any +problems with it or tweak it to perfection before moving on. + +List of Z-System Utilities for ZCCP + +Some of the following utilities are essential, others are nice to +have. The version numbers listed are the latest known versions at the +time that this documentation was written. Utilities can be found on +ZNode BBSs, and some of them are available on Internet anonymous ftp +sites (Simtel20 or its mirror sites). + +SALIAS16 - already mentioned in the example above. SALIAS (or +one of the other ZCPR alias utilities) are essential. + +SD138B - excellent DIRectory utility. SD offers many +different types of sorts, list formats, etc., displays date +stamps, and supports output to a file. + +MKDIR32 - utility for manipulating directory names and Named +Directory Register (*.NDR) files. + +ERASE57 - erases files. + +ZFILER10 - a file management shell that can launch applications. +It is programmable in that it can execute user defined macros +from a file. Multiple files can be "tagged" and operated on by +other programs. ZFILER is an excellent program, sort of a GUI +desktop without the slow graphics. + +SETPTH10 - used to set the command search path. Essential! + +VERROR17 - error handler that displays the command line for +reediting. VERROR17 is the only error handler that I found that +works with ZCCP. + +ZEX50 - Z-System EXecutive is a powerful batch file processor +that replaces the CP/M SUBMIT command. + +LBRHLP22 - Z-System Help utility displays help files. Help +files can be crunched (*.HZP), and/or loaded from a HELP.LBR +library. + +ARUNZ09 - runs an alias script from a text file. ARUNZ is +frequently used as an extended command processor. To use ARUNZ +(or any other executable utility) as an extended command +processor, rename it to CMDRUN.COM. + +VLU102 - Video Library Utility views or extracts files from +libraries. Versions of VLU above 1.02 do not work reliably with +ZPM3/ZCCP. + +Z33IF16 - is the IF.COM discussed in the section on flow control. + +SHOW14 - displays an immense amount of information about your +Z-System. SHOW also includes a memory patching function. + +ZCNFG24 - configures Z-System program options. Most Z-System +programs are distributed with a configuration (*.CFG) file that +produces a menu of configuration options when run with ZCNFG. + +ZP17 - Z-System Patch utility edits files, disk sectors, or +memory, and includes a built-in RPN calculator and number base +converter. + +ZMAN-NEW - This is a manual describing Z-System features in +depth. It is based on earlier versions of Z-System, and is a +little dated, but otherwise contains information that you won't +find anywhere else. Not everything in the manual applies to +operation of ZPM3/ZCCP, but with the documentation presented +here, you should be able to get a good idea of what works and +what doesn't. + +A TCAP termcap file for your system - This file is essential if you +want to use any ZCPR programs that need a TCAP. + +ZCCP Technical Notes + +ZCCP is a replacement CCP that implements ZCPR 3.3. It loads at +100H and is stored in the bank 0 CCP buffer for fast reloading as +does the standard CCP. By contrast, Z3Plus loads into high +memory and can be overwritten by transient commands, requiring +reloading Z3Plus from disk. Because ZCCP replaces the CCP, a +ZCCP system has more TPA (transient program area) than a Z3Plus +system. A ZCCP system on the C128 has more than 57K of TPA, +almost the same amount as a standard C128 CP/M system. + +This should be enough information to get started with ZPM3/ZCCP. +Set up a boot disk, experiment with some Z-System utilities, read +ZMAN-NEW, and get some applications running. You'll agree that +ZPM3/ZCCP breaths new life into CP/M. + +******************************************************************************* +* Randy Winchester * randy@mit.edu * PO Box 1074, Cambridge, MA 02142 * +******************************************************************************* + \ No newline at end of file diff --git a/Source/ZCCP/zinstal.zpm b/Source/ZCCP/zinstal.zpm new file mode 100644 index 00000000..1a6b2c4c Binary files /dev/null and b/Source/ZCCP/zinstal.zpm differ diff --git a/Source/ZCPR-DJ/Makefile b/Source/ZCPR-DJ/Makefile new file mode 100644 index 00000000..47584a38 --- /dev/null +++ b/Source/ZCPR-DJ/Makefile @@ -0,0 +1,10 @@ + +OBJECTS= zcpr.bin + +OTHERS = zcpr.rel + +TOOLS = ../../Tools +include $(TOOLS)/Makefile.inc + +zcpr.bin: zcpr.rel + $(ZXCC) $(TOOLS)/cpm/bin/L80 -zcpr,zcpr.bin/n/e diff --git a/Source/ZCPR/Makefile b/Source/ZCPR/Makefile new file mode 100644 index 00000000..4e35f6d1 --- /dev/null +++ b/Source/ZCPR/Makefile @@ -0,0 +1,12 @@ + +OBJECTS = zcpr.bin bdloc.com + +OTHERS = *.hex + +TOOLS = ../../Tools +include $(TOOLS)/Makefile.inc + +zcpr.bin: zcpr.asm + $(ZXCC) $(CPM)/MAC -$< -$$PO + $(ZXCC) $(CPM)/MLOAD25 -$@=zcpr.hex + diff --git a/Source/ZPM3/Build.cmd b/Source/ZPM3/Build.cmd new file mode 100644 index 00000000..47018c83 --- /dev/null +++ b/Source/ZPM3/Build.cmd @@ -0,0 +1,48 @@ +@echo off +setlocal + +set TOOLS=../../Tools + +set PATH=%TOOLS%\tasm32;%TOOLS%\zx;%TOOLS%\cpmtools;%PATH% + +set TASMTABS=%TOOLS%\tasm32 + +set ZXBINDIR=%TOOLS%/cpm/bin/ +set ZXLIBDIR=%TOOLS%/cpm/lib/ +set ZXINCDIR=%TOOLS%/cpm/include/ + +copy ..\ZCCP\ccp.com zccp.com +copy ..\ZCCP\zinstal.zpm . +copy ..\ZCCP\startzpm.com . +copy ..\CPM3\genbnk.dat . +copy ..\CPM3\zpmbios3.spr bnkbios3.spr +copy ..\CPM3\gencpm.com . +copy ..\CPM3\biosldrd.rel . +copy ..\CPM3\biosldrc.rel . +rem copy ..\CPM3\cpmldr.com . + +rem ZPM Loader +echo. +echo. +echo *** ZPM Loader *** +echo. +zx LINK -ZPMLDRD[L100]=ZPM3LDR,BIOSLDRD +move /Y zpmldrd.com zpmldr.bin +zx LINK -ZPMLDRC[L100]=ZPM3LDR,BIOSLDRC +move /Y zpmldrc.com zpmldr.com +rem pause + +rem Banked ZPM3 +echo. +echo. +echo *** Banked ZPM3 *** +echo. +copy genbnk.dat gencpm.dat +zx gencpm -auto -display +rem pause + +rem Loader + +tasm -t80 -g3 -fFF loader.asm loader.bin loader.lst + +copy /b loader.bin + zpmldr.bin zpmldr.sys diff --git a/Source/ZPM3/Clean.cmd b/Source/ZPM3/Clean.cmd new file mode 100644 index 00000000..57295d69 --- /dev/null +++ b/Source/ZPM3/Clean.cmd @@ -0,0 +1,21 @@ +@echo off +setlocal + +if exist zccp.com del zccp.com +if exist *.sys del *.sys +if exist gencpm.dat del gencpm.dat +if exist loader.cim del loader.cim +if exist bnkbios3.spr del bnkbios3.spr +if exist system.epr del system.epr +if exist system.evn del system.evn +if exist system.odd del system.odd +if exist biosldr.rel del biosldr.rel +if exist *.sym del *.sym +if exist zpmldr.com del zpmldr.com +if exist cpmldr.com del cpmldr.com +if exist startzpm.com del startzpm.com +if exist gencpm.com del gencpm.com +if exist *.dat del *.dat +if exist biosldr?.rel del biosldr?.rel +if exist *.bin del *.bin +if exist *.lst del *.lst diff --git a/Source/ZPM3/Makefile b/Source/ZPM3/Makefile new file mode 100644 index 00000000..d72e77bd --- /dev/null +++ b/Source/ZPM3/Makefile @@ -0,0 +1,54 @@ +OBJECTS = zpmldr.com zpmldr.sys cpm3.sys startzpm.com zccp.com +ifdef REBUILD_ZPM_TOOLS +OBJECTS += setz3.com clrhist.com autotog.com +else +NODELETE = setz3.com clrhist.com autotog.com +endif +OTHERS = zpmldr.bin loader.bin biosldrd.rel biosldrc.rel gencpm.com gencpm.dat bnkbios3.spr +TOOLS =../../Tools + +include $(TOOLS)/Makefile.inc + +zpmldr.bin: zpm3ldr.rel biosldrd.rel + $(ZXCC) $(CPM)/LINK -ZPMLDRD[L100]=ZPM3LDR,BIOSLDRD + mv zpmldrd.com zpmldr.bin + +zpmldr.com: zpm3ldr.rel biosldrc.rel + $(ZXCC) $(CPM)/LINK -ZPMLDRC[L100]=ZPM3LDR,BIOSLDRC + mv zpmldrc.com zpmldr.com + +zpmldr.sys: zpmldr.bin loader.bin + cat loader.bin zpmldr.bin > $@ + +cpm3.sys: gencpm.com gencpm.dat bnkbios3.spr + $(ZXCC) gencpm -auto -display + +bnkbios3.spr: ../CPM3/zpmbios3.spr + cp $< $@ + +gencpm.dat: ../CPM3/genbnk.dat + cp $< $@ + +gencpm.com: ../CPM3/gencpm.com + cp $< $@ + +biosldrc.rel: ../CPM3/biosldrc.rel + cp $< $@ + +biosldrd.rel: ../CPM3/biosldrd.rel + cp $< $@ + +zccp.com: ../ZCCP/ccp.com + cp $< $@ + +zinstal.zpm: ../ZCCP/zinstal.zpm + cp $< $@ + +startzpm.com: ../ZCCP/startzpm.com + cp $< $@ + +setz3.com: setz3.z80 + +clrhist.com: clrhist.z80 + +autotog.com: autotog.z80 diff --git a/Source/ZPM3/autotog.com b/Source/ZPM3/autotog.com new file mode 100644 index 00000000..3c7a5980 Binary files /dev/null and b/Source/ZPM3/autotog.com differ diff --git a/Source/ZPM3/autotog.z80 b/Source/ZPM3/autotog.z80 new file mode 100644 index 00000000..b59a6663 --- /dev/null +++ b/Source/ZPM3/autotog.z80 @@ -0,0 +1,131 @@ + +;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; A U T O T O G +; for ZPM3 +; by Simeon Cran +; 30/3/92 +; +; This program toggles the Auto Command Prompting facility of ZPM3. It is +; presented in source code form to inform users about how the facility is +; manipulated. + +; Be aware that when Auto Command Prompting is enabled with this program, +; it won't actually operate until turned on at the keyboard with ^Q. This +; program simply enables the ^Q toggling of Auto Command Prompting. + +; When ZPM3 is booted, Auto Command Prompting is disabled. Usually, a +; startup file would include the AUTOTOG command to turn it on unless it +; is felt that the facility could confuse the operator (as may happen with +; a remote ZPM3 system). +;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +; SYNTAX: +; AUTOTOG Toggles the state of the Auto Command Prompting +; AUTOTOG ON Enables Auto Command Prompting +; AUTOTOG OFF Disables Auto Command Prompting +; AUTOTOG // Displays a brief help message + +;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; +; Automatic Command Prompting is enabled and disabled by a bit 6 of offset +; 85h of the SCB page. This offset can not be directly accessed by the SCB +; function (31h). Instead, we get the SCB base page with function 31h, and +; then find the offset from there. No other bits in the byte may be touched. +; +;=============================================================================== + +BDOS equ 5 +deffcb equ 5ch +SCBfunc equ 31h ; Get/Set SCB function number +SCBoff equ 3bh ; Offset in SCB to get SCB base page +ACPoff equ 85h ; Offset in SCB base page of Auto Command Prompting bit + + jp start ; Jump over general data + +HELPmsg: + db ' SYNTAX:' + db 10,13 + db ' AUTOTOG Toggles the state of the Auto Command Prompting' + db 10,13 + db ' AUTOTOG ON Enables Auto Command Prompting' + db 10,13 + db ' AUTOTOG OFF Disables Auto Command Prompting' + db 10,13 + db ' AUTOTOG // Displays a brief help message' + db '$' +ONmsg: + db 'ZPM3 Auto Command Prompting is now enabled. Toggle with ^Q.' + db '$' +OFFmsg: + db 'ZPM3 Auto Command Prompting is now disabled.' + db '$' + +ONword: ; Word to match to turn Auto Command Prompting on + db 'ON ' +OFFword: ; Word to match to turn Auto Command Prompting off + db 'OFF ' +TOGword: ; Word to match to toggle Auto Command Prompting + db ' ' + +HELP: ld de,HELPmsg +MSGexit: + ld c,9 + call bdos + rst 0 + +start: ; Get the address of the bit which controls Auto Command Prompting. + ld c,SCBfunc + ld de,SCBPB + call bdos ; Get base page of SCB + ld h,a + ld l,ACPoff ; HL is now the address of the byte + ld (ACPaddr),hl ; Save it + +; Find out what the user wants to do. If the argument matches +; any of the three control words, act accordingly, otherwise +; show the help message and exit. + ld hl,ONword + call matchWord + jr z,TurnON + ld hl,OFFword + call matchWord + jr z,TurnOFF + ld hl,TOGword + call matchWord + jr nz,HELP +; Toggle the Auto Command Prompting. + ld hl,(ACPaddr) + bit 6,(hl) + jr z,TurnON +TurnOFF: ; Turn the Auto Command Prompting off. + ld hl,(ACPaddr) + res 6,(hl) + ld de,OFFmsg + jr MSGexit + +TurnON: ; Turn the Auto Command Prompting on. + ld hl,(ACPaddr) + set 6,(hl) + ld de,ONmsg + jr MSGexit + + + +matchWord: ; Compare the string at HL with the string at defFCB+1 for + ; 8 bytes. Return Z if it matches. + ld de,defFCB + ld bc,8 +matchW1: + inc de + ld a,(de) + cpi + ret nz + jp pe,matchW1 + ret + +SCBPB: ; System control block function parameter block. +ACPaddr: ; Save the address of the ACP bit here too. + db 03bh + db 0 ; Get operation + + \ No newline at end of file diff --git a/Source/ZPM3/bios.txt b/Source/ZPM3/bios.txt new file mode 100644 index 00000000..f9a6ccd7 --- /dev/null +++ b/Source/ZPM3/bios.txt @@ -0,0 +1,230 @@ + NOTES on your CP/M 3.0 BIOS and ZPM3 + ==================================== + Last updated 19/4/92 + +ZPM3 will work fine with your current CP/M 3.0 BIOS. This +document is not meant to tell you how to change your BIOS for +ZPM3, but rather to point out some interesting and useful facts +about the way ZPM3 uses the BIOS, and how you should configure +your BIOS. + + + +XMOVE routine. +~~~~~~~~~~~~~~ +If you have 128 byte physical sectors, or your BIOS does all the +deblocking so that it appears to the BDOS that you have 128 byte +physical sectors, XMOVE does not get used at all by ZPM3. Such +was not the case with CP/M 3.0 which would make redundant calls +to XMOVE. Make sure XMOVE is implemented and working anyhow as +applications may attempt to use it. + +When the BDOS is operating in the system bank (bank 0) and it +needs to move data in the TPA bank, it switches to the TPA bank +and does an ordinary LDIR. As such, XMOVE will never get called +by the BDOS with B=C (source bank and destination bank the same). + +In the CP/M 3.0 manuals, there are two differing opinions about +XMOVE as far as whether B is the source or the destination. The +truth is that C is the source and B is the destination. Anything +you see to the contrary is a misprint. + +MOVE routine. +~~~~~~~~~~~~~ +When CP/M 3.0 was released, it was made 8080 compatible simply +because CP/M 2.2 was 8080 compatible. I have never heard of an +8080 machine running CP/M 3.0, and it is likely that there has +never been one. Digital Research knew that the Z80 was the CPU of +choice for modern PC's, and while they wrote their code for the +8080, they recognised the Z80 with the MOVE routine (which a Z80 +BIOS could implement in just three instructions). + +ZPM3 uses the MOVE routine much less than CP/M 3.0 does. In fact, +the only time ZPM3 uses MOVE is with an XMOVE call directly +preceding it. If you have 128 byte physical sectors (or the BIOS +does the sector deblocking), MOVE will never get called. + +Always remember that MOVE must return with HL and DE pointing to +the end of the moved data. If they don't, you will have trouble. + + +TIME routine. +~~~~~~~~~~~~~ +Be aware that the DATE program supplied with CP/M 3.0 will not +work properly if your BIOS does not update the SCB with +interrupts. There have been replacements since then that are +available in the public domain. + +One common trap for BIOS writers is forgetting that HL and DE +must be saved by the TIME routine. There is no obvious reason for +it, and really they should be saved in the BDOS. + +ZPM3 does not expect HL to be saved. If you have had trouble with +your CP/M 3.0 clock things might work now. It was decided that +seeing as TIME was the only routine (apart from MOVE) which +required HL to be saved, it was too easy to overlook, and a real +pain to implement (some systems use HL to switch banks on entry +to the BIOS. MOVE is always accounted for, but TIME sometimes +isn't (Morrow MD11 owners take note!)). + +Ideally, there should be no reason to save HL in your BIOS, +unless you intend to run CP/M 3.0 sometimes (although I can't +imagine why). Any applications which attempt to use TIME through +the function 50 are not guaranteed that HL will be saved anyhow. + +Buffers. +~~~~~~~~ +CP/M 3.0 (and therefore ZPM3) keeps special disk buffers. The +system is rather complex. The directory is buffered separately +from the rest of the disk (and in the case of 128 byte sectors +the rest of the disk isn't buffered anyhow). + +You decide how many buffers to give to each disk's directory and +data, and you may choose to have buffers shared by different +drives. All these choices can make for lots of fun for the +hacker, but without knowing much about the internal workings of +the BDOS how do you best set the buffer up? + +There are many cases to consider depending on how much RAM you +have available to allocate to buffers. If you have virtually +unlimited RAM, you might as well allocate as many buffers as +GENCPM will allow. The only catch to this is that more buffers +implies the BDOS will take more time to look through them all +before coming to the decision that a disk read is required. The +good news is that the ZPM3 searching algorithm is particularly +fast. Empty buffers are discovered even faster than buffers +which are valid but don't match, so large numbers of empty +buffers pose very little problem. In general, even with the +maximum number of buffers, the advantages they give outweigh the +disadvantages. + +Of course, few people have unlimited RAM. If you have very little +room available, spend most of it on the directory buffers. These +buffers act like a cache of the directory, and can save the disk +heads from moving back to the directory tracks to find out where +the next block is stored. Even on very fast hard disks, the +advantages that decent directory buffers give are great. + +When dividing up directory buffers between a number of drives, +consider which drive holds the most files and which drive does +the most work. A drive which holds a lot of files but is rarely +accessed is not worth wasting buffers on. If you have a system +with one hard drive and one floppy drive, and you don't intend to +use the floppy drive very much, give only one buffer to the +floppy and all the rest to your hard drive. This will penalise +the floppy's performance somewhat, but the improvement it gives +to the hard drive will make it worthwhile. + +Data buffers, like directory buffers, perform two tasks: +deblocking of physical sectors, and cacheing. For data buffers +however the cacheing is the less important job, unless you have a +lot of data buffers available. The reason for this is that the +buffer algorithms work by taking the least recently used buffer +and using it for deblocking. If you are working on a file which +is 8k long, but you only have 4k of buffers, the BDOS will run +out of buffers before it has read the whole file and will grab +the least recently used one even though it contains valid data +from the file which could be required later on. The result is +that the BDOS does much searching through its 4k of buffers, but +rarely finds anything which matches and must read from the disk +anyhow. + +In practice the system works a little better than that because of +the way files are used by most programs, so data buffers are +still worthwhile, but to take real advantage of their cacheing +ability you must have more room in the data buffers than the size +of the file you are working with. With word processors such as +Wordstar and NewWord creating extra files as they work, you +really need more than twice as much room in the buffers than the +size of the file. + +So you can see why data buffers are less important than directory +buffers. Something else you should be aware of concerns multi- +sector i/o and the data buffers. When the BDOS is told to read a +file it searches its buffers and if it can't find the data there +it reads it from the disk. Normally it deblocks the data one +record at a time through its data buffers, leaving the data in +the buffers in case it is required again. However multi-sector +i/o does not usually need to deblock its data, so the data is +sent straight to the TPA without going through the data buffers. +If any of that data is required again, it will not be in the data +buffers and must be read from the disk. So two reads of the same +data using multi-sector i/o might actually be slower than reads +that are done a sector at a time! + +And the really important thing about all this is that the CCP +uses multi-sector i/o to load programs. So if you thought that +implementing large numbers of data buffers would give you faster +loading of programs, you were wrong. The data buffers won't help +program loading unless the data can be put into the buffers +first. + +If you use ZCCP, you will find there is a facility to prevent the +data buffers from being bypassed on program loads. It involves +simply setting the f1' bit of the file. The idea is that you set +f1' on all the files which are small enough not to clog up your +buffers, and then they run as if they are on a ram disk, but one +in which you can never lose data. The system is quite wonderful +in that the RAM used to hold the files is available to buffer +other data if required. Unlike a ram disk, the RAM is dynamically +allocated and the data is completely safe. But you must be using +ZCCP, and you must have at least 6k of data buffers before it +does anything useful. If you currently have a ram disk but few +data buffers, consider taking a chunk of your ram disk for data +buffers and switching to ZCCP. + +CPMLDR bug. +~~~~~~~~~~~ +This is closely related to the subject of buffers because you +will find that if you increase your buffers past a certain point, +the system will not boot. Almost certainly you will suspect a +problem with your BIOS code (you normally should), however the +CPMLDR.REL code supplied by DRI has a bug in it. + +You may be wondering if everything DRI did with CP/M 3.0 was +buggy! I must say that what they achieved was terrific, but it +had its faults as well. Hopefully ZPM3 has addressed them all. + +The CPMLDR problem occurs when your CPM3.SYS grows from being 16k +or less, to over 16k (and therefore two logical extents). You may +not have this problem under certain drive configurations, but if +you do, the symptom is that described above. + +There really is no way of patching around this, but if you have +your loader BIOS, you can certainly use the (somewhat superior) +ZPM3LDR.REL code instead. This works very similarly to the DRI +code, except that it works properly. Unlike the DRI code, you +will find that ZPM3LDR has all its messages at the head of the +file so that you can patch them and change them if you wish. + +ZPM3LDR does not clear the screen on boot up (CPMLDR does by +sending multiple linefeeds), but you could patch this if you +like. ZPM3LDR.REL will directly replace CPMLDR.REL. ZPM3LDR +however does not use the MOVE routine that CPMLDR requires +(although there is nothing much to be gained by removing it from +your loader bios code). + + +GENCPM bugs. +~~~~~~~~~~~~ +GENCPM has bugs in it. If you can, try and set up all your +buffers manually. That way you'll know where they are and you are +in complete control. + +The biggest fault I have found with GENCPM is that it will +allocate allocation vectors incorrectly. CP/M 3.0 can use double +bit allocation vectors, but doesn't necessarily. Sometimes (and I +think it is mainly with big disks), GENCPM will only allocate +enough room for single bit allocation vectors when double bit +vectors had been specified. The symptoms of this are varied, but +often, you can use your A: drive for a while, but as soon as you +use your B: drive funny things happen. If you only use A: and C: +drives, things appear to work OK. + +The first thing to try if you suspect a GENCPM induced problem is +setting up with only one drive and a single buffer. If that fixes +it, the problem could well be with GENCPM. + +Naturally, your BIOS code could still be the problem, so look out +for that too! + \ No newline at end of file diff --git a/Source/ZPM3/bnkbdos3.spr b/Source/ZPM3/bnkbdos3.spr new file mode 100644 index 00000000..cd31a6a4 Binary files /dev/null and b/Source/ZPM3/bnkbdos3.spr differ diff --git a/Source/ZPM3/clrhist.com b/Source/ZPM3/clrhist.com new file mode 100644 index 00000000..02a39c83 Binary files /dev/null and b/Source/ZPM3/clrhist.com differ diff --git a/Source/ZPM3/clrhist.z80 b/Source/ZPM3/clrhist.z80 new file mode 100644 index 00000000..9b149d24 --- /dev/null +++ b/Source/ZPM3/clrhist.z80 @@ -0,0 +1,49 @@ + +;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; C L R H I S T +; for ZPM3 +; by Simeon Cran +; 26/4/92 +; +; This program clears the ZPM3 function 10 history buffer. It is presented in +; source code form to inform users about how the facility is manipulated. + +; The only real use for clearing the history buffer is as a security feature +; on RZPM3 systems (remote ZPM3 systems (such as BBSes)). Note that individual +; commands may be cleared from the history buffer with control-V. +;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +; SYNTAX: +; CLRHIST Clears the history buffer + +;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; +; The history buffer is cleared by setting bit 7 of offset 85h of the SCB page. +; This offset can not be directly accessed by the SCB function (31h). Instead +; we get the SCB base page with function 31h, and then find the offset from +; there. No other bits in the byte may be touched. +;=============================================================================== + +BDOS equ 5 +deffcb equ 5ch +SCBfunc equ 31h ; Get/Set SCB function number +SCBoff equ 3bh ; Offset in SCB to get SCB base page +CLHoff equ 85h ; Offset in SCB base page of Clear History buffer bit. + + jp start ; Jump over general data + +start: ; Get the address of the bit which controls History buffer clearing + ld c,SCBfunc + ld de,SCBPB + call bdos ; Get base page of SCB + ld h,a + ld l,CLHoff ; HL is now the address of the byte + + set 7,(hl) ; All to do + rst 0 + +SCBPB: ; System control block function parameter block. + db 03bh + db 0 ; Get operation + + \ No newline at end of file diff --git a/Source/ZPM3/loader.asm b/Source/ZPM3/loader.asm new file mode 100644 index 00000000..ef80711d --- /dev/null +++ b/Source/ZPM3/loader.asm @@ -0,0 +1,226 @@ +;=============================================================================== +; LOADER.ASM +; +; BOOTLOADER FOR ROMWBW DISK OPERATING SYSTEMS. +; +; CP/M DISK FORMATS ALLOW FOR RESERVED TRACKS THAT CONTAIN AN IMAGE OF THE +; OPERATING SYSTEM TO BE LOADED WHEN THE DISK IS BOOTED. THE OPERATING SYSTEM +; IMAGE ITSELF IS NORMALLY PREFIXED BY A 1-N SECTORS CONTAINING OS BOOTSTRAP +; CODE AND DISK METADATA. +; +; THE RETROBREW COMPUTING GROUP HAS BEEN USING A CONVENTION OF PREFIXING THE +; OS IMAGE WITH 3 SECTORS (512 BYTES X 3 FOR A TOTAL OF 1536 BYTES): +; +; SECTOR 1: IBM-PC STYLE BOOT BLOCK CONTAINING BOOTSTRAP, +; PARTITION TABLE, AND BOOT SIGNATURE +; SECTOR 2: RESERVED +; SECTOR 3: METADATA +; +; THE HARDWARE BIOS IS EXPECTED TO READ AND LOAD THE FIRST TWO SECTORS FROM THE +; DISK TO MEMORY ADDRESS $8000 AND JUMP TO THAT LOCATION TO BEGIN THE BOOT +; PROCESS. THE BIOS IS EXPECTED TO VERIFY THAT A STANDARD BOOT SIGNATURE +; OF $55, $AA IS PRESENT AT OFFSET $1FE-$1FF. IF THE SIGNATURE IS NOT FOUND, +; THE BIOS SHOULD ASSUME THE DISK HAS NOT BEEN PROPERLY INITIALIZED AND SHOULD +; NOT JUMP TO THE LOAD ADDRESS. +; +;=============================================================================== +; +#INCLUDE "../ver.inc" +; +SYS_ENT .EQU $0100 ; SYSTEM (OS) ENTRY POINT ADDRESS +SYS_LOC .EQU $0100 ; STARTING ADDRESS TO LOAD SYSTEM IMAGE +SYS_END .EQU $1480 ; ENDING ADDRESS OF SYSTEM IMAGE +; +SEC_SIZE .EQU 512 ; DISK SECTOR SIZE +BLK_SIZE .EQU 128 ; OS BLOCK/RECORD SIZE +; +PREFIX_SIZE .EQU (SEC_SIZE * 3) ; 3 SECTORS +; +META_SIZE .EQU 32 ; SEE BELOW +META_LOC .EQU (PREFIX_SIZE - META_SIZE) +; +PT_LOC .EQU $1BE +PT_SIZ .EQU $40 +; +;------------------------------------------------------------------------------- +; SECTOR 1 +; +; THIS SECTOR FOLLOWS THE CONVENTIONS OF AN IBM-PC MBR CONTAINING THE OS +; BOOTSTRAP CODE, PARTITION TABLE, AND BOOT SIGNATURE +; +;---------------------------------------------------------------------------- +; +; THE FOLLOWING BOOTSTRAP CODE IS BUILT TO ASSUME IT WILL BE EXECUTED AT A STARTING +; ADDRESS OF $8000. THIS CODE IS *ONLY* FOR UNA. THE ROMWBW ROM BOOTLOADER +; USES THE METADATA TO LOAD THE OS DIRECTLY. +; + .ORG $8000 + JR BOOT +; +BOOT: + LD DE,STR_LOAD ; LOADING STRING + CALL PRTSTR ; PRINT + CALL PRTDOT ; PROGRESS +; + LD BC,$00FC ; UNA FUNC: GET BOOTSTRAP HISTORY + CALL $FFFD ; CALL UNA + JR NZ,ERROR ; HANDLE ERROR + CALL PRTDOT ; PROGRESS + LD B,L ; MOVE BOOT UNIT ID TO B +; + LD C,$41 ; UNA FUNC: SET LBA + LD DE,0 ; HI WORD ALWAYS ZERO + LD HL,3 ; IMAGE STARTS AT FOURTH SECTOR + CALL $FFFD ; SET LBA + JR NZ,ERROR ; HANDLE ERROR + CALL PRTDOT ; PROGRESS +; + LD C,$42 ; UNA FUNC: READ SECTORS + LD DE,$D000 ; STARTING ADDRESS FOR IMAGE + LD L,22 ; READ 22 SECTORS + CALL $FFFD ; DO READ + JR NZ,ERROR ; HANDLE ERROR + CALL PRTDOT ; PROGRESS +; + LD DE,STR_DONE ; DONE MESSAGE + CALL PRTSTR ; PRINT IT +; + LD D,B ; PASS BOOT UNIT TO OS + LD E,0 ; ASSUME LU IS ZERO + JP SYS_ENT ; GO TO SYSTEM +; +PRTCHR: + PUSH BC + PUSH DE + LD BC,$0012 ; UNIT 0, WRITE CHAR + LD E,A ; CHAR TO PRINT + CALL $FFFD ; PRINT + POP DE + POP BC + RET +; +PRTSTR: + PUSH BC + PUSH HL + LD BC,$0015 ; UNIT 0, WRITE CHARS UNTIL TERMINATOR + LD L,0 ; TERMINATOR IS NULL + CALL $FFFD ; PRINT + POP HL + POP BC + RET +; +PRTDOT: + LD A,'.' ; DOT CHARACTER + JR PRTCHR ; PRINT AND RETURN +; +; PRINT THE HEX BYTE VALUE IN A +; +PRTHEXBYTE: + PUSH AF + PUSH DE + CALL HEXASCII + LD A,D + CALL PRTCHR + LD A,E + CALL PRTCHR + POP DE + POP AF + RET +; +; CONVERT BINARY VALUE IN A TO ASCII HEX CHARACTERS IN DE +; +HEXASCII: + LD D,A + CALL HEXCONV + LD E,A + LD A,D + RLCA + RLCA + RLCA + RLCA + CALL HEXCONV + LD D,A + RET +; +; CONVERT LOW NIBBLE OF A TO ASCII HEX +; +HEXCONV: + AND $0F ;LOW NIBBLE ONLY + ADD A,$90 + DAA + ADC A,$40 + DAA + RET +; +ERROR: + LD DE,STR_ERR ; POINT TO ERROR STRING + CALL PRTSTR ; PRINT IT + HALT ; HALT +; +; DATA +; +STR_LOAD .DB "\r\nLoading",0 +STR_DONE .DB "\r\n",0 +STR_ERR .DB " Read Error!",0 +; + .ORG $ - $8000 ; BACK TO ABSOLUTE ADDRESS +; + .FILL PT_LOC - $,0 ; FILL TO START OF PARTITION TABLE +; +; RESERVE SPACE FOR STANDARD IBM-PC PARTITION TABLE. ALTHOUGH A +; PARTITION TABLE IS NOT RELEVANT FOR A FLOPPY DISK, IT DOES NO HARM. +; THE CONTENTS OF THE PARTITION TABLE MUST BE MANAGED BY FDISK80. +; +PARTTBL .FILL PT_SIZ,0 ; PARTITION TABLE, FILL WITH ZEROES +; +; THE END OF THE FIRST SECTOR MUST CONTAIN THE TWO BYTE BOOT SIGNATURE. +; +BOOTSIG .DB $55,$AA ; STANDARD BOOT SIGNATURE +; +;------------------------------------------------------------------------------- +; SECTOR 2 +; +; THIS SECTOR HAS NOT BEEN DEFINED AND IS RESERVED. +; +;---------------------------------------------------------------------------- +; + .FILL SEC_SIZE,0 ; JUST FILL SECTOR WITH ZEROES +; +;------------------------------------------------------------------------------- +; SECTOR 3 +; +; OS AND DISK METADATA +; +;---------------------------------------------------------------------------- +; + .FILL (BLK_SIZE * 3),0 ; FIRST 384 BYTES ARE NOT YET DEFINED +; +; THE FOLLOWING TWO BYTES ARE AN ADDITIONAL SIGNATURE THAT IS VERIFIED BY +; SOME HARDWARE BIOSES. +; +PR_SIG .DB $5A,$A5 ; SIGNATURE GOES HERE +; + .FILL (META_LOC - $),0 +; +; METADATA +; +PR_WP .DB 0 ; (1) WRITE PROTECT BOOLEAN +PR_UPDSEQ .DW 0 ; (2) PREFIX UPDATE SEQUENCE NUMBER (DEPRECATED?) +PR_VER .DB RMJ,RMN,RUP,RTP ; (4) OS BUILD VERSION +PR_LABEL .DB "Unlabeled$$$$$$$","$" ; (17) DISK LABEL (EXACTLY 16 BYTES!!!) + .DW 0 ; (2) DEPRECATED +PR_LDLOC .DW SYS_LOC ; (2) ADDRESS TO START LOADING SYSTEM +PR_LDEND .DW SYS_END ; (2) ADDRESS TO STOP LOADING SYSTEM +PR_ENTRY .DW SYS_ENT ; (2) ADDRESS TO ENTER SYSTEM (OS) +; +#IF (META_SIZE != ($ - META_LOC)) + .ECHO "META_SIZE VALUE IS WRONG!!!\r\n" + !!! +#ENDIF +; +#IF ($ != PREFIX_SIZE) + .ECHO "LOADER PREFIX IS WRONG SIZE!!!\r\n" + !!! +#ENDIF +; + .END diff --git a/Source/ZPM3/makedos.com b/Source/ZPM3/makedos.com new file mode 100644 index 00000000..5ca35e74 Binary files /dev/null and b/Source/ZPM3/makedos.com differ diff --git a/Source/ZPM3/makedos.txt b/Source/ZPM3/makedos.txt new file mode 100644 index 00000000..a55b1e36 --- /dev/null +++ b/Source/ZPM3/makedos.txt @@ -0,0 +1,90 @@ + Using the MAKEDOS utility. + ========================== + +In an ideal world, MAKEDOS would not be required as every +computer manufacturer would have provided the source code for +your BIOS and the GENCPM.COM utility. This however is not an +ideal world. + +If you have all the appropriate files, use GENCPM with the new +ZPM3 BNKBDOS3.SPR and RESBDOS3.SPR to make a new CP/M system. If +you only have your CP/M 3.0 system file, then read on to learn +how to use MAKEDOS.COM to convert it from CP/M 3.0 to ZPM3. + + +You CP/M 3.0 system file could be called one of a number of +things. Ideally it will be called CPM3.SYS. But it might have +another name such as J14CPM3.EMS (Amstrad computers). Even if you +find it, you must know how the system uses it. For example does +it load it from A0: when you boot your computer? If it does then +you are going to have to return your modified file to A0:. Or +does it keep the file hidden in system tracks of your disk? If +that is the case you will have to find out how to change the +system tracks. Chances are though that the file is read from A0: +on each cold boot. + +Before continuing, make sure you have a backup bootable disk. If +you overwrite your only system file and it fails to work you are +going to be pretty unhappy... so don't let it happen! + +Put your system file, MAKEDOS.COM, BNKBDOS3.SPR and RESBDOS3.SPR +onto the same disk and user area. Note that you must do this ON +THE SAME COMPUTER RUNNING CP/M 3.0 as the system is intended for. +This is most important because MAKEDOS gets information from its +host computer, and if the computer is different from the one the +system is intended for, it will get the wrong information. + +Type MAKEDOS SYSTEM.FIL at the prompt (replacing the SYSTEM.FIL +in the above command with the actual name of your system file +(such as MAKEDOS CPM3.SYS)). MAKEDOS will churn away for a while +and tell you some information. If it doesn't come up with an +error message, all is well and you can proceed. + +MAKEDOS makes three files. RES.DAT, BNK.DAT and another file with +the same name as your original but with the tail .NEW (eg +CPM3.SYS becomes CPM3.NEW). Your original file has not been +touched. Obviously, you have to rename the .NEW file so that it +has the correct name as the system. MAKEDOS doesn't do this for +you just in case something goes wrong... until you rename the new +file, you will still have a copy of the original. So, rename the +new file, put it where it needs to be for it to become the +system, and reboot the computer. All being well, you will come up +running ZPM3. + +Possible problems: +~~~~~~~~~~~~~~~~~~ +A few things may cause a failure and should be checked if you get +an error message. + +You must have enough disk space for all the files. Figure on +having enough for the new system (same size as the old system), +plus about another 16k. + +Another problem is that your serial number gets overwritten. +MAKEDOS uses your CP/M 3.0 serial number to find the BDOS in your +system file. It checks the serial number in the file against the +serial number on the host machine. However, it is possible for +your serial number to become corrupted. In such a case you should +reboot and try again. + +It is unlikely to affect anyone, but MAKEDOS may fail with +system files larger than 48k. If you need to convert such a file, +please contact me. + +Be aware that running MAKEDOS on a machine different from the +machine that the system file is intended for may not result in +any error messages, but will most likely cause the file to not be +converted properly. Always use the host computer, and make sure +you are running CP/M 3.0 or ZPM3. + + +Once you have successfully installed ZPM3, it may not be obvious +that it is running. ZPM3 will act just like CP/M 3.0 for the most +part. The easiest way to check is to enter a few commands, then +press control-W a few times. Unlike CP/M 3.0, ZPM3 remembers more +than one previous command, and you should see them presented to +you with control-W. + +If you have any further trouble, all you can really do is talk to +me, via Z-Node 62 in Perth, Western Australia (09 450 0200). Good +luck. \ No newline at end of file diff --git a/Source/ZPM3/resbdos3.spr b/Source/ZPM3/resbdos3.spr new file mode 100644 index 00000000..2908ed3e Binary files /dev/null and b/Source/ZPM3/resbdos3.spr differ diff --git a/Source/ZPM3/scb.txt b/Source/ZPM3/scb.txt new file mode 100644 index 00000000..78c65af8 --- /dev/null +++ b/Source/ZPM3/scb.txt @@ -0,0 +1,435 @@ +The following has been taken direct from the ZPM3 source and is +provided as a reference. No guarantees are made with regard to +its accuracy. The only SCB entries that you should manipulate in +CP/M 3.0 are the ones published by its authors. + + +;------------------------------------------------------------------------------- +;68 TRAPS FOR WARM BOOT and CONSOLE FUNCTIONS +;------------------------------------------------------------------------------- +; This table allows you to replace certain BIOS functions with new ones in the +; TPA. Because of the banked nature of CP/M 3, simply changing the BIOS +; vector could cause a problem as some BIOS functions need to be in the +; system bank. If you redirect any of these functions, you should replace +; the first jump (0c3h) in each group with a LD HL, (21h). When restoring +; the jumps check first that the BIOS vectors you are restoring are in +; fact to the BIOS and not to another redirection. To do this, make sure +; that the BIOS jumps are pointing above themselves, not down into TPA. + +; For ZPM, the need for the traps has been eliminated. +; Because pre-ZPM programs may attempt to write to the first byte of each +; trap, these bytes can only be used for other things with some caution! + +; Warm boot trap + jp ?wboot ;68 + jp dotpa ;6b +; Console status trap + jp ?const ;6e + jp dotpa ;71 +; Console input trap + jp ?conin ;74 + jp dotpa ;77 +; Console output trap + jp ?conout ;7a + jp dotpa ;7d + +;;; List output trap +;; jp ?list ;80 +;; jp dotpa ;83 + + + db 0c3h ;80 This first byte should not be used as it + ; could get changed by programs which attempt to + ; redirect the printer + + db 0 ;81 Not used yet + + db 0 ;82 Function 59 load user number. Normally + ; function 59 loads from the current user + ; however by setting this byte to a user + ; number + 1, function 59 will load from + ; that user area instead (only works with + ; the ZCCP loader RSX). 0=current user number. + + dw 0 ; -19 83 + ; This word is set to the address of the + ; ZCPR system environment. If it is 0000h + ; the BDOS assumes that the normal CP/M 3 + ; CCP system is running. Otherwise, the BDOS + ; will perform certain functions differently. + ; For example function 152 will use named + ; directories if available, function 10 + ; will the use message buffers for CCP running + ; flag and wheel protection of files is enabled. + + ; Note that Z3PLUS users can set this word + ; once Z3PLUS is installed to enable the + ; extra functions. ZCCP users need not worry + ; as it will be done automatically. + + db 0 ; -17 85 + ; This byte holds control flags for various ZPM3 + ; functions. + ; bit 7: Setting this bit will clear the function + ; 10 history buffer. Write only. + ; bit 6: Controls enabling of the function 10 AUTO + ; COMMAND facility. If set, control-Q toggles + ; the facility on and off. If clear, control-Q + ; has no effect. Read/Write. + ; bit 5: After function 152 has been called, if a DU: + ; D:,U: or DIR: spec has been found, this + ; bit is set and the drive and user is + ; set in the FCB. + ; bit 4: This flag is for system use only. It is + ; set after a function 55 call, but is reset + ; after any other call. + ; bit 3: After function 152 has been called, if a + ; DIR: spec has been parsed, this bit is set + ; and the user and drive is set in the FCB. + ; bits 2-0: Not used yet +;------------------------------------------------------------------------------- +; SYSTEM CONTROL BLOCK (unofficial) +;------------------------------------------------------------------------------- +; None of these is accessed by the resident BDOS or the user + dw 0 ; -16 86 + dw 0 ; -14 88 + dw 0 ; -12 8a + dw 0 ; -10 8c + dw 0 ; -e 8e + + dw 0 ; Bit mapped vector of drives -c 90 + ; with open files since last warm boot. + dw 0 ; Bit mapped vector of drives -a 92 + ; accessed since last warm boot. + + dw 0 ; -8 94 + dw 07h ; -6 96 + dw base+6 ; This word is the address -4 98 + ; of the entry to the BDOS. + ; It can be used to find the + ; actual BDOS as opposed to + ; the top of TPA. + db 0 ; -2 9a + db 0 ; -1 9b +;------------------------------------------------------------------------------ +;9c SYSTEM CONTROL BLOCK +;------------------------------------------------------------------------------ +; The official system control block starts here. In reality, the control block +; begins before this point, but this is the data section that we are +; told about in the DRI literature +; In this section, a code is used to signify which sections of the code +; access the bytes: a * means that that user may read and write the bytes +; a + means that the resident portion of the BDOS accesses the bytes +; a ~ means the banked portion of the BDOS accesses the bytes +; a ~~ means the banked portion of ZPM3 accesses the bytes, but CPM doesn't +scb: + db 0 ;+ Reserved 0 9c + dw 0 ;+ Reserved 1 9d + db 0 ; Reserved 3 9f + db 0 ; Reserved 4 a0 + + db 31h ; BDOS version number (in BCD) 5 a1 + + ; The following four bytes may be used for any purpose. + ; Note that CCP104 used 8 and 9. ZCCP and ZPM3 do not + ; affect these bytes at all. + db 0 ;* Reserved for user 6 a2 + db 0 ;* Reserved for user 7 a3 + db 0 ;* Reserved for user 8 a4 + db 0 ;* Reserved for user 9 a5 + + db 0 ; Reserved 0a a6 + db 0 ; Reserved 0b a7 + db 0 ; Reserved 0c a8 + db 0 ; Reserved 0d a9 + db 0 ; Reserved 0e aa + db 0 ; Reserved 0f ab + + dw 0 ;* Program Error Return Code. 10 ac + ; This 2-byte field can be used by a program to pass + ; an error code or value to chained programs. CP/M 3's + ; conditional command facility also uses this field to + ; determine if a program executes successfuly. The + ; BDOS Function 108 (Get/SET Program Return Code) is + ; used to get/set this value + + ; Following byte holds the base page of the top + ; multiple command RSX (only used by CCP). + db 0 ; Reserved 12 ae + + ; The following bytes are the default disk and user + ; of the CCP. When the CCP is run, the disk and user + ; is restored to these values unless flagged not to + ; by the chain command. + db 0 ; CCP disk 13 af + db 0 ; CCP user number 14 b0 + + ; The following word holds the address of the next + ; command to get if running multiple commands or + ; shells. It should not be set by the user. + + dw 0 ; Multiple command pointer. CCP 15 b1 + + db 0 ; System flag CCP use 17 b3 + ; This byte is bit mapped as follows: + ; Bit 0 Submit flag (set if a file beginning with '$' + ; is found, cleared by CCP) + ; 1 RSX flag (set by loader when it loads a null + ; file with RSXs attached (indicates to CCP + ; not to attempt to remove the RSXs until the + ; second warm boot). May be set by RSXs + ; 2-5 unknown (probably used by utilities) + ; 6 Change default DU to last program's DU + ; when chaining. + ; 7 Chain flag. Set to indicate to CCP that + ; there is a command to chain to at 080h. + + db 0 ; System flag CCP use 18 b4 + ; This byte is bit mapped as follows: + ; Bit 0 Display command flag + ; 1 Display command flag + ; 2 Unknown + ; 3 File type search order + ; 4 File type search order + ; 5 Reset disk system + ; 6 "GET" RSX flag (set if GET RSX is redirecting) + ; 7 CCP running flag + ; Bit 7 is the only one used by the BDOS (in function 10) + + db 0 ; System flag CCP use 19 b5 + ; This byte is bit mapped as follows: + ; Bit 0 Unknown + ; 1 Cold boot flag + ; 2-7 Unknown + + db 0 ;* Console Width 1a b6 + ; This byte contains the number of columns + ; (characters) per line on your console relative + ; to zero. Most systems default this value to + ; 79. You can set this default value by using + ; GENCPM or the DEVICE utility. The console width + ; value is used by CP/M 3 in BDOS function 10. It + ; is not used by ZPM3. Typing a character into the + ; last position of the screen, as specified by the + ; Console Width field, must not cause the terminal + ; to advance to the next line. + + db 0 ; Console Column Position 1b b7 + ; This byte contains the current console column postion + + db 0 ;* Console Page Length 1c b8 + ; This byte contains the number of lines (rows) on your + ; console relative to zero. Most systems default this + ; value to 23. This default value may be changed by + ; using GENCPM or the DEVICE utility. + + db 0 ; Reserved 1d b9 + ; The following word is used by function 10 and points + ; to the next character to get in an initialised + ; function 10 buffer. If a ^C termination occurs or + ; if a null terminator is found before a CR or LF, + ; this word is set 0. By setting DE NZ and pointing + ; this word to a buffer before calling it, you + ; can have it initialize buffers other than + ; the default DMA. + + dw 0 ;+~Reserved 1e ba + ; The following word is used by multiple commands and + ; shells. When function 10 retrieves information from + ; an initialised buffer, it stores the next character + ; position at offset 1e and here at 20. If a ^C + ; termination occurs, 1e is set to 00, but 20 is left + ; as it was so that the next command can be retrieved. + ; Therefore, if 1e is 00 and 20 is NZ it means that + ; a ^C termination happened + + dw 0 ;~ Reserved 20 bc + + + ; Redirection flags (following) for each of the five + ; logical character devices. If your system's BIOS + ; supports assignment of logical devices to physical + ; devices, you can direct each of the five logical + ; character devices to any combination of up to 12 + ; physical devices. The 16 bit word for each device + ; represents the following: + ; + ; Each bit represents a physical device where bit 15 + ; corresponds to device zero and bit 4 corresponds to + ; device 11. Bits zero through 3 are reserved for + ; system use and are used for redirection to disk files. + ; + dw 0 ;* CONIN Redirection Flag 22 be + dw 0 ;* CONOUT Redirection Flag 24 c0 + dw 0 ;* AUXIN Redirection Flag 26 c2 + dw 0 ;* AUXOUT Redirection Flag 28 c4 + dw 0 ;* LIST Redirection Flag 2a c6 + + db 0 ;* Page Mode 2c c8 + ; If this byte is set to zero, some CP/M 3 utilities + ; and CCP built in commands display one page of data + ; at a time; you display the next page by pressing + ; any key. If this byte is not set to zero, the system + ; displays data on the screen without stopping. To + ; stop and start the display, you can press CTRL-S and + ; CTRL-Q respectively. + + db 0 ; Default page mode 2d c9 + + db 0 ;* ~~ 2e ca + ; Determines if CTRL-H is interpreted as a rub/del + ; character. If this byte is set to 0, then CTRL-H is + ; a backspace character (moves back and deletes). If + ; this byte is set to 0ffh, then CTRL-H is a rub/del + ; character, echoes the deleted character. + ; Under ZPM3, the byte has no effect. It should not + ; be used however as it may be written to by + ; applications. + + db 0 ;* 2f cb + ; Determines if rub/del is interpreted as CTRL-H + ; character. If this byte is set to 0, then rub/del + ; echoes the deleted character. If this byte is + ; set to 0ffh, then rub/del is interpreted as a + ; CTRL-H character (moves back and deletes). + ; Under ZPM3, the byte has no effect. It should not + ; be used however as it may be written to by + ; applications. + + db 0 ;~ Reserved 30 cc + + ; Following two bytes are probably used by CP/M3 utilities + db 0 ; Reserved 31 cd + db 0 ; Reserved 32 ce + + dw 0 ;*+ Console Mode 33 cf + ; This is a 16 bit system parameter that determines + ; the action of certain BDOS Console I/O functions. + + dw bnkbuf ; Address of 128 byte buffer 35 d1 + + db '$' ;*+ Output delimiter character. 37 d3 + ; The default output delimiter character is $, but + ; you can change this value by using the BDOS Function + ; 110 Get/Set Output Delimiter. + + db 0 ;* List Output Flag 38 d4 + ; If this byte is reset to 0, console output is not + ; echoed to the list device. If this byte is set + ; to 1, console output is echoed to the list device. + + db 0 ; Scroll flag 39 d5 + ; Following bits set when in system bank and: + ; Bit 7 is set when function 11 is checking the status. + ; Bit 6 is set when function 2 is checking input. + ; Note that raw input (function 6 and function 2 raw) + ; will not set these bits. + + dw scb ; Holds the address of the SCB 3a d6 + + dw 0080h ;+ Current DMA Address. 3c d8 + ; This address can be set by BDOS Function 26. The + ; CCP initializes this value to 0080h. BDOS Function + ; 13, Reset Disk System also sets the DMA address to + ; 0080h. + + db 0 ; Current Disk. 3e da + ; This byte contains the currently selected default + ; disk number. This value ranges from 0-15 + ; corresponding to drives a-p, respectively. BDOS + ; Function 24, Return Current Disk, can be used to + ; determine the current disk value. + + dw 0 ; BDOS variable 'INFO' 3f db + ; This word is used by the banked portion of the + ; BDOS. It is normally an entry parameter. + + db 0 ; FCB flag 41 dd + ; If this byte = 0ffh, the word at 03fh is a valid + ; FCB address. + + db 0 ; Same drive flag 42 de + + db 0 ;+ BDOS function for error 43 df + + db 0 ; Current User Number. 44 e0 + ; This byte contains the current user number. This + ; value ranges from 0-15. BDOS Function 32, + ; Get/Set User Code can change or interrogate + ; the currently active user number. Under ZPM3 you may + ; change the currently active user number directly + ; by writing to this byte. + + dw 0 ;+ Reserved 45 e1 + ; Holds the current directory entry number. Lower + ; two bits are the search return code. + + dw 0 ;+ Search FCB address 47 e3 + ; Holds the FCB address of the last search for + ; first/next operation. + + db 0 ;+ Search type flag 49 e5 + ; 0=? in drive code search. + ; 0fh=normal search. + + db 01 ;* BDOS Multi-Sector Count. 4a e6 + ; This field is set by BDOS Function 44, Set Multi- + ; Sector Count. + + db 0 ;* BDOS Error Mode. 4b e7 + ; This field is set by BDOS Function 45, Set BDOS + ; Error Mode. If this byte is set to 0ffh, the + ; system returns to the current program without + ; displaying any error messages. + + + db 0 ;* Drive Search Chain 1 4c e8 + db 0ffh ;* Drive Search Chain 2 4d e9 + db 0ffh ;* Drive Search Chain 3 4e ea + db 0ffh ;* Drive Search Chain 4 4f eb + + db 0 ;* Temporary File Drive 50 ec + + db 0 ; Error Drive. 51 ed + ; This byte contains the drive number of the selected + ; drive when the last physical or extended error + ; occured. + + db 0 ; Reserved 52 ee + db 0 ; Reserved 53 ef + + db 0 ; Media Flag 54 f0 + ; This flag may be set by the BIOS to indicate that + ; a drive door has opened thus signalling the BDOS + ; to relog the drive if required. + db 0 ; Reserved 55 f1 + db 0 ; Reserved 56 f2 + + db 080h ; BDOS Flags. 57 f3 + ; bit 7= expanded error messages + ; 6= single byte allocation vectors + + dw 0ffffh ;* Date in days in binary since 1 Jan 78 58 f4 + db 0ffh ;* Hour in BCD 5a f6 + db 0ffh ;* Minutes in BCD 5b f7 + db 0ffh ;* Seconds in BCD 5c f8 + +COMBASE: + dw 0 ; Common Memory Base address 5d f9 + ; This value is zero for nonbanked systems, and + ; nonzero for banked systems. Because the base + ; address must reside on a page boundary, the + ; first byte will always be 0. The second byte + ; is the important one being the common memory + ; base page. + + jp bnkdos2 ; Pointer to second entry in banked DOS 5f fb + ; This entry handles the displaying of errors to + ; the user + + dw start ; Top of user TPA 62 fe + ; This word always reflects the top of TPA and + ; should be the same as the word at 0006h unless a + ; transient changes (0006h) without knowing about + ; this word + \ No newline at end of file diff --git a/Source/ZPM3/setz3.com b/Source/ZPM3/setz3.com new file mode 100644 index 00000000..4ddb374f Binary files /dev/null and b/Source/ZPM3/setz3.com differ diff --git a/Source/ZPM3/setz3.z80 b/Source/ZPM3/setz3.z80 new file mode 100644 index 00000000..947f3006 --- /dev/null +++ b/Source/ZPM3/setz3.z80 @@ -0,0 +1,80 @@ + +;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +; S E T Z 3 +; for ZPM3 +; by Simeon Cran +; 30/3/92 +; +; This program automatically sets the system environment address in the ZPM3 +; SCB for Z3PLUS users. Certain advanced ZCPR facilities such as wheel +; protection of files will then be activated. +; +; Z3PLUS users should run SETZ3.COM once when they start up Z3PLUS and again +; when returning to regular (non-Z-System) operation. When run after Z3PLUS +; is started, the SCB environment address word is set with the ZCPR environment +; address. When run after returning to regular operation, the SCB environment +; address word is cleared to 0000h. +; +;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +;=============================================================================== + +BDOS equ 5 +deffcb equ 5ch +SCBfunc equ 31h ; Get/Set SCB function number +SCBoff equ 3bh ; Offset in SCB to get SCB base page +Z3ENVoff equ 83h ; Offset in SCB base page of the ZCPR system + ; environment pointer. + + jp start ; Jump over general data + db 'Z3ENV' + db 1 +Z3ENV: dw 0 + dw 0 +HELPmsg: + db ' SYNTAX:' + db 10,13 + db ' SETZ3' + db 10,13 + db ' Sets the ZCPR environment address in the SCB,' + db 10,13 + db ' or else clears it if not running ZCPR.' + db 10,13 + db ' SETZ3 // Displays this brief help message' + db '$' + +HELP: ld de,HELPmsg +MSGexit: + ld c,9 + call bdos + rst 0 + +start: ; Get the address of the SCB environment address pointer. + ld de,(Z3ENV) + ld a,d + or e ; Has it been set by Z3PLUS? + jr nz,Zinstall ; Jump if it has + ex de,hl + ld a,d + or e ; Was it in HL instead? + jr nz,Zinstall +Zinstall: ; DE holds 0 if uninstalling, otherwise the address of the + ; ZCPR evironment descriptor. + push de + ld c,SCBfunc + ld de,SCBPB + call bdos ; Get base page of SCB + ld h,a + ld l,Z3ENVoff ; HL is now the address of the word + pop de + ld (hl),e + inc hl + ld (hl),d ; Set it appropriately + rst 0 ; And exit. + + +SCBPB: ; System control block function parameter block. + db 03bh + db 0 ; Get operation + + \ No newline at end of file diff --git a/Source/ZPM3/version.not b/Source/ZPM3/version.not new file mode 100644 index 00000000..644d01e0 --- /dev/null +++ b/Source/ZPM3/version.not @@ -0,0 +1,35 @@ + ZPM3 by Simeon Cran. + ==================== +ZPM3 is a Z80 coded BDOS replacement for banked CP/M 3.0 systems. + +This is release 0010 on 1/2/93. + +Since version 0009 the .SPR files have been updated. They are +slightly faster and will possibly work on the Bondwell or other +machines that they seemed to fail on before. ZPM3LDR.REL has also +been modified. The main improvements throughout should be in +speed. The differences may not be obvious, but you should upgrade +to this latest version. SJC. + +Files in this release are: + +VERSION.NOT +ZPM3.TXT +RESBDOS3.SPR +BNKBDOS3.SPR +AUTOTOG.COM +AUTOTOG.Z80 +SETZ3.COM +SETZ3.Z80 +MAKEDOS.COM +MAKEDOS.TXT +BIOS.TXT +SCB.TXT +ZPM3LDR.TXT +ZPM3LDR.REL +CLRHIST.COM +CLRHIST.Z80 +ECHOTERM.COM +============ +17 files +============ \ No newline at end of file diff --git a/Source/ZPM3/zinstal.zpm b/Source/ZPM3/zinstal.zpm new file mode 100644 index 00000000..1a6b2c4c Binary files /dev/null and b/Source/ZPM3/zinstal.zpm differ diff --git a/Source/ZPM3/zpm3.txt b/Source/ZPM3/zpm3.txt new file mode 100644 index 00000000..7df5cb8f --- /dev/null +++ b/Source/ZPM3/zpm3.txt @@ -0,0 +1,483 @@ + + Z P M 3 by Simeon Cran + ======================== + + A Z80 coded CP/M 3.0 compatible BDOS replacement. + + The first public release: 27/3/92 + This document dated: 16/6/92 + + Distributed at: Z-Node 62 (Perth, Western Australia) + V21,V22,V22bis 09 450 0200 + + +WELCOME TO ZPM3 +~~~~~~~~~~~~~~~ +Welcome to the best CP/M compatible operating system for Z80 +based computers with banked memory. The best? Yes, we believe so. +CP/M 3.0 has had bad press, but the fact is that it is faster +than CP/M 2.2 ever was, and it offered more integrated +facilities. Perhaps it was all the Z80 replacement BDOSes for +CP/M 2.2 which stole the limelight from CP/M 3.0, or was it just +that few computers had the required banked memory? + +Whatever the reason for CP/M 3.0's lack of success in the +marketplace, there are still plenty of users who will stand by +its wonderful facilities and speed. For those users ZPM3 provides +the long awaited Z80 coded update. + +ZPM3 offers all the good things that CP/M 3.0 does, and then it +offers more. Because ZPM3 is written in Z80 code rather than the +8080 code of CP/M 3.0, it can do everything that CP/M 3.0 does, +but in much less space. With the extra space recovered, ZPM3 +packs in a number of new facilities. Yet the whole package fits +in exactly the same space as CP/M 3.0 so you can directly replace +your old CP/M 3.0 BDOS with ZPM3 without a worry. + +ZPM3 is also fast. Faster, in fact, than CP/M 3.0. This is +possible because the rich Z80 instruction set allows many +algorithms to be implemented more efficiently. In addition, the +extra space available in ZPM3 has been put to use to further +optimise the code. Lots of small optimisations smooth the +execution flow, so ZPM3 becomes the fastest operating system on +most banked CP/M computers. + + +THE FEATURES +~~~~~~~~~~~~ +ZPM3, in addition to complete CP/M 3.0 compatibility, offers the +following features: + + +Random Read Bug fixed. +++++++++++++++++++++++ +Maybe you didn't know, but CP/M 3.0 has a bug. It affects random +reads under very specific circumstances, and can result in a +program thinking that you don't have some pieces of data in a +file when in fact you do. The bug would occur very, very rarely, +but it is real. ZPM3 finally squashes it. + + +Protected SCB User code ++++++++++++++++++++++++ +The System Control Block of CP/M 3.0 was a revolution at the +time. ZCPR has a system environment and most other operating +systems have other similar structures, but the SCB of CP/M 3.0 +was one of the very first. + +Unfortunately, Digital Research never properly documented it, and +some programmers found things out about it that weren't quite +true and started programming accordingly. As well, because it is +available in the TPA bank, runaway programs can overwrite it +causing problems. + +Mostly though, the SCB will survive, or at least any problems +will be so obvious that the user will realise that a crash has +occurred and will reboot. A real problem exists with the CP/M 3.0 +code however when the user value is written over with a value +above 15. Many programs now directly write to this byte, and if +they put a value in that is above 15, all sorts of havoc can +happen with the disk system. Actually, CP/M 3.0 will handle user +areas above 15 with this method, and all seems ok until the +operating system mistakes one of these directory entries as an +XFCB. Simply put, user areas above 15 must not be used with CP/M +3.0. + +ZPM3 has code which prevents these problems, making the system +even more stable. + + +Obsoleted Trap system. +++++++++++++++++++++++ +One of the problems of the banked operating system was that it +was possible to redirect the BIOS to code below common memory, in +which case the banked BDOS could not access it. One solution is +to call all BIOS code from common memory, but this involves a +bank switch for every BIOS call, and this slows things down +considerably. + +CP/M 3.0 got around the problem by providing special code just +below the SCB. If you redirected the BIOS, you also had to change +this code which caused a bank switch when your new BIOS routine +was called. When you removed the redirection, you also had to +restore the special code. + +This system has major drawbacks. For a start, if you redirect the +BIOS, then another program redirects your redirection, then you +remove your first redirection (along with the special code), the +bank switch won't happen for the second redirection and the +system will crash. + +If a CP/M 2.2 program tried to do the redirection, it would know +nothing about CP/M 3.0 and would not adjust the special code, so +a crash would result in that case too. + +The special code was called the "Trap System" as it was meant to +trap redirection (as long as you set the trap). ZPM3 has +eliminated the need for the traps. They are still there, and +programs can still fiddle with them, but it doesn't matter how +they are set, they are ignored. There is simply no need for them +anymore. And this has been achieved without a performance +penalty. In fact, in the case of a program which sets the traps +but forgets to restore them, performance is now much better. + + +Semi-Permanent Read Only status for drives. ++++++++++++++++++++++++++++++++++++++++++++ +In recent years, a trend in CP/M 2.2 is to make drives which have +been set read only to remain that way until explicitly changed by +function 37. ZPM3 now adopts this logic. Previously a control-C +would return a read only drive to read write. The advantage is +that a program can now make a drive read only for a session and +know that it will stay that way. + + +ZCPR compatible function 152 +++++++++++++++++++++++++++++ +Function 152 is the CP/M 3.0 parser. It was a great innovation at +the time as parsing is one of the more tedious aspects of +programming for CP/M. Unfortunately, almost as soon as it +appeared, it was made obsolete by the fact that it didn't handle +references to user number (DU references). A line such as +A:FILE.TYP would be correctly parsed, but A3:FILE.TYP would not. +CP/M 3.0 programs would often parse the drive and user +separately, then give function 152 the line without the DU: +reference. All this extra work should not have been necessary if +CP/M 3.0 had included user number parsing. + +ZPM3 parses the user number, and goes even further by handling +named directories for ZCPR. This is possible as long as you set a +special word in the SCB which tells ZPM3 where to find the ZCPR +system environment descriptor. ZCCP, a companion CCP for ZPM3, +handles this automatically, but for Z3PLUS users, a special +utility is available which automatically sets this word. + +The result is that CP/M 3.0 programs will not balk at DU: +references and ZPM3 aware programs can use the full DU: and DIR: +facilities of function 152. It has also made the brilliant ZCCP +code possible. + + +New Functions 54 and 55 ++++++++++++++++++++++++ +Datestamps in CP/M 3.0 are wonderful, but difficult to +manipulate. Two new functions make them easier to handle and at +the same time give compatibility to Z80DOS aware programs. + +Function 54 (Get Stamp) returns a Z80DOS compatible datestamp. +Any program (such as many directory programs) which recognise the +Z80DOS standard can make use of function 54. There is only one +slight difference between Z80DOS datestamps and ZPM3's which you +should be aware of. Z80DOS will return a correct datestamp after +any successful open or search of any extent. ZPM3 can only return +a correct datestamp after a successful open or search of the +first extent of the file. This is because CP/M 3.0 datestamps are +only saved for the first extents of each file, in order to +provide the highest performance. + +Even more interesting is Function 55 (Use Stamp) which provides a +mechanism for changing datestamps on files. Trying to do this +with CP/M 3.0 was virtually impossible because it involved direct +sector writes. With Function 55 you can simply set the stamp and +then write. + + +Wheel protected files ++++++++++++++++++++++ +If you are using a ZCPR system (ZCCP or Z3PLUS), ZPM3 has access +to the wheel byte and supports wheel protected files. Such files +act normally if the wheel is set (signifying a priveleged user), +but if the wheel is not set, the files can not be changed. This +is of most benefit to BBS systems. The implementation is +virtually the same as most current Z80 CP/M 2.2 compatible +BDOSes. + + +Better error messages ++++++++++++++++++++++ +CP/M 3.0 introduced the best error messages that CP/M had ever +had. ZPM3 goes further. The main difference you will notice is +that the user number as well as the drive is shown in the error +message. This is invaluable in helping you identify which file +might have caused a problem. + + +Function 10 history buffer and improved editing. +++++++++++++++++++++++++++++++++++++++++++++++++ +Function 10 is used by the CCP to input command lines. Many other +programs use function 10 for input. + +CP/M 3.0 introduced a history buffer for function 10. You press +control-W and you were returned the last command. It is a great +facility, but because it only remembers one command it is rather +limited. There have been RSXes written which give a much larger +history buffer, but RSXes take up extra program memory so are +undesirable. + +ZPM3 gives a large (approximately 250 bytes) history buffer which +can store multiple commands. It also makes very intelligent use +of the buffer so that identical commands are not stored twice, +and commands of less than three characters are not stored. The +history buffer takes up no additional memory, and is always +available. + +For security, it is possible to clear the history buffer so that +other users can not see what commands you have used. + +The ZPM3 history buffer feature is so good, that for many users, +the ZPM3 upgrade is completely justified by it. + +As part of the history buffer system, ZPM3 also offers a facility +called Automatic Command Prompting. This can be disabled, or can +be made switchable from the keyboard. When it is on, ZPM3 tries +to fill in the rest of your command based on what commands you +used most recently. It is like magic, and can save you typing out +complicated commands many times. In effect, it looks through the +history buffer for you and finds the command it thinks you want. +As you keep typing, if it turns out that the command doesn't +match anymore, it will try to match another command, and if it +can't, it lets you make the command by yourself. This facility is +quite amazing to watch. + +And to integrate the history buffer and the automatic command +prompting, function 10 has the best command line editing you'll +find anywhere. Most of the control keys do something when you are +editing a function 10 line, and for the most part they mimic the +standard WordStar/NewWord/ZDE functions. You can jump to +different words in the command, delete individual words, delete +individual letters, insert letters, and a whole lot more. + + +Here is a list of what the various control keys do for function +10: + +A Move left one word +B Go to the beginning or end of the line +C Warm boot if at start of line, otherwise nothing +D Go right one character +E Go backwards one command in the history buffer +F Go right one word +G Delete current character +H Destructive backspace +I +J Enter line +K Delete all to the right +L +M Enter line +N +O +P Toggle printing +Q Toggle automatic command prompting (if enabled) +R +S Go left one character +T Delete current word +U Add current line to history buffer +V Clear line and delete from history buffer +W Go forwards one command in the history buffer +X Delete all to the left +Y Clear the whole line +Z + + +CPMLDR.REL bug fixed. ++++++++++++++++++++++ +If you have ever tried to use the CPMLDR.REL code supplied with +CP/M 3.0 to load a CPM3.SYS file larger than 16k, you have +probably come across the CPMLDR.REL bug. The computer probably +crashed, and you were left wondering what you did wrong in your +bios. + +Well CPMLDR.REL has a bug. To solve this for you ZPM3 comes with +ZPM3LDR.REL which directly replaces CPMLDR.REL. It is also +somewhat better in that all the messages, and the fcb for loading +CPM3.SYS, are at the start of the file along with plenty of spare +room. As a result you can easily patch the signon and error +messages to say whatever you like and even change the FCB to load +a file called something other than CPM3.SYS. + + + + +All About the Random Read Bug. +============================== +Never heard of it? Well it's there in CP/M 3.0. I spent a lot of +time trying to work out what it was and just why it was +happening, and if you are interested, here are the details. + +CP/M 3.0 uses the Record Count byte of an active FCB a little +differently from the way CP/M 2.2 does. It is mentioned in the +CP/M 3.0 manuals that the record count may contain numbers +greater than 128, but in such a case it implies that the record +count is really 128. CP/M 2.2 would not return record counts +greater than 128. + +The reason for the use of the record count in this way is to help +speed up some of the logic used to find records in a file. It +works very well for sequential access. When it comes to random +access, the system has some failings. + +The idea behind CP/M 3.0's unusual use of the record count is to +keep the record count of the last logical extent of the current +physical extent always in the Record Count byte. When accessing +extents before the last one, bit 7 of the byte is set. That way +it will always be at least 128 for logical extents before the +last (which CP/M 3.0 translates to mean equal to 128), and the +lower 7 bits are used as convenient storage for the record count +of the last logical extent. This is particularly convenient +because it means there is no need to go and read the directory +entry again when it comes time to read the last logical extent. + +I hope you have followed that! In sequential access, this scheme +is great. The problem occurs with random access. In this case it +is possible to access a logical extent which has no records in +it. This could be any logical extent past the last one. In such a +case the record count must be returned as 0 (which is correct). +If we then go back to a previous logical extent in the same +physical extent, CP/M 3.0 gets confused and assumes that there +must be 128 records in that extent because the one we just came +from had no records and we are now accessing an earlier extent. +You're probably well and truly lost by now! + +Anyhow, the assumption that CP/M 3.0 makes is quite wrong. The +record count ends up being set to 128, a read is allowed to go +ahead as if nothing was wrong, no error is returned, and the +record count remains incorrectly set until a different physical +extent is opened. The result could be chaos, but mostly it just +means that a program returns the wrong information. + +Remember, a logical extent is always 16k. A physical extent can +be a multiple of 16k and is all the data described by one +directory entry. If your system has physical extents which are +16k, you would never have the problem because a new physical +extent would be properly opened for every new logical extent that +was accessed. + +Typically though, a physical extent is 32k, so it holds 2 logical +extents. The problem won't arise until the file grows past the +32k mark in such a case. And when the file gets over 48k the +problem can't occur again until it gets over 64k... and so on. +Even then, it can only happen if reads are attempted to +particular extents in a particular order. So you shouldn't be too +surprised if the bug hasn't been too noticeable to you. + +ZPM3 squashes the bug once and for all by using the correct +logic. In the situation where the bug would normally occur, ZPM3 +makes sure it gets the correct record count information, and the +reads return the correct record count every time. + +If you are interested in seeing a demonstration of the bug in +action (on CP/M 3.0) and comparing it with ZPM3, there is a file +floating around various bulletin boards which contains +demonstrations for the bug and an RSX to fix it. The RSX is a +less than perfect way of overcoming the bug, although it seems to +work. However, now that you have ZPM3, you don't need to worry. + + + + +Other things you should know about ZPM3 +======================================= +ZPM3 has worked on EVERY CP/M 3.0 system tried so far except one. +This is a Bondwell computer, and as yet it isn't clear why it +won't work. I will study the source code of its BIOS and come up +with a fix shortly. + +The MAKEDOS.COM utility is not perfect (as mentioned previously) +and it seems that nobody has managed to get it to work with the +Commodore C128 system. You must use the conventional method for +installing ZPM3 on such systems. + +If you have a computer that ZPM3 will not install on with MAKEDOS +and you do not have access to the files required to do a +conventional install, please contact me. I am interested in +making ZPM3 as universal as possible and will help you to install +it on your system. + +The ESCAPE key is ignored by function 10. There has been some +lively discussion about this but the decision is final: it stays +ignored. Remember what function 10 is for and you will understand +why I made it ignore the ESCAPE key. The argument against this +has been from people who control their terminals from the command +line. Apparently some people type in an escape sequence at the +command line (which CP/M 3.0 will not output correctly anyhow +(converting the escape character to ^[)) then press return to +have the CCP echo back the line including the escape character. + +Sorry folks, that is a KLUDGE in my books! Anybody using Z-System +would of course use an ALIAS and ECHO to do this properly, but +for those who will continue to complain that I have sacrificed +CP/M 3.0 compatibility I am now including ECHOTERM.COM to solve +your problems. Run it and whatever you type will be sent to the +terminal correctly after you press RETURN. Press RETURN twice to +exit the program. + +And a reminder that the ability to put control characters into +function 10 lines was always limited by the fact that some +control keys were used to edit the command line. CP/M 3.0 added +even more, and ZPM3 uses virtually all the control keys. The few +that aren't used are ignored, and this is in fact a FEATURE which +guarantees that unusable characters can't get into function 10 +lines by accident. + + + + +LEGALS and SUCH +=============== +The ZPM3 package is supplied free of charge, on the condition +that you don't use it to make money. If you want to use it +commercially you must contact me to get the OK (and negotiate our +fee). + +If you find anyone (except myself) charging money for ZPM3, +please inform me! + +Nobody is making any guarantees about this software. None at all. +If it causes your house to burn down, or a divorce, or just a bad +day, this is unfortunate, regrettable, but there is nothing that +I can or will do about it. You have been warned. + +The ZPM3 package must only be distributed in the form that you +found it. Do not change or add anything. Don't even change it +into a different type of archive. Just leave it alone. However +you are free to distribute it to as many places and people that +you can. Just don't charge for it. + + + +If in using ZPM3 you find that it doesn't act as described, +please forward the details to me so that either the ZPM3 code or +the documentation can be changed. If you would like further +details, please forward your specific questions to me. SJC. + + + + +As a service to all our ZPM3 fans, the latest version of the ZPM3 +package can now be ordered. At this stage we can only supply IBM +formatted 3.5 inch 720k disks, however if you are keen enough +that shouldn't matter. ZPM3 remains free, however this service +will cost you $15 Australian (for the disk, copying, postage and +packing) to most places in the Western World (others by +arrangement). + +This is a good way to guarantee you have the latest version, and +to guarantee that your package has not been corrupted by some +unscrupulous person. + +When we fill your order, we will make sure to include the latest +demonstration copy of MYZ80 - the fastest and best Z80 emulator +for IBM AT (and better) compatibles. MYZ80 can run ZPM3 with +ease. It also handles ZCPR and CP/M 2.2. And yes, we do mean +FASTEST. + +Send your international money order to: + + Software by Simeon + ZPM3 Package + 2 Maytone Ave + Killara NSW + Australia 2071 + +Your order will be promptly filled. + \ No newline at end of file diff --git a/Source/ZPM3/zpm3ldr.rel b/Source/ZPM3/zpm3ldr.rel new file mode 100644 index 00000000..bca9c4be Binary files /dev/null and b/Source/ZPM3/zpm3ldr.rel differ diff --git a/Source/ZPM3/zpm3ldr.txt b/Source/ZPM3/zpm3ldr.txt new file mode 100644 index 00000000..9307b039 --- /dev/null +++ b/Source/ZPM3/zpm3ldr.txt @@ -0,0 +1,68 @@ + + + ZPM3LDR.REL + =========== + + A CPMLDR.REL replacement for CP/M 3.0 and ZPM3 systems. + + +CPMLDR.REL, as supplied by DRI, has a bug on some systems which +prevents the loading of CPM3.SYS files larger than 16k. This is a +significant problem especially if you intend to enlarge your bios +or increase the number of buffers allocated to your system. + +ZPM3LDR.REL was developed primarily to overcome this bug. +ZPM3LDR.REL is able to load CPM3.SYS files up to the maximum +possible system size without any problem. ZPM3LDR.REL also offers +some convenient enhancements. + + +The usual way to use ZPM3LDR.REL is exactly as you would use +CPMLDR.REL: link it to your loader bios and SCB.REL files to make +the loader program which must be installed onto your system +tracks. Before you install the program however, you may choose to +patch the file at locations provided for in ZPM3LDR.REL. + +The messages issued by ZPM3LDR can be changed. They take a +standard '$' terminated form (as used by BDOS function 9). Using +a debugger such as SID.COM, you should be able to view these +messages and note that they have extra '$' terminators at then +end of each. This is the room in which you may expand or alter +the ZPM3LDR messages. Just remember not to overwrite the next +message. + +The CPM3.SYS FCB will be visible there too, allowing you to +change it so that ZPM3LDR will load a file of a different name +instead. + +The copyright message is there but not to show that DRI has +copyright on ZPM3LDR.REL (which it doesn't!). That is part of an +advanced feature of ZPM3LDR.REL which allows it to check for +valid CPM3.SYS files. CPMLDR.REL would attempt to load any file +called CPM3.SYS, even if it wasn't really a CP/M 3.0 system file. +The results could be catastrophic. ZPM3LDR.REL will always check +for the 112 byte signature at the start of the file, and will +refuse to load CPM3.SYS unless the signature is correct. + +This has another advantage. You may patch this signature to +whatever you wish. Then, after generating your CPM3.SYS file +(using GENCPM.COM) you should patch it too. The patch might be to +put in the version of your BIOS or some such thing. On MYZ80, I +use this system whenever I change the MYZ80 80x86 bios in such a +way that the CPM3.SYS files won't work properly anymore. That +way, any old CPM3.SYS files that are not valid anymore, won't get +loaded accidentally. + +For your information, the first 128 bytes of CPM3.SYS always +begin with 6 bytes which tell CPMLDR where to load each section +and where the cold boot entry is. After that there are 10 bytes +of 0. ZPM3LDR does not check these bytes against anything so you +can patch them with whatever you like. + +The next 112 bytes would normally contain the DRI copyright +message, the serial number of your system, then a fill of 0 bytes +to the next record. Because ZPM3LDR looks for the copyright +message as a signature of a valid CPM3.SYS, if it is changed, you +will have to change ZPM3LDR as well. + + \ No newline at end of file diff --git a/Source/ZSDOS/Build.cmd b/Source/ZSDOS/Build.cmd index ad45fee5..984b376a 100644 --- a/Source/ZSDOS/Build.cmd +++ b/Source/ZSDOS/Build.cmd @@ -13,3 +13,11 @@ set ZXINCDIR=%TOOLS%/cpm/include/ zx ZMAC -ZSDOS -/P zx LINK -ZSDOS.BIN=ZSDOS[LD800] + +tasm -t80 -g3 -fFF loader.asm loader.bin loader.lst + +copy /b ..\zcpr-dj\zcpr.bin + zsdos.bin + ..\cbios\cbios_wbw.bin zsys_wbw.bin +copy /b ..\zcpr-dj\zcpr.bin + zsdos.bin + ..\cbios\cbios_una.bin zsys_una.bin + +copy /b loader.bin + zsys_wbw.bin zsys_wbw.sys +copy /b loader.bin + zsys_una.bin zsys_una.sys diff --git a/Source/ZSDOS/Clean.cmd b/Source/ZSDOS/Clean.cmd index 1d355724..65ff8877 100644 --- a/Source/ZSDOS/Clean.cmd +++ b/Source/ZSDOS/Clean.cmd @@ -9,3 +9,4 @@ if exist *.hex del *.hex if exist *.rel del *.rel if exist *.sym del *.sym if exist *.err del *.err +if exist *.sys del *.sys diff --git a/Source/ZSDOS/Clock/Build.cmd b/Source/ZSDOS/Clock/Build.cmd index 55bcd8db..8b14f5c5 100644 --- a/Source/ZSDOS/Clock/Build.cmd +++ b/Source/ZSDOS/Clock/Build.cmd @@ -11,4 +11,4 @@ set ZXBINDIR=%TOOLS%/cpm/bin/ set ZXLIBDIR=%TOOLS%/cpm/lib/ set ZXINCDIR=%TOOLS%/cpm/include/ -zx ZMAC -HBCLK -/P +zx ZMAC -WBWCLK -/P diff --git a/Source/ZSDOS/Clock/CLOCKS.DAT b/Source/ZSDOS/Clock/CLOCKS.DAT index a78c2d6c..e64c7e05 100644 Binary files a/Source/ZSDOS/Clock/CLOCKS.DAT and b/Source/ZSDOS/Clock/CLOCKS.DAT differ diff --git a/Source/ZSDOS/Clock/LDDS.COM b/Source/ZSDOS/Clock/LDDS.COM index 9be1d4a4..357f1360 100644 Binary files a/Source/ZSDOS/Clock/LDDS.COM and b/Source/ZSDOS/Clock/LDDS.COM differ diff --git a/Source/ZSDOS/Clock/LDNZT.COM b/Source/ZSDOS/Clock/LDNZT.COM index 535d8418..87cf0b47 100644 Binary files a/Source/ZSDOS/Clock/LDNZT.COM and b/Source/ZSDOS/Clock/LDNZT.COM differ diff --git a/Source/ZSDOS/Clock/LDP2D.COM b/Source/ZSDOS/Clock/LDP2D.COM index 40220f17..a89e03d8 100644 Binary files a/Source/ZSDOS/Clock/LDP2D.COM and b/Source/ZSDOS/Clock/LDP2D.COM differ diff --git a/Source/ZSDOS/Clock/ReadMe.txt b/Source/ZSDOS/Clock/ReadMe.txt index 05aaa67a..0fc634f7 100644 --- a/Source/ZSDOS/Clock/ReadMe.txt +++ b/Source/ZSDOS/Clock/ReadMe.txt @@ -1,9 +1,23 @@ -This directory contains the source and assembled versions of the ZSystem Clock Drivers for N8VEM HBIOS. +This directory contains the source and assembled versions of the +ZSystem Clock Drivers for RomWBW HBIOS. -The hbclk.z80 source file can be compiled using Build.cmd which will produce a relocatable binary (hbclk.rel). +The wbwclk.z80 source file can be compiled using Build.cmd which will +produce a relocatable binary (hbclk.rel). -The relocatable binary should be added/updated in the stamps.dat libary. The stamps.dat file is just a standard LU type library and is easily updated using NULU. The members are the relocatable binaries, but with the .REL extension removed. +The relocatable binary should be added/updated in the STAMPS.DAT +library. The STAMPS.DAT file is just a standard LU type library and +is easily updated using NULU. The members are the relocatable +binaries, but with the .REL extension removed. -SETUPZST is used to create runnable executable (.COM) files. An executable has been created for DateStamper (LDDS.COM) and P2DOS (LDP2D.COM). The executables are all configured for operation as an RSX (resident system extension). +SETUPZST is used to create runnable executable (.COM) files. An +executable has been created for DateStamper (LDDS.COM), P2DOS +(LDP2D.COM), and NZTime (LDNZT.COM) . The executables are all +configured for operation as an RSX (resident system extension). -The STAMPS.DAT file here is a version that I cobbled together. Using the STAMPS.DAT file included in the ZSDOS distribution results in a load file that does not work. It claims to load, but is not present. I found a "fixed" version of STAMPS.DAT on the Walnut Creek CD-ROM which works, but was missing the NZ and NZP2 stamp variants. So, I added those variants to the working version of STAMPS.DAT which is included here. +The STAMPS.DAT file here is a version that I cobbled together. Using +the STAMPS.DAT file included in the ZSDOS distribution results in a +load file that does not work. It claims to load, but is not +present. I found a "fixed" version of STAMPS.DAT on the Walnut Creek +CD-ROM which works, but was missing the NZ and NZP2 stamp variants. +So, I added those variants to the working version of STAMPS.DAT which +is included here. diff --git a/Source/ZSDOS/Clock/hbclk.z80 b/Source/ZSDOS/Clock/hbclk.z80 deleted file mode 100644 index f28c6af1..00000000 --- a/Source/ZSDOS/Clock/hbclk.z80 +++ /dev/null @@ -1,113 +0,0 @@ - TITLE "N8VEM HBIOS Clock Interface" - SUBTTL "Description of Clock Module" -;=================================================================== -; HBCLK.Z80 -; HBIOS Clock driver for N8VEM Z80 Series Computer -; Wayne Warthen -; Version: 18 Apr 2014 -;=================================================================== - -VERS EQU 10 - .Z80 - NAME HBIOS - - MACLIB CLOCK.LIB - - COMMON /_CLKID/ - -DESCST: DEFW 0 ; Pointer to static year value if required - -CLKNAM: DEFB 'N8VEM HBIOS Clock ' ; Exactly 24 chars in name - DEFB VERS/10+'0','.',VERS MOD 10 +'0',0 - -DESCR: DEFB 'N8VEM Z80 Series HBIOS Clock',0 - - IF [$-DESCST] > 256 - OVER2 - ENDIF - - PAGE - SUBTTL "Configurable Clock Hardware Parameters" - - COMMON /_PARM_/ - -PARBAS: DEFW 0 ; # of parameters (Set to 00 if none) - DEFW 0 ; Pointer to STRS (Set to 00 if none) - - CSEG - -;----------------------------------------------------------- -; Z S D O S C L O C K H E A D E R -;----------------------------------------------------------- -; Enter: HL points to a 6-byte buffer to Get/Set time -; Exit : A=1 on Success, A=FFH if error -; HL points to last char in buffer -; E contains original seconds (HL+5) - -;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -; R e a d / W r i t e t h e C l o c k -;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -PRGBAS: JP GETTIM ; Jump to Read Clock - JP WRCLK ; Jump to Set Clock -; -GETTIM: PUSH HL ; Save final buffer pointer - LD HL,TIMBUF ; Point to temp buf for HBIOS read - LD B,20H ; HBIOS Read Clock function = $20 - RST 08 ; Call HBIOS to get time - JR NZ,ERRRET ; Error return - LD HL,TIMBUF ; Source is start of temp buf - POP DE ; Destination is buffer provided originally - LD BC,5 ; Copy just the first 5 bytes - LDIR ; Do it - LD A,(DE) ; Save the original seconds value - INC BC ; Setup to copy final byte (BC := 1) - LDIR ; Do the last byte (seconds) - EX DE,HL ; Setup HL to point to seconds for return - DEC HL ; Need to dec HL back to seconds adr - LD D,0 ; Tenths of seconds is always zero - LD E,A ; Original seconds value to E - LD A,1 ; Signal success - RET ; Done -; -WRCLK: LD DE,TIMBUF ; Copy to temp buf - LD BC,6 ; 6 bytes - LDIR ; Do it - LD HL,TIMBUF ; Point to temp buf for HBIOS call - LD B,21H ; Set clock function - RST 08 ; Call HBIOS to set the time - JR NZ,ERRRET ; Error return - LD A,1 ; Signal success - RET ; Done -; -ERRRET: XOR A ; Error - RET ; Done -; -TIMBUF DEFS 6 ; Temp date/time buffer - - PAGE - SUBTTL "Run-Time Configuration of Ports and Masks" - -; This code installs configurable items into the clock module -; Enter with DE pointing to the physical address of the relocatable module - - COMMON /_POST_/ - RET ; This RETURN MUST be present even if no other - ; code is included in this section - - COMMON /_PRE_/ - -;--------------------------------------------------------------- -; Read clock and wait for seconds to roll - watchdog protected -; Enter with: DE pointing to relocated clock read routine -; HL pointing to base of high module - -; This module is executed just prior to installing the module to insure -; that a valid clock is present -; Enter with DE pointing to beginning of relocated clock CSEG - - COMMON /_PRE_/ - - INCLUDE PRECLOCK.LIB - - END diff --git a/Source/ZSDOS/Clock/preclock.lib b/Source/ZSDOS/Clock/preclock.lib index fd7d711e..8c539963 100644 --- a/Source/ZSDOS/Clock/preclock.lib +++ b/Source/ZSDOS/Clock/preclock.lib @@ -10,6 +10,8 @@ TSTRD: JR TSTRD0 ; Jump around address store TSTRD0: LD (CKCLK+1),DE ; Patch GETTIM address in CALL CKCLK ; Get time to start with + DEC A ; WBW: 1 -> 0 + JR NZ,BAD ; WBW: NO GOOD LD A,(HL) ; Get seconds CP 60H ; Check for valid digit JR NC,BAD ; >= 60h diff --git a/Source/ZSDOS/Clock/wbwclk.z80 b/Source/ZSDOS/Clock/wbwclk.z80 new file mode 100644 index 00000000..05c43b9c --- /dev/null +++ b/Source/ZSDOS/Clock/wbwclk.z80 @@ -0,0 +1,113 @@ + TITLE "ROMWBW HBIOS Clock Interface" + SUBTTL "Description of Clock Module" +;=================================================================== +; WBWCLK.Z80 +; HBIOS Clock driver for RomWBW System Software +; Wayne Warthen +; Version: 31 Mar 2020 +;=================================================================== + +VERS EQU 11 + .Z80 + NAME WBWCLK + + MACLIB CLOCK.LIB + + COMMON /_CLKID/ + +DESCST: DEFW 0 ; Pointer to static year value if required + +CLKNAM: DEFB 'RomWBW HBIOS Clock ' ; Exactly 24 chars in name + DEFB VERS/10+'0','.',VERS MOD 10 +'0',0 + +DESCR: DEFB 'RomWBW Series HBIOS Clock',0 + + IF [$-DESCST] > 256 + OVER2 + ENDIF + + PAGE + SUBTTL "Configurable Clock Hardware Parameters" + + COMMON /_PARM_/ + +PARBAS: DEFW 0 ; # of parameters (Set to 00 if none) + DEFW 0 ; Pointer to STRS (Set to 00 if none) + + CSEG + +;----------------------------------------------------------- +; Z S D O S C L O C K H E A D E R +;----------------------------------------------------------- +; Enter: HL points to a 6-byte buffer to Get/Set time +; Exit : A=1 on Success, A=FFH if error +; HL points to last char in buffer +; E contains original seconds (HL+5) + +;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - +; R e a d / W r i t e t h e C l o c k +;- - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +PRGBAS: JP GETTIM ; Jump to Read Clock + JP WRCLK ; Jump to Set Clock +; +GETTIM: PUSH HL ; Save final buffer pointer + LD HL,TIMBUF ; Point to temp buf for HBIOS read + LD B,20H ; HBIOS Read Clock function = $20 + RST 08 ; Call HBIOS to get time + LD HL,TIMBUF ; Source is start of temp buf + POP DE ; Destination is buffer provided originally + JR NZ,ERRRET ; Error return + LD BC,5 ; Copy just the first 5 bytes + LDIR ; Do it + LD A,(DE) ; Save the original seconds value + INC BC ; Setup to copy final byte (BC := 1) + LDIR ; Do the last byte (seconds) + EX DE,HL ; Setup HL to point to seconds for return + DEC HL ; Need to dec HL back to seconds adr + LD D,0 ; Tenths of seconds is always zero + LD E,A ; Original seconds value to E + LD A,1 ; Signal success + RET ; Done +; +WRCLK: LD DE,TIMBUF ; Copy to temp buf + LD BC,6 ; 6 bytes + LDIR ; Do it + LD HL,TIMBUF ; Point to temp buf for HBIOS call + LD B,21H ; Set clock function + RST 08 ; Call HBIOS to set the time + JR NZ,ERRRET ; Error return + LD A,1 ; Signal success + RET ; Done +; +ERRRET: OR 0FFH ; Error + RET ; Done +; +TIMBUF DEFS 6 ; Temp date/time buffer + + PAGE + SUBTTL "Run-Time Configuration of Ports and Masks" + +; This code installs configurable items into the clock module +; Enter with DE pointing to the physical address of the relocatable module + + COMMON /_POST_/ + RET ; This RETURN MUST be present even if no other + ; code is included in this section + + COMMON /_PRE_/ + +;--------------------------------------------------------------- +; Read clock and wait for seconds to roll - watchdog protected +; Enter with: DE pointing to relocated clock read routine +; HL pointing to base of high module + +; This module is executed just prior to installing the module to insure +; that a valid clock is present +; Enter with DE pointing to beginning of relocated clock CSEG + + COMMON /_PRE_/ + + INCLUDE PRECLOCK.LIB + + END diff --git a/Source/ZSDOS/Distribution/ZCAL.COM b/Source/ZSDOS/Distribution/ZCAL.COM index a5add241..f239e952 100644 Binary files a/Source/ZSDOS/Distribution/ZCAL.COM and b/Source/ZSDOS/Distribution/ZCAL.COM differ diff --git a/Source/ZSDOS/Makefile b/Source/ZSDOS/Makefile new file mode 100644 index 00000000..90963e9d --- /dev/null +++ b/Source/ZSDOS/Makefile @@ -0,0 +1,24 @@ +SYSFILES = zsys_wbw.sys zsys_una.sys +BINFILES = zsys_wbw.bin zsys_una.bin +OBJECTS = $(SYSFILES) $(BINFILES) +OTHERS = zsdos.rel zsdos.err loader.bin zsdos.bin +TOOLS = ../../Tools +CCP = ../ZCPR-DJ/zcpr.bin + +include $(TOOLS)/Makefile.inc + +zsdos.rel: zsdos.z80 + $(ZXCC) $(CPM)/ZMAC -$< -/P + +zsdos.bin: zsdos.rel + $(ZXCC) $(CPM)/LINK -$@=$<[LD800] + +%.sys: %.bin loader.bin + cat loader.bin $*.bin > $@ + +zsys_wbw.bin: $(CCP) zsdos.bin ../CBIOS/cbios_wbw.bin + cat $(CCP) zsdos.bin ../CBIOS/cbios_wbw.bin > $@ + +zsys_una.bin: $(CCP) zsdos.bin ../CBIOS/cbios_una.bin + cat $(CCP) zsdos.bin ../CBIOS/cbios_una.bin > $@ + diff --git a/Source/ZSDOS/loader.asm b/Source/ZSDOS/loader.asm new file mode 100644 index 00000000..bdebc314 --- /dev/null +++ b/Source/ZSDOS/loader.asm @@ -0,0 +1,226 @@ +;=============================================================================== +; LOADER.ASM +; +; BOOTLOADER FOR ROMWBW DISK OPERATING SYSTEMS. +; +; CP/M DISK FORMATS ALLOW FOR RESERVED TRACKS THAT CONTAIN AN IMAGE OF THE +; OPERATING SYSTEM TO BE LOADED WHEN THE DISK IS BOOTED. THE OPERATING SYSTEM +; IMAGE ITSELF IS NORMALLY PREFIXED BY A 1-N SECTORS CONTAINING OS BOOTSTRAP +; CODE AND DISK METADATA. +; +; THE RETROBREW COMPUTING GROUP HAS BEEN USING A CONVENTION OF PREFIXING THE +; OS IMAGE WITH 3 SECTORS (512 BYTES X 3 FOR A TOTAL OF 1536 BYTES): +; +; SECTOR 1: IBM-PC STYLE BOOT BLOCK CONTAINING BOOTSTRAP, +; PARTITION TABLE, AND BOOT SIGNATURE +; SECTOR 2: RESERVED +; SECTOR 3: METADATA +; +; THE HARDWARE BIOS IS EXPECTED TO READ AND LOAD THE FIRST TWO SECTORS FROM THE +; DISK TO MEMORY ADDRESS $8000 AND JUMP TO THAT LOCATION TO BEGIN THE BOOT +; PROCESS. THE BIOS IS EXPECTED TO VERIFY THAT A STANDARD BOOT SIGNATURE +; OF $55, $AA IS PRESENT AT OFFSET $1FE-$1FF. IF THE SIGNATURE IS NOT FOUND, +; THE BIOS SHOULD ASSUME THE DISK HAS NOT BEEN PROPERLY INITIALIZED AND SHOULD +; NOT JUMP TO THE LOAD ADDRESS. +; +;=============================================================================== +; +#INCLUDE "../ver.inc" +; +SYS_ENT .EQU $E600 ; SYSTEM (OS) ENTRY POINT ADDRESS +SYS_LOC .EQU $D000 ; STARTING ADDRESS TO LOAD SYSTEM IMAGE +SYS_END .EQU $FE00 ; ENDING ADDRESS OF SYSTEM IMAGE +; +SEC_SIZE .EQU 512 ; DISK SECTOR SIZE +BLK_SIZE .EQU 128 ; OS BLOCK/RECORD SIZE +; +PREFIX_SIZE .EQU (SEC_SIZE * 3) ; 3 SECTORS +; +META_SIZE .EQU 32 ; SEE BELOW +META_LOC .EQU (PREFIX_SIZE - META_SIZE) +; +PT_LOC .EQU $1BE +PT_SIZ .EQU $40 +; +;------------------------------------------------------------------------------- +; SECTOR 1 +; +; THIS SECTOR FOLLOWS THE CONVENTIONS OF AN IBM-PC MBR CONTAINING THE OS +; BOOTSTRAP CODE, PARTITION TABLE, AND BOOT SIGNATURE +; +;---------------------------------------------------------------------------- +; +; THE FOLLOWING BOOTSTRAP CODE IS BUILT TO ASSUME IT WILL BE EXECUTED AT A STARTING +; ADDRESS OF $8000. THIS CODE IS *ONLY* FOR UNA. THE ROMWBW ROM BOOTLOADER +; USES THE METADATA TO LOAD THE OS DIRECTLY. +; + .ORG $8000 + JR BOOT +; +BOOT: + LD DE,STR_LOAD ; LOADING STRING + CALL PRTSTR ; PRINT + CALL PRTDOT ; PROGRESS +; + LD BC,$00FC ; UNA FUNC: GET BOOTSTRAP HISTORY + CALL $FFFD ; CALL UNA + JR NZ,ERROR ; HANDLE ERROR + CALL PRTDOT ; PROGRESS + LD B,L ; MOVE BOOT UNIT ID TO B +; + LD C,$41 ; UNA FUNC: SET LBA + LD DE,0 ; HI WORD ALWAYS ZERO + LD HL,3 ; IMAGE STARTS AT FOURTH SECTOR + CALL $FFFD ; SET LBA + JR NZ,ERROR ; HANDLE ERROR + CALL PRTDOT ; PROGRESS +; + LD C,$42 ; UNA FUNC: READ SECTORS + LD DE,$D000 ; STARTING ADDRESS FOR IMAGE + LD L,22 ; READ 22 SECTORS + CALL $FFFD ; DO READ + JR NZ,ERROR ; HANDLE ERROR + CALL PRTDOT ; PROGRESS +; + LD DE,STR_DONE ; DONE MESSAGE + CALL PRTSTR ; PRINT IT +; + LD D,B ; PASS BOOT UNIT TO OS + LD E,0 ; ASSUME LU IS ZERO + JP SYS_ENT ; GO TO SYSTEM +; +PRTCHR: + PUSH BC + PUSH DE + LD BC,$0012 ; UNIT 0, WRITE CHAR + LD E,A ; CHAR TO PRINT + CALL $FFFD ; PRINT + POP DE + POP BC + RET +; +PRTSTR: + PUSH BC + PUSH HL + LD BC,$0015 ; UNIT 0, WRITE CHARS UNTIL TERMINATOR + LD L,0 ; TERMINATOR IS NULL + CALL $FFFD ; PRINT + POP HL + POP BC + RET +; +PRTDOT: + LD A,'.' ; DOT CHARACTER + JR PRTCHR ; PRINT AND RETURN +; +; PRINT THE HEX BYTE VALUE IN A +; +PRTHEXBYTE: + PUSH AF + PUSH DE + CALL HEXASCII + LD A,D + CALL PRTCHR + LD A,E + CALL PRTCHR + POP DE + POP AF + RET +; +; CONVERT BINARY VALUE IN A TO ASCII HEX CHARACTERS IN DE +; +HEXASCII: + LD D,A + CALL HEXCONV + LD E,A + LD A,D + RLCA + RLCA + RLCA + RLCA + CALL HEXCONV + LD D,A + RET +; +; CONVERT LOW NIBBLE OF A TO ASCII HEX +; +HEXCONV: + AND $0F ;LOW NIBBLE ONLY + ADD A,$90 + DAA + ADC A,$40 + DAA + RET +; +ERROR: + LD DE,STR_ERR ; POINT TO ERROR STRING + CALL PRTSTR ; PRINT IT + HALT ; HALT +; +; DATA +; +STR_LOAD .DB "\r\nLoading",0 +STR_DONE .DB "\r\n",0 +STR_ERR .DB " Read Error!",0 +; + .ORG $ - $8000 ; BACK TO ABSOLUTE ADDRESS +; + .FILL PT_LOC - $,0 ; FILL TO START OF PARTITION TABLE +; +; RESERVE SPACE FOR STANDARD IBM-PC PARTITION TABLE. ALTHOUGH A +; PARTITION TABLE IS NOT RELEVANT FOR A FLOPPY DISK, IT DOES NO HARM. +; THE CONTENTS OF THE PARTITION TABLE MUST BE MANAGED BY FDISK80. +; +PARTTBL .FILL PT_SIZ,0 ; PARTITION TABLE, FILL WITH ZEROES +; +; THE END OF THE FIRST SECTOR MUST CONTAIN THE TWO BYTE BOOT SIGNATURE. +; +BOOTSIG .DB $55,$AA ; STANDARD BOOT SIGNATURE +; +;------------------------------------------------------------------------------- +; SECTOR 2 +; +; THIS SECTOR HAS NOT BEEN DEFINED AND IS RESERVED. +; +;---------------------------------------------------------------------------- +; + .FILL SEC_SIZE,0 ; JUST FILL SECTOR WITH ZEROES +; +;------------------------------------------------------------------------------- +; SECTOR 3 +; +; OS AND DISK METADATA +; +;---------------------------------------------------------------------------- +; + .FILL (BLK_SIZE * 3),0 ; FIRST 384 BYTES ARE NOT YET DEFINED +; +; THE FOLLOWING TWO BYTES ARE AN ADDITIONAL SIGNATURE THAT IS VERIFIED BY +; SOME HARDWARE BIOSES. +; +PR_SIG .DB $5A,$A5 ; SIGNATURE GOES HERE +; + .FILL (META_LOC - $),0 +; +; METADATA +; +PR_WP .DB 0 ; (1) WRITE PROTECT BOOLEAN +PR_UPDSEQ .DW 0 ; (2) PREFIX UPDATE SEQUENCE NUMBER (DEPRECATED?) +PR_VER .DB RMJ,RMN,RUP,RTP ; (4) OS BUILD VERSION +PR_LABEL .DB "Unlabeled$$$$$$$","$" ; (17) DISK LABEL (EXACTLY 16 BYTES!!!) + .DW 0 ; (2) DEPRECATED +PR_LDLOC .DW SYS_LOC ; (2) ADDRESS TO START LOADING SYSTEM +PR_LDEND .DW SYS_END ; (2) ADDRESS TO STOP LOADING SYSTEM +PR_ENTRY .DW SYS_ENT ; (2) ADDRESS TO ENTER SYSTEM (OS) +; +#IF (META_SIZE != ($ - META_LOC)) + .ECHO "META_SIZE VALUE IS WRONG!!!\r\n" + !!! +#ENDIF +; +#IF ($ != PREFIX_SIZE) + .ECHO "LOADER PREFIX IS WRONG SIZE!!!\r\n" + !!! +#ENDIF +; + .END diff --git a/Source/ver.inc b/Source/ver.inc new file mode 100644 index 00000000..91ca16d7 --- /dev/null +++ b/Source/ver.inc @@ -0,0 +1,5 @@ +#DEFINE RMJ 3 +#DEFINE RMN 1 +#DEFINE RUP 0 +#DEFINE RTP 0 +#DEFINE BIOSVER "3.1-pre.2" diff --git a/Source/ver.lib b/Source/ver.lib new file mode 100644 index 00000000..bc39cfbf --- /dev/null +++ b/Source/ver.lib @@ -0,0 +1,7 @@ +rmj equ 3 +rmn equ 1 +rup equ 0 +rtp equ 0 +biosver macro + db "3.1-pre.2" + endm diff --git a/Tools/Makefile.inc b/Tools/Makefile.inc new file mode 100644 index 00000000..4d0c5583 --- /dev/null +++ b/Tools/Makefile.inc @@ -0,0 +1,171 @@ +# +# try to use suffix rules whenever possible in any of the lower makefiles +# in the case of exceptions, just use an explicit build rule +# +# there are also some bizarre things being done with case-sensitive sources +# make is very much case-sensitive, so we use this. if your underlying +# filesystem is not case-preserving, forget it. +# +# .asm: TASM sources, except somewheres it is MAC or RMAC +# .z80: Z80ASM sources, except ZSDOS, where they are ZMAC +# .azm: zsm sources +# +UNAME := $(shell uname) +BINDIR = $(TOOLS)/$(UNAME) + +# +# since this file is included from below, it's handy to have an idea +# where we are relative to the tree +# +TREEROOT := $(shell cd $(TOOLS)/.. ; pwd) +HERE := $(shell pwd) +RELPATH := $(subst $(TREEROOT),,$(HERE)) + +# +# where's a copy of this tree for windows so we can diff binaries +# +WINROOT = $(TREEROOT)/../RomWBW.windows +DIFFTO := $(shell if [ -d $(WINROOT) ] ; then cd $(WINROOT); pwd; fi) +DIFFPATH := $(DIFFTO)/$(RELPATH) + +# +# this is a script that resolves a filename in a case-insensitive way +# to be used in diff'ing objects +# +CASEFN = $(TOOLS)/unix/casefn.sh + +ZXCC=$(BINDIR)/zx +TASM=$(BINDIR)/uz80as -t hd64180 +OPENSPIN=$(BINDIR)/openspin +BSTC=$(BINDIR)//bstc +CPMCP=$(BINDIR)/cpmcp + +# +# directory containing cpm binaries +# +CPM=$(TOOLS)/cpm/bin + +%.com: %.asm + if [ "$(USETASM)" = 1 ] ; then \ + $(TASM) $< $@ ; \ + else \ + $(ZXCC) $(CPM)/MAC -$< -$$PO ; \ + $(ZXCC) $(CPM)/MLOAD25 -tmp.bin=$*.hex ; \ + mv tmp.bin $@ ; \ + rm -f $$($(CASEFN) $*.hex) ; \ + fi + +%.hex: %.asm + $(ZXCC) $(CPM)/MAC -$< -$$PO ; \ + +%.bin: %.ASM + $(ZXCC) $(CPM)/MAC -$< -$$PO + $(ZXCC) $(CPM)/MLOAD25 -tmp.bin=$*.hex + mv tmp.bin $@ + rm -f $$($(CASEFN) $*.hex) + +%.com: %.z80 + $(ZXCC) $(CPM)/Z80ASM -$(basename $<)/F ; \ + mv $$($(CASEFN) $@) tmp.com ; mv tmp.com $@ + +%.bin: %.asm + $(TASM) $< $@ + +%.rel: %.asm + $(ZXCC) $(CPM)/RMAC -$< + +%.rel: %.z80 + $(ZXCC) $(CPM)/Z80ASM -$(basename $<)/MF + +%.hex: %.180 + $(ZXCC) $(CPM)/SLR180 -$(basename $<)/HF + +%.rel: %.azm + $(ZXCC) $(CPM)/ZSM =$< + +%.bin: %.rel + $(ZXCC) $(CPM)/LINK -$@=$< + +%.rel: %.mac + $(ZXCC) $(CPM)/M80 -=$(basename $<) + +ifeq ($(UNAME), Linux) +%.eeprom: %.spin + $(BSTC) -e -l $< +endif + +# +# darwin bstc won't run, since mac os does not do 32 bit binaries any more +# openspin ought to work +# +ifeq ($(UNAME), Darwin) +%.eeprom: %.spin + $(OPENSPIN) -e $< +endif + +# +# first target is default +# +all:: $(OBJECTS) + @for dir in $(SUBDIRS) ; do \ + ( echo "building in `pwd`/$$dir" ; cd "$$dir" ; make all ) ; \ + done + @if [ "$(DEST)" ] ; then for file in $(OBJECTS) ; do \ + mkdir -p $(DEST) ; \ + echo copy $$file to $(DEST) ; \ + cp $$($(CASEFN) $$file) $(DEST) ; \ + done ; fi + @if [ "$(DOCDEST)" ] ; then for file in $(DOCS) ; do \ + mkdir -p $(DOCDEST) ; \ + echo copy $$file to $(DOCDEST) ; \ + cp $$($(CASEFN) $$file) $(DOCDEST) ; \ + done ; fi + +clean:: + @-rm -f $$($(CASEFN) make.out *.sym *.lst *.prn *.diff *.dump $(OTHERS) $(filter-out $(NODELETE),$(OBJECTS))) + @for dir in $(SUBDIRS) ; do \ + ( echo "cleaning in `pwd`/$$dir" ; cd "$$dir" ; make clean ) ; \ + done + +clobber:: clean + @if [ "$(DEST)" ] ; then for file in $(OBJECTS) ; do \ + rm -f $$($(CASEFN) $(DEST)/$$file) ; \ + done ; fi + @-rm -f $$($(CASEFN) $(filter-out $(NODELETE),$(OBJECTS))) + @for dir in $(SUBDIRS) ; do \ + ( echo "clobbering in `pwd`/$$dir" ; cd "$$dir" ; make clobber ) ; \ + done + +# +# this is used to verify that the unix and windows tool chains are generating +# the same objects +# +diff:: +ifneq ($(DIFFTO),) + @for dir in $(SUBDIRS) ; do \ + ( echo "diff in $(HERE)/$$dir" ; cd "$$dir" ; make diff ) ; \ + done + @for i in $(OBJECTS) $(MOREDIFF) ; do \ + sf=$$($(CASEFN) $$i) ; \ + df=$$($(CASEFN) $(DIFFPATH)/$$i) ; \ + if [ -f "$$df" -a -f "$$sf" ] ; then \ + if [ "$(VERBOSEDIFF)" ] ; then \ + echo compare $$sf and $$df ; \ + fi ; \ + if ! cmp -s $$sf $$df ; then \ + echo " " $$sf and $$df differ ; \ + if [ "$(VERBOSEDIFF)" = "2" ] ; then \ + hexdump -Cv $$sf > $$sf.dump ; \ + hexdump -Cv $$df > $$(basename $$df).dump.diff ; \ + fi \ + fi \ + else \ + if [ ! -f "$$sf" ] ; then echo $$i missing ; fi ; \ + if [ ! -f "$$df" ] ; then echo $(DIFFPATH)/$$i missing ; fi ; \ + fi ; \ + done +endif + +vdiff: + make VERBOSEDIFF=2 diff + diff --git a/Tools/ReadMe.txt b/Tools/ReadMe.txt index a66323e4..be0c03e9 100644 --- a/Tools/ReadMe.txt +++ b/Tools/ReadMe.txt @@ -20,17 +20,17 @@ does for MS-DOS. bst: -The bst tool set is a multi-platform set of tools for developing -with the Parallax Propeller microcontroller. bst stands for -“Brad's Spin Toolâ€, however it is never capitalised. This toolset -is used to compile the Propeller firmware for PropIO and -ParPortProp. +The bst tool set is a multi-platform set of tools for developing with +the Parallax Propeller microcontroller. bst stands for “Brad's Spin +Toolâ€, however it is never capitalised. This toolset is used to +compile the Propeller firmware for PropIO and ParPortProp. cpm: -This is the root of a directory tree containing CP/M-80 programs. -These programs are used (via the Windows CP/M command line -emulator 'zx') to build certain components of RomWBW. The use of +This is the root of a directory tree containing native CP/M-80 +programs. These programs cannot be invoked directly by DOS/Windows. +Instead, they are executed via the Windows CP/M command line +emulator 'zx' to build certain components of RomWBW. The use of real CP/M-80 programs as part of the build process ensures proper construction of these components. diff --git a/Tools/cpm/bin/BASCOM.COM b/Tools/cpm/bin/BASCOM.COM new file mode 100644 index 00000000..1dda4754 Binary files /dev/null and b/Tools/cpm/bin/BASCOM.COM differ diff --git a/Tools/cpm/bin/DISKINFO.COM b/Tools/cpm/bin/DISKINFO.COM new file mode 100644 index 00000000..068cc97a Binary files /dev/null and b/Tools/cpm/bin/DISKINFO.COM differ diff --git a/Tools/cpm/bin/SLRMAC.COM b/Tools/cpm/bin/SLRMAC.COM new file mode 100644 index 00000000..8d7f679a Binary files /dev/null and b/Tools/cpm/bin/SLRMAC.COM differ diff --git a/Tools/cpm/bin/SLRNK.COM b/Tools/cpm/bin/SLRNK.COM new file mode 100644 index 00000000..f33f2d99 Binary files /dev/null and b/Tools/cpm/bin/SLRNK.COM differ diff --git a/Tools/cpm/bin/TEX21B.COM b/Tools/cpm/bin/TEX21B.COM new file mode 100644 index 00000000..aa72b287 Binary files /dev/null and b/Tools/cpm/bin/TEX21B.COM differ diff --git a/Tools/cpm/bin/Z80MR.COM b/Tools/cpm/bin/Z80MR.COM deleted file mode 100644 index 0bdda14d..00000000 Binary files a/Tools/cpm/bin/Z80MR.COM and /dev/null differ diff --git a/Tools/cpm/bin/ZSM.COM b/Tools/cpm/bin/ZSM.COM new file mode 100644 index 00000000..3704c8d9 Binary files /dev/null and b/Tools/cpm/bin/ZSM.COM differ diff --git a/Tools/cpmtools/cpmchattr.exe b/Tools/cpmtools/cpmchattr.exe index 61a01932..f9d5e044 100644 Binary files a/Tools/cpmtools/cpmchattr.exe and b/Tools/cpmtools/cpmchattr.exe differ diff --git a/Tools/cpmtools/cpmchmod.exe b/Tools/cpmtools/cpmchmod.exe index 65db9af7..2e23a271 100644 Binary files a/Tools/cpmtools/cpmchmod.exe and b/Tools/cpmtools/cpmchmod.exe differ diff --git a/Tools/cpmtools/cpmcp.exe b/Tools/cpmtools/cpmcp.exe index 21c4f9ea..56d401d8 100644 Binary files a/Tools/cpmtools/cpmcp.exe and b/Tools/cpmtools/cpmcp.exe differ diff --git a/Tools/cpmtools/cpmls.exe b/Tools/cpmtools/cpmls.exe index c779134c..960b1773 100644 Binary files a/Tools/cpmtools/cpmls.exe and b/Tools/cpmtools/cpmls.exe differ diff --git a/Tools/cpmtools/cpmrm.exe b/Tools/cpmtools/cpmrm.exe index c44d68db..0a2bba1c 100644 Binary files a/Tools/cpmtools/cpmrm.exe and b/Tools/cpmtools/cpmrm.exe differ diff --git a/Tools/cpmtools/fsck.cpm.exe b/Tools/cpmtools/fsck.cpm.exe index eceeec9a..3e3d9f47 100644 Binary files a/Tools/cpmtools/fsck.cpm.exe and b/Tools/cpmtools/fsck.cpm.exe differ diff --git a/Tools/cpmtools/fsed.cpm.exe b/Tools/cpmtools/fsed.cpm.exe index 2cc97245..c763385c 100644 Binary files a/Tools/cpmtools/fsed.cpm.exe and b/Tools/cpmtools/fsed.cpm.exe differ diff --git a/Tools/cpmtools/mkfs.cpm.exe b/Tools/cpmtools/mkfs.cpm.exe index f8ac9b96..e878d4a1 100644 Binary files a/Tools/cpmtools/mkfs.cpm.exe and b/Tools/cpmtools/mkfs.cpm.exe differ diff --git a/Tools/fonttool/fonttool.exe b/Tools/fonttool/fonttool.exe new file mode 100644 index 00000000..2b5fda71 Binary files /dev/null and b/Tools/fonttool/fonttool.exe differ diff --git a/Tools/lzsa/ReadMe.txt b/Tools/lzsa/ReadMe.txt new file mode 100644 index 00000000..5238957e --- /dev/null +++ b/Tools/lzsa/ReadMe.txt @@ -0,0 +1,16 @@ +Command line compressor by Emmanuel Marty +The compression is done as follows: + +lzsa.exe -f2 -r + +where option -f2 selects lzsa version 2 compression. +where option -r asks for the generation of raw (frame-less) data. + +Original compression source code and files available here: + +Latest github code: + https://github.com/emmanuel-marty/lzsa +Implementation example (older version): + https://cpcrulez.fr/applications_tools_cruncher_LZSA2_Z80.htm +x86 lzsa compressor application: + http://www.pouet.net/prod.php?which=81573 \ No newline at end of file diff --git a/Tools/lzsa/lzsa.exe b/Tools/lzsa/lzsa.exe new file mode 100644 index 00000000..1c1927d7 Binary files /dev/null and b/Tools/lzsa/lzsa.exe differ diff --git a/Tools/simh/Sim.cfg b/Tools/simh/Sim.cfg index 2b86fdb4..8c8af2bf 100644 --- a/Tools/simh/Sim.cfg +++ b/Tools/simh/Sim.cfg @@ -23,21 +23,18 @@ attach n8vem0 %1 ; hard disks ;set hdsk debug=read;write;verbose -attach hdsk0 ..\..\Binary\hd0.img -attach hdsk1 ..\..\Binary\hd1.img +attach hdsk0 ..\..\Binary\hd_combo.img +attach hdsk1 ..\..\Binary\hd_combo.img set hdsk0 format=HDSK set hdsk1 format=HDSK -set hdsk0 geom=T:520/N:256/S:512 -set hdsk1 geom=T:520/N:256/S:512 +set hdsk0 geom=T:2048/N:256/S:512 +set hdsk1 geom=T:2048/N:256/S:512 set hdsk0 wrtenb set hdsk1 wrtenb ; enable timer interrupt, 50Hz -; DDT, SID, etc. use the normal IM 1 vector 6 ($38) -; for breakpoints so we use vector 6 ($30) d timd 20 -;d timh 38 -d timh 30 +d timh 38 set simh timeron ; start emulation diff --git a/Tools/unix/Makefile b/Tools/unix/Makefile new file mode 100644 index 00000000..bbe6724b --- /dev/null +++ b/Tools/unix/Makefile @@ -0,0 +1,27 @@ +# +# build the tools for linux and Darwin +# +UNAME := $(shell uname) +ifeq ($(UNAME), Linux) + SUFFIX=linux +endif +ifeq ($(UNAME), Darwin) + SUFFIX=osx +endif + +SUBDIRS= bst uz80as zx cpmtools bin2asm lzsa + +all: + @for i in $(SUBDIRS) ; do \ + (cd $$i ; make all ) \ + done + +clobber: + @for i in $(SUBDIRS) ; do \ + (cd $$i ; make clobber ) \ + done + +clean: + @for i in $(SUBDIRS) ; do \ + (cd $$i ; make clean ) \ + done diff --git a/Tools/unix/bin2asm/LICENSE b/Tools/unix/bin2asm/LICENSE new file mode 100644 index 00000000..b6df5b30 --- /dev/null +++ b/Tools/unix/bin2asm/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 ipatix + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Tools/unix/bin2asm/Makefile b/Tools/unix/bin2asm/Makefile new file mode 100644 index 00000000..27af4088 --- /dev/null +++ b/Tools/unix/bin2asm/Makefile @@ -0,0 +1,29 @@ +CC = gcc +CFLAGS = -Werror -Wall -Wextra -Wconversion -O2 -D NDEBUG +BINARY = bin2asm + +DEST := ../../$(shell uname) + +SRC_FILES = $(wildcard *.c) +OBJ_FILES = $(SRC_FILES:.c=.o) + +all: $(BINARY) $(DEST) + cp $(BINARY) $(DEST) + +$(DEST): + mkdir -p $(DEST) + +.PHONY: clean + +clean: + rm -f $(OBJ_FILES) + +clobber: + rm -f $(DEST)/$(BINARY) $(BINARY) + +$(BINARY): $(OBJ_FILES) + $(CC) -o $@ $^ $(LIBS) + +%.o: %.c + $(CC) -c -o $@ $< $(CFLAGS) $(IMPORT) + diff --git a/Tools/unix/bin2asm/README.md b/Tools/unix/bin2asm/README.md new file mode 100644 index 00000000..cc666ba1 --- /dev/null +++ b/Tools/unix/bin2asm/README.md @@ -0,0 +1,5 @@ +# bin2asm +Converts binary files to ASM compatible assembly files + +Usage: +$ bin2asm hello.bin > hello.asm diff --git a/Tools/unix/bin2asm/bin2asm.c b/Tools/unix/bin2asm/bin2asm.c new file mode 100644 index 00000000..e9af0fab --- /dev/null +++ b/Tools/unix/bin2asm/bin2asm.c @@ -0,0 +1,105 @@ +#include +#include +#include +#include + +void die(const char msg[]) { + perror(msg); + exit(EXIT_FAILURE); +} + +int main(int argc, char *argv[]) { + for (int i = 1; i < argc; i++) { + // determine file size + FILE *input_file = fopen(argv[i], "rb"); + if (!input_file) { + fprintf(stderr, "fail %s\n", argv[i]); + die("Couldn't open input file"); + } + if (fseek(input_file, 0, SEEK_END) == -1) { + die("Error determining file size"); + } + long file_size = ftell(input_file); + if (file_size == -1) { + die("Error determining file size (2)"); + } + if (fseek(input_file, 0, SEEK_SET) == -1) { + die("Error determining file size (3)"); + } + // get file name + char *name = basename(argv[i]); + char *dot = strrchr(name, '.'); + if (dot) { + *dot = '\0'; + } + + // print header +#ifdef notdef + printf(" .section .rodata\r\n"); + printf(" .global %s\r\n", name); + printf(" .align 2\r\n\r\n"); +#endif + printf("%s:\r\n\r\n", name); + + // write lines + while (file_size > 0) { + size_t bytes_read = (file_size > 8) ? 8 : (size_t)file_size; + unsigned char data_buf[8]; + size_t actual_read = fread(data_buf, 1, bytes_read, input_file); + if (actual_read != bytes_read) { + fprintf(stderr, "Error while reading file, only %d read instead of %d\n", (int)actual_read, (int)bytes_read); + if (feof(input_file)) + fprintf(stderr, "Reached end of file\n"); + if (ferror(input_file)) + fprintf(stderr, "An unknown error occured while reading the file\n"); + perror("ERROR"); + exit(EXIT_FAILURE); + } + switch (bytes_read) { + case 1: + printf(" .byte 0x%02X\r\n", + data_buf[0]); + break; + case 2: + printf(" .byte 0x%02X, 0x%02X\r\n", + data_buf[0], data_buf[1]); + break; + case 3: + printf(" .byte 0x%02X, 0x%02X, 0x%02X\r\n", + data_buf[0], data_buf[1], data_buf[2]); + break; + case 4: + printf(" .byte 0x%02X, 0x%02X, 0x%02X, 0x%02X\r\n", + data_buf[0], data_buf[1], data_buf[2], data_buf[3]); + break; + case 5: + printf(" .byte 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X\r\n", + data_buf[0], data_buf[1], data_buf[2], data_buf[3], data_buf[4]); + break; + case 6: + printf(" .byte 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X\r\n", + data_buf[0], data_buf[1], data_buf[2], data_buf[3], data_buf[4], data_buf[5]); + break; + case 7: + printf(" .byte 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X\r\n", + data_buf[0], data_buf[1], data_buf[2], data_buf[3], data_buf[4], data_buf[5], data_buf[6]); + break; + case 8: + printf(" .byte 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X\r\n", + data_buf[0], data_buf[1], data_buf[2], data_buf[3], data_buf[4], data_buf[5], data_buf[6], data_buf[7]); + break; + default: + fprintf(stderr, "Invalid program state\n"); + exit(EXIT_FAILURE); + } + file_size -= (long)bytes_read; + } + + printf("\r\n"); + + if (fclose(input_file)) { + die("Error while closing file"); + } + } + return 0; +} diff --git a/Tools/unix/bst/Makefile b/Tools/unix/bst/Makefile new file mode 100644 index 00000000..0d269dea --- /dev/null +++ b/Tools/unix/bst/Makefile @@ -0,0 +1,28 @@ +# +# build the propeller tools for linux and Darwin +# +UNAME := $(shell uname) +ifeq ($(UNAME), Linux) + SUFFIX=linux +endif +ifeq ($(UNAME), Darwin) + SUFFIX=osx +endif + +DEST = ../../$(UNAME) + +all: $(DEST) + -for i in *.$(SUFFIX) ; do \ + chmod +x $$i ; \ + cp $$i $(DEST)/$$(basename $$i .$(SUFFIX)) ; \ + done + +$(DEST): + mkdir $(DEST) + +clobber: + -for i in *.$(SUFFIX) ; do \ + rm $(DEST)/$$(basename $$i .$(SUFFIX)) ; \ + done + +clean: diff --git a/Tools/unix/bst/bstc.linux b/Tools/unix/bst/bstc.linux new file mode 100755 index 00000000..200c412e Binary files /dev/null and b/Tools/unix/bst/bstc.linux differ diff --git a/Tools/unix/bst/bstc.osx b/Tools/unix/bst/bstc.osx new file mode 100755 index 00000000..d69cbe56 Binary files /dev/null and b/Tools/unix/bst/bstc.osx differ diff --git a/Tools/unix/bst/bstl.linux b/Tools/unix/bst/bstl.linux new file mode 100755 index 00000000..03ee91cc Binary files /dev/null and b/Tools/unix/bst/bstl.linux differ diff --git a/Tools/unix/bst/bstl.osx b/Tools/unix/bst/bstl.osx new file mode 100755 index 00000000..18179de2 Binary files /dev/null and b/Tools/unix/bst/bstl.osx differ diff --git a/Tools/unix/bst/openspin.linux b/Tools/unix/bst/openspin.linux new file mode 100755 index 00000000..8bc88e5d Binary files /dev/null and b/Tools/unix/bst/openspin.linux differ diff --git a/Tools/unix/bst/openspin.osx b/Tools/unix/bst/openspin.osx new file mode 100755 index 00000000..f55a8131 Binary files /dev/null and b/Tools/unix/bst/openspin.osx differ diff --git a/Tools/unix/casefn.sh b/Tools/unix/casefn.sh new file mode 100755 index 00000000..34e7eb40 --- /dev/null +++ b/Tools/unix/casefn.sh @@ -0,0 +1,110 @@ +#!/bin/bash +# given filename on the command line, echo the form of the file that +# actually can be opened. this needs to do filesystem case shenanigans +# +# we don't handle files with embedded spaces, a horrible idea anyway +# +# this is a bit slow with lots of files, so there's a cache of the join file +# +# the form of this cache file is gnarly: +# pwd dir:%d dir:%d ... +# where pwd is the current dir, dir are relative paths, and %d are mod times in +# seconds from the epoch +# +pid=.$$ +search=/tmp/casefn.search$pid +join=/tmp/casefn.join$pid +in=/tmp/casefn.in$pid +cache=/tmp/casefn.cache + +function cleanup { + rm -f $join $search $in +} + +trap cleanup EXIT +cleanup + +if [ $# -lt 1 ] ; then + exit 0 +fi + +scmd="stat -c %Y" +if [ $(uname) = "Darwin" ] ; then + scmd="stat -f %m" +fi + +function dirtime { + d=$1 + if [ -d $d ] ; then + $scmd $d + else + echo 0 + fi +} + +here=$(pwd) +chead="$here" + +# +# normalize to lower case all input file names +# while building an enumeration of all distinct directories +# +for infn in $* ; do + dirn=$(dirname $infn) + df= + for dl in $dirs ; do + if [ $dl == $dirn ] ; then + df=$dl + break; + fi + done + if [ -z $df ] ; then + dirs="$dirs $dirn" + chead="$chead $dirn:$(dirtime $dirn)" + fi + echo -n $dirn/ >> $in + basename $infn | tr '[A-Z]' '[a-z]' >> $in +done +sort -u $in > $search + +# +# if our cached join list matches our directory list, use it +# +if [ -f $cache ] ; then + cachedirs="$(head -1 $cache)" + if [ "$chead" = "$cachedirs" ] ; then + # echo hit >/dev/stderr + tail -n +2 $cache > $join + else + # echo miss >/dev/stderr + rm -f $cache + fi +fi + +# +# build join list of file names and lower case forms +# +if [ ! -f $join ] ; then + rm -f $in + for dn in $dirs ; do + cd $here + cd $dn + for i in * ; do + # skip any file names containing a space + if echo "$i" | grep -sq " " ; then + continue + fi + echo $dn/$(echo "$i" | tr '[A-Z]' '[a-z]')",$dn/$i" >> $in + done + done + sort -t, -k 1,1 $in > $join + echo "$chead" > $cache + cat $join >> $cache +fi + +join -t, -o 1.2 $join $search | sort -u > $in +if [ $(wc -l < $in) -gt 0 ] ; then + cat $in + exit 0 +fi +exit 2 diff --git a/Tools/unix/cpmtools/COPYING b/Tools/unix/cpmtools/COPYING new file mode 100644 index 00000000..44325404 --- /dev/null +++ b/Tools/unix/cpmtools/COPYING @@ -0,0 +1,676 @@ + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/Tools/unix/cpmtools/Makefile b/Tools/unix/cpmtools/Makefile new file mode 100644 index 00000000..d630b6f2 --- /dev/null +++ b/Tools/unix/cpmtools/Makefile @@ -0,0 +1,61 @@ +# +# cpmtools makefile stripped down to remove autoconf +# + +UNAME := $(shell uname) +DEST = ../../$(UNAME) + +CC = gcc +CFLAGS = -g + +DEFFORMAT = ibm-3740 +DEVICE = posix +DISKDEFS=$(shell cd ../../cpmtools ; pwd)/diskdefs +DEFFORMAT=wbw_fd144 + +# Locate the ncurses libs or try a guess if pkg-config does not exist +NCURSESLIBS=$(shell if env pkg-config --libs ncurses ; then : ; else echo -lncurses ; fi) + +CPPFLAGS = -DDISKDEFS=\"$(DISKDEFS)\" -DFORMAT=\"$(DEFFORMAT)\" + +DEVICEOBJ = device_posix.o + +OBJECTS = cpmls cpmrm cpmcp cpmchmod cpmchattr mkfs.cpm fsck.cpm fsed.cpm + +all: $(OBJECTS) $(DEST) + cp $(OBJECTS) $(DEST) + +cpmls: cpmls.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ cpmls.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +cpmrm: cpmrm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ cpmrm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +cpmcp: cpmcp.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ cpmcp.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +cpmchmod: cpmchmod.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ cpmchmod.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +cpmchattr: cpmchattr.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ cpmchattr.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +mkfs.cpm: mkfs.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ mkfs.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +fsck.cpm: fsck.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ fsck.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + +fsed.cpm: fsed.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) + $(CC) $(LDFLAGS) -o $@ fsed.cpm.o cpmfs.o getopt.o getopt1.o $(DEVICEOBJ) $(NCURSESLIBS) + +$(DEST): + mkdir -p $(DEST) + +clean: + -rm -f *.o $(OBJECTS) + +clobber: clean + -for i in $(OBJECTS) ; do \ + rm -f $(DEST)/$$i ; \ + done diff --git a/Tools/unix/cpmtools/badfs/Makefile b/Tools/unix/cpmtools/badfs/Makefile new file mode 100644 index 00000000..75c4ee00 --- /dev/null +++ b/Tools/unix/cpmtools/badfs/Makefile @@ -0,0 +1,7 @@ +# +# Dummy makefile +# +all: +clean: +distclean: + diff --git a/Tools/unix/cpmtools/badfs/blocknumber b/Tools/unix/cpmtools/badfs/blocknumber new file mode 100644 index 00000000..9259cacb Binary files /dev/null and b/Tools/unix/cpmtools/badfs/blocknumber differ diff --git a/Tools/unix/cpmtools/badfs/doubleext b/Tools/unix/cpmtools/badfs/doubleext new file mode 100644 index 00000000..8ee7d65b Binary files /dev/null and b/Tools/unix/cpmtools/badfs/doubleext differ diff --git a/Tools/unix/cpmtools/badfs/extension b/Tools/unix/cpmtools/badfs/extension new file mode 100644 index 00000000..cc02845f Binary files /dev/null and b/Tools/unix/cpmtools/badfs/extension differ diff --git a/Tools/unix/cpmtools/badfs/extno b/Tools/unix/cpmtools/badfs/extno new file mode 100644 index 00000000..5948b0ee Binary files /dev/null and b/Tools/unix/cpmtools/badfs/extno differ diff --git a/Tools/unix/cpmtools/badfs/hugecom b/Tools/unix/cpmtools/badfs/hugecom new file mode 100644 index 00000000..3d8fea49 Binary files /dev/null and b/Tools/unix/cpmtools/badfs/hugecom differ diff --git a/Tools/unix/cpmtools/badfs/label b/Tools/unix/cpmtools/badfs/label new file mode 100644 index 00000000..982928d1 Binary files /dev/null and b/Tools/unix/cpmtools/badfs/label differ diff --git a/Tools/unix/cpmtools/badfs/lcr b/Tools/unix/cpmtools/badfs/lcr new file mode 100644 index 00000000..da20f4b5 Binary files /dev/null and b/Tools/unix/cpmtools/badfs/lcr differ diff --git a/Tools/unix/cpmtools/badfs/multipleblocks b/Tools/unix/cpmtools/badfs/multipleblocks new file mode 100644 index 00000000..52c1d922 Binary files /dev/null and b/Tools/unix/cpmtools/badfs/multipleblocks differ diff --git a/Tools/unix/cpmtools/badfs/name b/Tools/unix/cpmtools/badfs/name new file mode 100644 index 00000000..67265aca Binary files /dev/null and b/Tools/unix/cpmtools/badfs/name differ diff --git a/Tools/unix/cpmtools/badfs/recordcount b/Tools/unix/cpmtools/badfs/recordcount new file mode 100644 index 00000000..e8633a56 Binary files /dev/null and b/Tools/unix/cpmtools/badfs/recordcount differ diff --git a/Tools/unix/cpmtools/badfs/status b/Tools/unix/cpmtools/badfs/status new file mode 100644 index 00000000..33da2481 Binary files /dev/null and b/Tools/unix/cpmtools/badfs/status differ diff --git a/Tools/unix/cpmtools/badfs/timestamps b/Tools/unix/cpmtools/badfs/timestamps new file mode 100644 index 00000000..a434cc96 Binary files /dev/null and b/Tools/unix/cpmtools/badfs/timestamps differ diff --git a/Tools/unix/cpmtools/config.h b/Tools/unix/cpmtools/config.h new file mode 100644 index 00000000..45bf194d --- /dev/null +++ b/Tools/unix/cpmtools/config.h @@ -0,0 +1,57 @@ +/* config.h. Generated from config.h.in by configure. */ +#define HAVE_FCNTL_H 1 +#define HAVE_LIMITS_H 1 +#define HAVE_UNISTD_H 1 +#define HAVE_WINDOWS_H 0 +#define HAVE_WINIOCTL_H 0 +#define HAVE_LIBDSK_H 0 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_MODE_T 0 +#define NEED_NCURSES 1 +#define HAVE_NCURSES_NCURSES_H 0 + +#if HAVE_SYS_STAT_H +#include +#endif + +#if HAVE_SYS_TYPES_H +#include +#endif + +#if HAVE_LIMITS_H +#include +#endif + +#if HAVE_UNISTD_H +#include +#endif + +#if HAVE_WINDOWS_H +#include +#endif + +#if HAVE_WINIOCTL_H +#include +#endif + +#if HAVE_LIBDSK_H +#include +#endif + +#if HAVE_FCNTL_H +#include +#endif + +#ifndef _POSIX_PATH_MAX +#define _POSIX_PATH_MAX _MAX_PATH +#endif + +#include + +/* Define either for large file support, if your OS needs them. */ +/* #undef _FILE_OFFSET_BITS */ +/* #undef _LARGE_FILES */ + +/* Define if using dmalloc */ +/* #undef USE_DMALLOC */ diff --git a/Tools/unix/cpmtools/cpm.5 b/Tools/unix/cpmtools/cpm.5 new file mode 100644 index 00000000..9f11ff98 --- /dev/null +++ b/Tools/unix/cpmtools/cpm.5 @@ -0,0 +1,300 @@ +.\" Believe it or not, reportedly there are nroffs which do not know \(en +.if n .ds en - +.if t .ds en \(en +.TH CPM 5 "October 25, 2014" "CP/M tools" "File formats" +.SH NAME \"{{{roff}}}\"{{{ +cpm \- CP/M disk and file system format +.\"}}} +.SH DESCRIPTION \"{{{ +.SS "Characteristic sizes" \"{{{ +Each CP/M disk format is described by the following specific sizes: +.RS +.sp +Sector size in bytes +.br +Number of tracks +.br +Number of sectors +.br +Block size +.br +Number of directory entries +.br +Logical sector skew +.br +Number of reserved system tracks (optional) +.br +Offset to start of volume (optional and not covered by operating system, +but disk driver specific) +.sp +.RE +A block is the smallest allocatable storage unit. CP/M supports block +sizes of 1024, 2048, 4096, 8192 and 16384 bytes. Unfortunately, this +format specification is not stored on the disk and there are lots of +formats. Accessing a block is performed by accessing its sectors, which +are stored with the given software skew. +.\"}}} +.SS "Device areas" \"{{{ +A CP/M disk contains four areas: +.RS +.sp +Volume offset (optional and not covered by operating system, but disk driver specific) +.br +System tracks (optional) +.br +Directory +.br +Data +.sp +.RE +The system tracks store the boot loader and CP/M itself. In order to save +disk space, there are non-bootable formats which omit those system tracks. +The term \fIdisk capacity\fP always excludes the space for system tracks. +Note that there is no bitmap or list for free blocks. When accessing a +drive for the first time, CP/M builds this bitmap in core from the directory. +.LP +A hard disk can have the additional notion of a \fIvolume offset\fP to +locate the start of the drive image (which may or may not have system +tracks associated with it). The base unit for volume offset is byte +count from the beginning of the physical disk, but specifiers of +\fIK\fP, \fIM\fP, \fIT\fP or \fIS\fP may be appended to denote +kilobytes, megabytes, tracks or sectors. If provided, a specifier +must immediately follow the numeric value with no whitespace. For +convenience upper and lower case are both accepted and only the first +letter is significant, thus 2KB, 8MB, 1000trk and 16sec are valid +values. The \fBoffset\fP must appear subsequent to track, sector and sector +length values for the sector and track units to work. +.\"}}} +.SS "Directory entries" \"{{{ +The directory is a sequence of directory entries (also called extents), +which contain 32 bytes of the following structure: +.RS +.sp +.ta 3n 6n 9n 12n 15n 18n 21n 24n 27n 30n 33n 36n 39n 42n 45n +St F0 F1 F2 F3 F4 F5 F6 F7 E0 E1 E2 Xl Bc Xh Rc +.br +Al Al Al Al Al Al Al Al Al Al Al Al Al Al Al Al +.sp +.RE +.\"{{{ St = status +\fBSt\fP is the status; possible values are: +.RS +.sp +0\*(en15: used for file, status is the user number +.br +16\*(en31: used for file, status is the user number (P2DOS) +or used for password extent (CP/M 3 or higher) +.br +32: disc label +.br +33: time stamp (P2DOS) +.br +0xE5: unused +.sp +.RE +.\"}}} +.LP +.\"{{{ F0-E2 = file name and extension +\fBF0\*(enE2\fP are the file name and its extension. They may consist of +any printable 7 bit ASCII character but: \fB< > . , ; : = ? * [ ]\fP. +The file name must not be empty, the extension may be empty. Both are +padded with blanks. The highest bit of each character of the file name +and extension is used as attribute. The attributes have the following +meaning: +.RS +.sp +F0: requires set wheel byte (Backgrounder II) +.br +F1: public file (P2DOS, ZSDOS), forground-only command (Backgrounder II) +.br +F2: date stamp (ZSDOS), background-only commands (Backgrounder II) +.br +F7: wheel protect (ZSDOS) +.br +E0: read-only +.br +E1: system file +.br +E2: archived +.sp +.RE +Public files (visible under each user number) are not supported by CP/M +2.2, but there is a patch and some free CP/M clones support them without +any patches. +.LP +The wheel byte is (by default) the memory location at 0x4b. If it is +zero, only non-privileged commands may be executed. +.\"}}} +.LP +.\"{{{ Xl, Xh = extent number +\fBXl\fP and \fBXh\fP store the extent number. A file may use more than +one directory entry, if it contains more blocks than an extent can hold. +In this case, more extents are allocated and each of them is numbered +sequentially with an extent number. If a physical extent stores more than +16k, it is considered to contain multiple logical extents, each pointing +to 16k data, and the extent number of the last used logical extent +is stored. Note: Some formats decided to always store only one logical +extent in a physical extent, thus wasting extent space. CP/M 2.2 allows +512 extents per file, CP/M 3 and higher allow up to 2048. Bit 5\*(en7 of +Xl are 0, bit 0\*(en4 store the lower bits of the extent number. Bit 6 +and 7 of Xh are 0, bit 0\*(en5 store the higher bits of the extent number. +.\"}}} +.LP +.\"{{{ Rc, Bc = record count, byte count +\fBRc\fP and \fBBc\fP determine the length of the data used by this extent. The +physical extent is divided into logical extents, each of them being 16k +in size (a physical extent must hold at least one logical extent, e.g. a +blocksize of 1024 byte with two-byte block pointers is not allowed). +Rc stores the number of 128 byte records of the last used logical extent. +Bc stores the number of bytes in the last used record. The value 0 means +128 for backward compatibility with CP/M 2.2, which did not support Bc. +ISX records the number of unused instead of used bytes in Bc. +.\"}}} +.LP +.\"{{{ Al = allocated blocks +\fBAl\fP stores block pointers. If the disk capacity minus boot +tracks but including the directory area is less than 256 blocks, Al +is interpreted as 16 byte-values, otherwise as 8 double-byte-values. +Since the directory area is not subtracted, the directory area starts +with block 0 and files can never allocate block 0, which is why this +value can be given a new meaning: A block pointer of 0 marks a hole in +the file. If a hole covers the range of a full extent, the extent will +not be allocated. In particular, the first extent of a file does not +neccessarily have extent number 0. A file may not share blocks with other +files, as its blocks would be freed if the other files is erased without +a following disk system reset. CP/M returns EOF when it reaches a hole, +whereas UNIX returns zero-value bytes, which makes holes invisible. +.\"}}} +.\"}}} +.SS "Native time stamps" \"{{{ +P2DOS and CP/M Plus support time stamps, which are stored in each fourth +directory entry. This entry contains the time stamps for +the extents using the previous three directory entries. Note that you +really have time stamps for each extent, no matter if it is the first +extent of a file or not. The structure of time stamp entries is: +.RS +.sp +1 byte status 0x21 +.br +8 bytes time stamp for third-last directory entry +.br +2 bytes unused +.br +8 bytes time stamp for second-last directory entry +.br +2 bytes unused +.br +8 bytes time stamp for last directory entry +.sp +.RE +A time stamp consists of two dates: Creation and modification date (the +latter being recorded when the file is closed). CP/M Plus further +allows optionally to record the access instead of creation date as first +time stamp. +.RS +.sp +2 bytes (little-endian) days starting with 1 at 01-01-1978 +.br +1 byte hour in BCD format +.br +1 byte minute in BCD format +.sp +.RE +All time stamps are stored in local time. +.\"}}} +.SS "DateStamper time stamps" \"{{{ +The DateStamper software added functions to the BDOS to manage +time stamps by allocating a read only file with the name "!!!TIME&.DAT" +in the very first directory entry, covering the very first data +blocks. It contains one entry per directory entry with the +following structure of 16 bytes: +.RS +.sp +5 bytes create datefield +.br +5 bytes access datefield +.br +5 bytes modify datefield +.br +1 byte magic number/checksum +.sp +.RE +The magic number is used for the first 7 entries of each 128-byte record +and contains the characters \fB!\fP, \fB!\fP, \fB!\fP, \fBT\fP, \fBI\fP, +\fBM\fP and \fBE\fP. The checksum is used on every 8th entry (last entry +in 128-byte record) and is the sum of the first 127 bytes of the record. +Each datefield has this structure: +.RS +.sp +1 byte BCD coded year (no century, so it is sane assuming any year < 70 +means 21st century) +.br +1 byte BCD coded month +.br +1 byte BCD coded day +.br +1 byte BCD coded hour or, if the high bit is set, the high byte of a +counter for systems without real time clock +.br +1 byte BCD coded minute, or the low byte of the counter +.sp +.DE +.\"}}} +.SS "Disc labels" \"{{{ +CP/M Plus support disc labels, which are stored in an arbitrary directory +entry. +The structure of disc labels is: +.RS +.sp +1 byte status 0x20 +.br +\fBF0\*(enE2\fP are the disc label +.br +1 byte mode: bit 7 activates password protection, bit 6 causes time stamps on +access, but 5 causes time stamps on modifications, bit 4 causes time stamps on +creation and bit 0 is set when a label exists. Bit 4 and 6 are exclusively set. +.br +1 byte password decode byte: To decode the password, xor this byte with the password +bytes in reverse order. To encode a password, add its characters to get the +decode byte. +.br +2 reserved bytes +.br +8 password bytes +.br +4 bytes label creation time stamp +.br +4 bytes label modification time stamp +.sp +.RE +.\"}}} +.SS "Passwords" \"{{{ +CP/M Plus supports passwords, which are stored in an arbitrary directory +entry. +The structure of these entries is: +.RS +.sp +1 byte status (user number plus 16) +.br +\fBF0\*(enE2\fP are the file name and its extension. +.br +1 byte password mode: bit 7 means password required for reading, bit 6 for writing +and bit 5 for deleting. +.br +1 byte password decode byte: To decode the password, xor this byte with the password +bytes in reverse order. To encode a password, add its characters to get the +decode byte. +.br +2 reserved bytes +.br +8 password bytes +.sp +.RE +.\"}}} +.\"}}} +.SH "SEE ALSO" \"{{{ +.IR mkfs.cpm (1), +.IR fsck.cpm (1), +.IR fsed.cpm (1), +.IR cpmls (1) +.\"}}} diff --git a/Tools/unix/cpmtools/cpm.ps b/Tools/unix/cpmtools/cpm.ps new file mode 100644 index 00000000..6c631338 --- /dev/null +++ b/Tools/unix/cpmtools/cpm.ps @@ -0,0 +1,478 @@ +%!PS-Adobe-3.0 +%%Creator: groff version 1.19 +%%CreationDate: Sun Feb 3 19:48:55 2013 +%%DocumentNeededResources: font Times-Roman +%%+ font Times-Bold +%%+ font Times-Italic +%%DocumentSuppliedResources: procset grops 1.19 0 +%%Pages: 4 +%%PageOrder: Ascend +%%DocumentMedia: Default 595 842 0 () () +%%Orientation: Portrait +%%EndComments +%%BeginDefaults +%%PageMedia: Default +%%EndDefaults +%%BeginProlog +%%BeginResource: procset grops 1.19 0 +/setpacking where{ +pop +currentpacking +true setpacking +}if +/grops 120 dict dup begin +/SC 32 def +/A/show load def +/B{0 SC 3 -1 roll widthshow}bind def +/C{0 exch ashow}bind def +/D{0 exch 0 SC 5 2 roll awidthshow}bind def +/E{0 rmoveto show}bind def +/F{0 rmoveto 0 SC 3 -1 roll widthshow}bind def +/G{0 rmoveto 0 exch ashow}bind def +/H{0 rmoveto 0 exch 0 SC 5 2 roll awidthshow}bind def +/I{0 exch rmoveto show}bind def +/J{0 exch rmoveto 0 SC 3 -1 roll widthshow}bind def +/K{0 exch rmoveto 0 exch ashow}bind def +/L{0 exch rmoveto 0 exch 0 SC 5 2 roll awidthshow}bind def +/M{rmoveto show}bind def +/N{rmoveto 0 SC 3 -1 roll widthshow}bind def +/O{rmoveto 0 exch ashow}bind def +/P{rmoveto 0 exch 0 SC 5 2 roll awidthshow}bind def +/Q{moveto show}bind def +/R{moveto 0 SC 3 -1 roll widthshow}bind def +/S{moveto 0 exch ashow}bind def +/T{moveto 0 exch 0 SC 5 2 roll awidthshow}bind def +/SF{ +findfont exch +[exch dup 0 exch 0 exch neg 0 0]makefont +dup setfont +[exch/setfont cvx]cvx bind def +}bind def +/MF{ +findfont +[5 2 roll +0 3 1 roll +neg 0 0]makefont +dup setfont +[exch/setfont cvx]cvx bind def +}bind def +/level0 0 def +/RES 0 def +/PL 0 def +/LS 0 def +/MANUAL{ +statusdict begin/manualfeed true store end +}bind def +/PLG{ +gsave newpath clippath pathbbox grestore +exch pop add exch pop +}bind def +/BP{ +/level0 save def +1 setlinecap +1 setlinejoin +72 RES div dup scale +LS{ +90 rotate +}{ +0 PL translate +}ifelse +1 -1 scale +}bind def +/EP{ +level0 restore +showpage +}bind def +/DA{ +newpath arcn stroke +}bind def +/SN{ +transform +.25 sub exch .25 sub exch +round .25 add exch round .25 add exch +itransform +}bind def +/DL{ +SN +moveto +SN +lineto stroke +}bind def +/DC{ +newpath 0 360 arc closepath +}bind def +/TM matrix def +/DE{ +TM currentmatrix pop +translate scale newpath 0 0 .5 0 360 arc closepath +TM setmatrix +}bind def +/RC/rcurveto load def +/RL/rlineto load def +/ST/stroke load def +/MT/moveto load def +/CL/closepath load def +/Fr{ +setrgbcolor fill +}bind def +/setcmykcolor where{ +pop +/Fk{ +setcmykcolor fill +}bind def +}if +/Fg{ +setgray fill +}bind def +/FL/fill load def +/LW/setlinewidth load def +/Cr/setrgbcolor load def +/setcmykcolor where{ +pop +/Ck/setcmykcolor load def +}if +/Cg/setgray load def +/RE{ +findfont +dup maxlength 1 index/FontName known not{1 add}if dict begin +{ +1 index/FID ne{def}{pop pop}ifelse +}forall +/Encoding exch def +dup/FontName exch def +currentdict end definefont pop +}bind def +/DEFS 0 def +/EBEGIN{ +moveto +DEFS begin +}bind def +/EEND/end load def +/CNT 0 def +/level1 0 def +/PBEGIN{ +/level1 save def +translate +div 3 1 roll div exch scale +neg exch neg exch translate +0 setgray +0 setlinecap +1 setlinewidth +0 setlinejoin +10 setmiterlimit +[]0 setdash +/setstrokeadjust where{ +pop +false setstrokeadjust +}if +/setoverprint where{ +pop +false setoverprint +}if +newpath +/CNT countdictstack def +userdict begin +/showpage{}def +/setpagedevice{}def +}bind def +/PEND{ +clear +countdictstack CNT sub{end}repeat +level1 restore +}bind def +end def +/setpacking where{ +pop +setpacking +}if +%%EndResource +%%BeginFeature: *PageSize Default +<< /PageSize [ 595 842 ] /ImagingBBox null >> setpagedevice +%%EndFeature +%%IncludeResource: font Times-Roman +%%IncludeResource: font Times-Bold +%%IncludeResource: font Times-Italic +grops begin/DEFS 1 dict def DEFS begin/u{.001 mul}bind def end/RES 72 +def/PL 841.89 def/LS false def/ENC0[/asciicircum/asciitilde/Scaron +/Zcaron/scaron/zcaron/Ydieresis/trademark/quotesingle/Euro/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef/.notdef +/.notdef/.notdef/.notdef/space/exclam/quotedbl/numbersign/dollar/percent +/ampersand/quoteright/parenleft/parenright/asterisk/plus/comma/hyphen +/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon +/semicolon/less/equal/greater/question/at/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O +/P/Q/R/S/T/U/V/W/X/Y/Z/bracketleft/backslash/bracketright/circumflex +/underscore/quoteleft/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y +/z/braceleft/bar/braceright/tilde/.notdef/quotesinglbase/guillemotleft +/guillemotright/bullet/florin/fraction/perthousand/dagger/daggerdbl +/endash/emdash/ff/fi/fl/ffi/ffl/dotlessi/dotlessj/grave/hungarumlaut +/dotaccent/breve/caron/ring/ogonek/quotedblleft/quotedblright/oe/lslash +/quotedblbase/OE/Lslash/.notdef/exclamdown/cent/sterling/currency/yen +/brokenbar/section/dieresis/copyright/ordfeminine/guilsinglleft +/logicalnot/minus/registered/macron/degree/plusminus/twosuperior +/threesuperior/acute/mu/paragraph/periodcentered/cedilla/onesuperior +/ordmasculine/guilsinglright/onequarter/onehalf/threequarters +/questiondown/Agrave/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE +/Ccedilla/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex +/Idieresis/Eth/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis +/multiply/Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn +/germandbls/agrave/aacute/acircumflex/atilde/adieresis/aring/ae/ccedilla +/egrave/eacute/ecircumflex/edieresis/igrave/iacute/icircumflex/idieresis +/eth/ntilde/ograve/oacute/ocircumflex/otilde/odieresis/divide/oslash +/ugrave/uacute/ucircumflex/udieresis/yacute/thorn/ydieresis]def +/Times-Italic@0 ENC0/Times-Italic RE/Times-Bold@0 ENC0/Times-Bold RE +/Times-Roman@0 ENC0/Times-Roman RE +%%EndProlog +%%Page: 1 1 +%%BeginPageSetup +BP +%%EndPageSetup +/F0 10/Times-Roman@0 SF 174.415(CPM\(5\) File)72 48 R 174.415 +(formats CPM\(5\))2.5 F/F1 10.95/Times-Bold@0 SF -.219(NA)72 84 S(ME) +.219 E F0(cpm \255 CP/M disk and \214le system format)108 96 Q F1 +(DESCRIPTION)72 112.8 Q/F2 10/Times-Bold@0 SF(Characteristic sizes)87 +124.8 Q F0(Each CP/M disk format is described by the follo)108 136.8 Q +(wing speci\214c sizes:)-.25 E(Sector size in bytes)144 160.8 Q +(Number of tracks)144 172.8 Q(Number of sectors)144 184.8 Q(Block size) +144 196.8 Q(Number of directory entries)144 208.8 Q(Logical sector sk) +144 220.8 Q -.25(ew)-.1 G(Number of reserv)144 232.8 Q +(ed system tracks \(optional\))-.15 E(Of)144 244.8 Q(fset to start of v) +-.25 E(olume \(optional\))-.2 E 2.848(Ab)108 268.8 S .348 +(lock is the smallest allocatable storage unit.)-2.848 F .347 +(CP/M supports block sizes of 1024, 2048, 4096, 8192 and)5.348 F .207 +(16384 bytes.)108 280.8 R(Unfortunately)5.207 E 2.707(,t)-.65 G .208(hi\ +s format speci\214cation is not stored on the disk and there are lots o\ +f formats.)-2.707 F(Accessing a block is performed by accessing its sec\ +tors, which are stored with the gi)108 292.8 Q -.15(ve)-.25 G 2.5(ns).15 +G(oftw)-2.5 E(are sk)-.1 E -.25(ew)-.1 G(.)-.4 E F2(De)87 309.6 Q +(vice ar)-.15 E(eas)-.18 E F0 2.5(AC)108 321.6 S +(P/M disk contains three areas:)-2.5 E -1.29(Vo)144 345.6 S(lume of)1.29 +E(fset \(optional\))-.25 E(System tracks \(optional\))144 357.6 Q +(Directory)144 369.6 Q(Data)144 381.6 Q .058 +(The system tracks store the boot loader and CP/M itself.)108 405.6 R +.058(In order to sa)5.058 F .358 -.15(ve d)-.2 H .057 +(isk space, there are non-bootable).15 F 1.55 +(formats which omit those system tracks.)108 417.6 R 1.55(The term)6.55 +F/F3 10/Times-Italic@0 SF 1.55(disk capacity)4.05 F F0(al)4.05 E -.1(wa) +-.1 G 1.55(ys e).1 F 1.55(xcludes the space for system)-.15 F 2.748 +(tracks. Note)108 429.6 R .248 +(that there is no bitmap or list for free blocks.)2.748 F .248 +(When accessing a dri)5.248 F .548 -.15(ve f)-.25 H .248 +(or the \214rst time, CP/M).15 F -.2(bu)108 441.6 S +(ilds this bitmap in core from the directory).2 E(.)-.65 E 3.15(Ah)108 +458.4 S .65(ard disk can ha)-3.15 F .95 -.15(ve t)-.2 H .65 +(he additional notion of a).15 F F3 .65(volume of)3.15 F(fset)-.18 E F0 +.65(to locate the start of the dri)3.15 F .95 -.15(ve i)-.25 H .65 +(mage \(which).15 F .531(may or may not ha)108 470.4 R .831 -.15(ve s) +-.2 H .531(ystem tracks associated with it\). The base unit for v).15 F +.53(olume of)-.2 F .53(fset is byte count from)-.25 F 1.224(the be)108 +482.4 R 1.224(ginning of the ph)-.15 F 1.224(ysical disk, b)-.05 F 1.225 +(ut speci\214ers of)-.2 F F3(K)3.725 E F0(,)A F3(M)3.725 E F0(,)A F3(T) +3.725 E F0(or)3.725 E F3(S)3.725 E F0 1.225 +(may be appended to denote kilobytes,)3.725 F(me)108 494.4 Q -.05(ga) +-.15 G .806(bytes, tracks or sectors.).05 F .806(If pro)5.806 F .805 +(vided, a speci\214er must immediately follo)-.15 F 3.305(wt)-.25 G .805 +(he numeric v)-3.305 F .805(alue with no)-.25 F 2.881(whitespace. F)108 +506.4 R .381(or con)-.15 F -.15(ve)-.4 G .381(nience upper and lo).15 F +.381(wer case are both accepted and only the \214rst letter is signi\ +\214cant,)-.25 F .02(thus 2KB, 8MB, 1000trk and 16sec are v)108 518.4 R +.019(alid v)-.25 F .019(alues. Of)-.25 F .019 +(fset must appear subsequent to track, sector and sec-)-.25 F +(tor length v)108 530.4 Q(alues.)-.25 E F2(Dir)87 547.2 Q +(ectory entries)-.18 E F0 .408 +(The directory is a sequence of directory entries \(also called e)108 +559.2 R .409(xtents\), which contain 32 bytes of the follo)-.15 F(w-) +-.25 E(ing structure:)108 571.2 Q 4.16(St F0)144 595.2 R 1.94 +(F1 F2 F3 F4 F5 F6 F7 E0)4.44 F 1.39(E1 E2 Xl)3.89 F 1.39(Bc Xh)5 F(Rc) +2.78 E 2.5(Al Al Al Al Al Al Al Al Al Al Al Al Al Al Al Al)144 607.2 R +F2(St)108 631.2 Q F0(is the status; possible v)2.5 E(alues are:)-.25 E +(0\21115: used for \214le, status is the user number)144 655.2 Q .795(1\ +6\21131: used for \214le, status is the user number \(P2DOS\) or used f\ +or passw)144 667.2 R .794(ord e)-.1 F .794(xtent \(CP/M 3 or)-.15 F +(higher\))144 679.2 Q(32: disc label)144 691.2 Q +(33: time stamp \(P2DOS\))144 703.2 Q(0xE5: unused)144 715.2 Q +(CP/M tools)72 768 Q(February 18, 2012)151.35 E(1)192.2 E 0 Cg EP +%%Page: 2 2 +%%BeginPageSetup +BP +%%EndPageSetup +/F0 10/Times-Roman@0 SF 174.415(CPM\(5\) File)72 48 R 174.415 +(formats CPM\(5\))2.5 F/F1 10/Times-Bold@0 SF(F0\211E2)108 84 Q F0 .412 +(are the \214le name and its e)2.912 F 2.913(xtension. The)-.15 F 2.913 +(ym)-.15 G .413(ay consist of an)-2.913 F 2.913(yp)-.15 G .413 +(rintable 7 bit ASCII character b)-2.913 F(ut:)-.2 E F1(<)2.913 E 3.362 +(>.,;:=?*[])108 96 S F0 5.862(.T)-3.362 G .862 +(he \214le name must not be empty)-5.862 F 3.361(,t)-.65 G .861(he e) +-3.361 F .861(xtension may be empty)-.15 F 5.861(.B)-.65 G .861 +(oth are padded with)-5.861 F 2.831(blanks. The)108 108 R .331 +(highest bit of each character of the \214le name and e)2.831 F .331 +(xtension is used as attrib)-.15 F 2.832(ute. The)-.2 F(attrib)2.832 E +(utes)-.2 E(ha)108 120 Q .3 -.15(ve t)-.2 H(he follo).15 E +(wing meaning:)-.25 E(F0: requires set wheel byte \(Backgrounder II\)) +144 144 Q(F1: public \214le \(P2DOS, ZSDOS\), for)144 156 Q +(ground-only command \(Backgrounder II\))-.18 E +(F2: date stamp \(ZSDOS\), background-only commands \(Backgrounder II\)) +144 168 Q(F7: wheel protect \(ZSDOS\))144 180 Q(E0: read-only)144 192 Q +(E1: system \214le)144 204 Q(E2: archi)144 216 Q -.15(ve)-.25 G(d).15 E +.338(Public \214les \(visible under each user number\) are not supporte\ +d by CP/M 2.2, b)108 240 R .338(ut there is a patch and some)-.2 F +(free CP/M clones support them without an)108 252 Q 2.5(yp)-.15 G +(atches.)-2.5 E .827(The wheel byte is \(by def)108 268.8 R .828 +(ault\) the memory location at 0x4b)-.1 F 5.828(.I)-.4 G 3.328(fi)-5.828 +G 3.328(ti)-3.328 G 3.328(sz)-3.328 G .828(ero, only non-pri)-3.328 F +(vile)-.25 E .828(ged commands)-.15 F(may be e)108 280.8 Q -.15(xe)-.15 +G(cuted.).15 E F1(Xl)108 297.6 Q F0(and)2.546 E F1(Xh)2.546 E F0 .046 +(store the e)2.546 F .046(xtent number)-.15 F 5.046(.A)-.55 G .045 +(\214le may use more than one directory entry)-2.5 F 2.545(,i)-.65 G +2.545(fi)-2.545 G 2.545(tc)-2.545 G .045(ontains more blocks)-2.545 F +.21(than an e)108 309.6 R .21(xtent can hold.)-.15 F .21 +(In this case, more e)5.21 F .21 +(xtents are allocated and each of them is numbered sequentially)-.15 F +.457(with an e)108 321.6 R .457(xtent number)-.15 F 5.457(.I)-.55 G +2.957(fap)-5.457 G -.05(hy)-2.957 G .457(sical e).05 F .456 +(xtent stores more than 16k, it is considered to contain multiple logi-) +-.15 F .234(cal e)108 333.6 R .234 +(xtents, each pointing to 16k data, and the e)-.15 F .234 +(xtent number of the last used logical e)-.15 F .235(xtent is stored.) +-.15 F(Note:)5.235 E 1.55(Some formats decided to al)108 345.6 R -.1(wa) +-.1 G 1.549(ys store only one logical e).1 F 1.549(xtent in a ph)-.15 F +1.549(ysical e)-.05 F 1.549(xtent, thus w)-.15 F 1.549(asting e)-.1 F +(xtent)-.15 E 2.81(space. CP/M)108 357.6 R .31(2.2 allo)2.81 F .31 +(ws 512 e)-.25 F .31(xtents per \214le, CP/M 3 and higher allo)-.15 F +2.811(wu)-.25 G 2.811(pt)-2.811 G 2.811(o2)-2.811 G 2.811(048. Bit) +-2.811 F .311(5\2117 of Xl are 0, bit)2.811 F .577(0\2114 store the lo) +108 369.6 R .577(wer bits of the e)-.25 F .576(xtent number)-.15 F 5.576 +(.B)-.55 G .576 +(it 6 and 7 of Xh are 0, bit 0\2115 store the higher bits of the)-5.576 +F -.15(ex)108 381.6 S(tent number).15 E(.)-.55 E F1(Rc)108 398.4 Q F0 +(and)2.946 E F1(Bc)2.946 E F0 .446 +(determine the length of the data used by this e)2.946 F 2.946 +(xtent. The)-.15 F(ph)2.947 E .447(ysical e)-.05 F .447(xtent is di)-.15 +F .447(vided into logical)-.25 F -.15(ex)108 410.4 S .156 +(tents, each of them being 16k in size \(a ph).15 F .156(ysical e)-.05 F +.156(xtent must hold at least one logical e)-.15 F .156 +(xtent, e.g. a block-)-.15 F .053(size of 1024 byte with tw)108 422.4 R +.054(o-byte block pointers is not allo)-.1 F 2.554(wed\). Rc)-.25 F .054 +(stores the number of 128 byte records of)2.554 F .457 +(the last used logical e)108 434.4 R 2.957(xtent. Bc)-.15 F .456 +(stores the number of bytes in the last used record.)2.957 F .456(The v) +5.456 F .456(alue 0 means 128)-.25 F .654(for backw)108 446.4 R .654 +(ard compatibility with CP/M 2.2, which did not support Bc.)-.1 F .655 +(ISX records the number of unused)5.655 F(instead of used bytes in Bc.) +108 458.4 Q F1(Al)108 475.2 Q F0 .9(stores block pointers.)3.4 F .899(I\ +f the disk capacity is less than 256 blocks, Al is interpreted as 16 by\ +te-v)5.9 F(alues,)-.25 E .243(otherwise as 8 double-byte-v)108 487.2 R +2.743(alues. A)-.25 F .243 +(block pointer of 0 marks a hole in the \214le.)2.743 F .243 +(If a hole co)5.243 F -.15(ve)-.15 G .243(rs the range).15 F .341 +(of a full e)108 499.2 R .341(xtent, the e)-.15 F .341 +(xtent will not be allocated.)-.15 F .34(In particular)5.341 F 2.84(,t) +-.4 G .34(he \214rst e)-2.84 F .34 +(xtent of a \214le does not neccessarily)-.15 F(ha)108 511.2 Q .479 -.15 +(ve ex)-.2 H .179(tent number 0.).15 F 2.679<418c>5.179 G .18 +(le may not share blocks with other \214les, as its blocks w)-2.679 F +.18(ould be freed if the other)-.1 F .822 +(\214les is erased without a follo)108 523.2 R .822 +(wing disk system reset.)-.25 F .822 +(CP/M returns EOF when it reaches a hole, whereas)5.822 F +(UNIX returns zero-v)108 535.2 Q(alue bytes, which mak)-.25 E +(es holes in)-.1 E(visible.)-.4 E F1(Nati)87 552 Q .2 -.1(ve t)-.1 H +(ime stamps).1 E F0 1.053(P2DOS and CP/M Plus support time stamps, whic\ +h are stored in each fourth directory entry)108 564 R 6.054(.T)-.65 G +1.054(his entry)-6.054 F 1.3(contains the time stamps for the e)108 576 +R 1.299(xtents using the pre)-.15 F 1.299 +(vious three directory entries.)-.25 F 1.299(Note that you really)6.299 +F(ha)108 588 Q 1.294 -.15(ve t)-.2 H .994(ime stamps for each e).15 F +.994(xtent, no matter if it is the \214rst e)-.15 F .994 +(xtent of a \214le or not.)-.15 F .995(The structure of time)5.994 F +(stamp entries is:)108 600 Q 2.5(1b)144 624 S(yte status 0x21)-2.5 E 2.5 +(8b)144 636 S(ytes time stamp for third-last directory entry)-2.5 E 2.5 +(2b)144 648 S(ytes unused)-2.5 E 2.5(8b)144 660 S +(ytes time stamp for second-last directory entry)-2.5 E 2.5(2b)144 672 S +(ytes unused)-2.5 E 2.5(8b)144 684 S +(ytes time stamp for last directory entry)-2.5 E 2.872(At)108 708 S .372 +(ime stamp consists of tw)-2.872 F 2.872(od)-.1 G .372(ates: Creation a\ +nd modi\214cation date \(the latter being recorded when the \214le) +-2.872 F .935(is closed\).)108 720 R .936(CP/M Plus further allo)5.935 F +.936(ws optionally to record the access instead of creation date as \ +\214rst time)-.25 F(CP/M tools)72 768 Q(February 18, 2012)151.35 E(2) +192.2 E 0 Cg EP +%%Page: 3 3 +%%BeginPageSetup +BP +%%EndPageSetup +/F0 10/Times-Roman@0 SF 174.415(CPM\(5\) File)72 48 R 174.415 +(formats CPM\(5\))2.5 F(stamp.)108 84 Q 2.5(2b)144 108 S +(ytes \(little-endian\) days starting with 1 at 01-01-1978)-2.5 E 2.5 +(1b)144 120 S(yte hour in BCD format)-2.5 E 2.5(1b)144 132 S +(yte minute in BCD format)-2.5 E/F1 10/Times-Bold@0 SF +(DateStamper time stamps)87 160.8 Q F0 .552(The DateStamper softw)108 +172.8 R .552(are added functions to the BDOS to manage time stamps by a\ +llocating a read only)-.1 F .441(\214le with the name "!!!TIME&.D)108 +184.8 R -1.11(AT)-.4 G 2.941("i)1.11 G 2.941(nt)-2.941 G .441(he v) +-2.941 F .441(ery \214rst directory entry)-.15 F 2.941(,c)-.65 G -.15 +(ove)-2.941 G .441(ring the v).15 F .442(ery \214rst data blocks.)-.15 F +(It)5.442 E(contains one entry per directory entry with the follo)108 +196.8 Q(wing structure of 16 bytes:)-.25 E 2.5(5b)144 220.8 S +(ytes create date\214eld)-2.5 E 2.5(5b)144 232.8 S +(ytes access date\214eld)-2.5 E 2.5(5b)144 244.8 S +(ytes modify date\214eld)-2.5 E 2.5(1b)144 256.8 S(yte checksum)-2.5 E +.237(The checksum is only used on e)108 280.8 R -.15(ve)-.25 G .236(ry \ +8th entry \(last entry in 128-byte record\) and is the sum of the \214r\ +st 127).15 F(bytes of the record.)108 292.8 Q +(Each date\214eld has this structure:)5 E 2.5(1b)144 316.8 S +(yte BCD coded year \(no century)-2.5 E 2.5(,s)-.65 G 2.5(oi)-2.5 G 2.5 +(ti)-2.5 G 2.5(ss)-2.5 G(ane assuming an)-2.5 E 2.5(yy)-.15 G +(ear < 70 means 21st century\))-2.5 E 2.5(1b)144 328.8 S +(yte BCD coded month)-2.5 E 2.5(1b)144 340.8 S(yte BCD coded day)-2.5 E +2.608(1b)144 352.8 S .108(yte BCD coded hour or)-2.608 F 2.608(,i)-.4 G +2.608(ft)-2.608 G .108(he high bit is set, the high byte of a counter f\ +or systems without real)-2.608 F(time clock)144 364.8 Q 2.5(1b)144 376.8 +S(yte BCD coded minute, or the lo)-2.5 E 2.5(wb)-.25 G +(yte of the counter)-2.5 E F1(Disc labels)87 405.6 Q F0 .258(CP/M Plus \ +support disc labels, which are stored in an arbitrary directory entry) +108 417.6 R 5.257(.T)-.65 G .257(he structure of disc labels)-5.257 F +(is:)108 429.6 Q 2.5(1b)144 453.6 S(yte status 0x20)-2.5 E F1(F0\211E2) +144 465.6 Q F0(are the disc label)2.5 E 2.886(1b)144 477.6 S .386 +(yte mode: bit 7 acti)-2.886 F -.25(va)-.25 G .386(tes passw).25 F .387 +(ord protection, bit 6 causes time stamps on access, b)-.1 F .387 +(ut 5 causes)-.2 F .874(time stamps on modi\214cations, bit 4 causes ti\ +me stamps on creation and bit 0 is set when a label)144 489.6 R -.15(ex) +144 501.6 S 2.5(ists. Bit).15 F 2.5(4a)2.5 G(nd 6 are e)-2.5 E(xclusi) +-.15 E -.15(ve)-.25 G(ly set.).15 E 3.45(1b)144 513.6 S .95(yte passw) +-3.45 F .95(ord decode byte: T)-.1 F 3.45(od)-.8 G .951(ecode the passw) +-3.45 F .951(ord, xor this byte with the passw)-.1 F .951(ord bytes in) +-.1 F(re)144 525.6 Q -.15(ve)-.25 G(rse order).15 E 5(.T)-.55 G 2.5(oe) +-5.8 G(ncode a passw)-2.5 E +(ord, add its characters to get the decode byte.)-.1 E 2.5(2r)144 537.6 +S(eserv)-2.5 E(ed bytes)-.15 E 2.5(8p)144 549.6 S(assw)-2.5 E(ord bytes) +-.1 E 2.5(4b)144 561.6 S(ytes label creation time stamp)-2.5 E 2.5(4b) +144 573.6 S(ytes label modi\214cation time stamp)-2.5 E F1 -.1(Pa)87 +602.4 S(ssw).1 E(ords)-.1 E F0 1.484(CP/M Plus supports passw)108 614.4 +R 1.484(ords, which are stored in an arbitrary directory entry)-.1 F +6.484(.T)-.65 G 1.484(he structure of these)-6.484 F(entries is:)108 +626.4 Q 2.5(1b)144 650.4 S(yte status \(user number plus 16\))-2.5 E F1 +(F0\211E2)144 662.4 Q F0(are the \214le name and its e)2.5 E(xtension.) +-.15 E 3.171(1b)144 674.4 S .671(yte passw)-3.171 F .671 +(ord mode: bit 7 means passw)-.1 F .672 +(ord required for reading, bit 6 for writing and bit 5 for)-.1 F +(deleting.)144 686.4 Q 3.451(1b)144 698.4 S .951(yte passw)-3.451 F .951 +(ord decode byte: T)-.1 F 3.451(od)-.8 G .951(ecode the passw)-3.451 F +.95(ord, xor this byte with the passw)-.1 F .95(ord bytes in)-.1 F(re) +144 710.4 Q -.15(ve)-.25 G(rse order).15 E 5(.T)-.55 G 2.5(oe)-5.8 G +(ncode a passw)-2.5 E(ord, add its characters to get the decode byte.) +-.1 E 2.5(2r)144 722.4 S(eserv)-2.5 E(ed bytes)-.15 E(CP/M tools)72 768 +Q(February 18, 2012)151.35 E(3)192.2 E 0 Cg EP +%%Page: 4 4 +%%BeginPageSetup +BP +%%EndPageSetup +/F0 10/Times-Roman@0 SF 174.415(CPM\(5\) File)72 48 R 174.415 +(formats CPM\(5\))2.5 F 2.5(8p)144 84 S(assw)-2.5 E(ord bytes)-.1 E/F1 +10.95/Times-Bold@0 SF(SEE ALSO)72 112.8 Q/F2 10/Times-Italic@0 SF +(mkfs.cpm)108 124.8 Q F0(\(1\),).32 E F2(fsc)2.5 E(k.cpm)-.2 E F0 +(\(1\),).32 E F2(fsed.cpm)2.5 E F0(\(1\),).32 E F2(cpmls)2.5 E F0(\(1\)) +.27 E(CP/M tools)72 768 Q(February 18, 2012)151.35 E(4)192.2 E 0 Cg EP +%%Trailer +end +%%EOF diff --git a/Tools/unix/cpmtools/cpmchattr.1 b/Tools/unix/cpmtools/cpmchattr.1 new file mode 100644 index 00000000..f130d5c2 --- /dev/null +++ b/Tools/unix/cpmtools/cpmchattr.1 @@ -0,0 +1,92 @@ +.TH CPMCHATTR 1 "October 25, 2014" "CP/M tools" "User commands" +.SH NAME \"{{{roff}}}\"{{{ +cpmchattr \- change file attributes on CP/M files +.\"}}} +.SH SYNOPSIS \"{{{ +.ad l +.B cpmchattr +.RB [ \-f +.IR format ] +.I image +.I attrib +.I file-pattern +\&... +.ad b +.\"}}} +.SH DESCRIPTION \"{{{ +\fBCpmchattr\fP changes the file attributes for files on CP/M disks. +.\"}}} +.SH OPTIONS \"{{{ +.IP "\fB\-f\fP \fIformat\fP" +Use the given CP/M disk \fIformat\fP instead of the default format. +.IP "\fB\-T\fP \fIlibdsk-type\fP" +libdsk driver type, e.g. \fBtele\fP for Teledisk images or \fBraw\fP for raw images +(requires building cpmtools with support for libdsk). +.IP "\fIattrib\fP" +Set the file attributes as given. +.\"}}} +.SH "FILE ATTRIBUTES" \"{{{ +The file attribute string can contain the characters +1,2,3,4,r,s,a,n and m. +The meanings of these are: +.TP +.B 1-4 +The CP/M "user attributes" F1-F4. CP/M does not assign any +meaning to these attributes, though MP/M does. +.TP +.B r +The file is read-only. This is the same as using +.I cpmchmod(1) +to revoke write permissions. +.TP +.B s +The file is a system file. This attribute can also be set by +.I cpmchmod(1). +.TP +.B a +The file has been backed up. +.TP +.B n +Reset all attributes to zero. So the string "n1r" resets all attributes and +then sets F1 and Read-Only. +.TP +.B m +Attributes after an m are unset rather than set. The string "12m34" sets +atttributes F1 and F2, and unsets F3 and F4. +.\"}}} +.SH "RETURN VALUE" \"{{{ +Upon successful completion, exit code 0 is returned. +.\"}}} +.SH ERRORS \"{{{ +Any errors are indicated by exit code 1. +.\"}}} +.SH ENVIRONMENT \"{{{ +CPMTOOLSFMT Default format +.\"}}} +.SH FILES \"{{{ +${prefix}/share/diskdefs CP/M disk format definitions +.\"}}} +.SH AUTHORS \"{{{ +This program is copyright 1997\(en2012 Michael Haardt + and copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" \"{{{ +.IR cpmls (1), +.IR cpmchmod (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/cpmchattr.c b/Tools/unix/cpmtools/cpmchattr.c new file mode 100644 index 00000000..be39130b --- /dev/null +++ b/Tools/unix/cpmtools/cpmchattr.c @@ -0,0 +1,119 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include + +#include "getopt_.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +const char cmd[]="cpmchattr"; + +int main(int argc, char *argv[]) /*{{{*/ +{ + /* variables */ /*{{{*/ + const char *err; + const char *image; + const char *format; + const char *devopts=NULL; + int c,i,usage=0,exitcode=0; + struct cpmSuperBlock drive; + struct cpmInode root; + int gargc; + char **gargv; + const char *attrs; + /*}}}*/ + + /* parse options */ /*{{{*/ + if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT; + while ((c=getopt(argc,argv,"T:f:h?"))!=EOF) switch(c) + { + case 'T': devopts=optarg; break; + case 'f': format=optarg; break; + case 'h': + case '?': usage=1; break; + } + + if (optind>=(argc-2)) usage=1; + else + { + image=argv[optind++]; + attrs = argv[optind++]; + } + + if (usage) + { + fprintf(stderr,"Usage: %s [-f format] [-T dsktype] image [NMrsa1234] pattern ...\n",cmd); + exit(1); + } + /*}}}*/ + /* open image */ /*{{{*/ + if ((err=Device_open(&drive.dev, image, O_RDWR, devopts))) + { + fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err); + exit(1); + } + if (cpmReadSuper(&drive,&root,format)==-1) + { + fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo); + exit(1); + } + /*}}}*/ + cpmglob(optind,argc,argv,&root,&gargc,&gargv); + for (i=0; i and copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" \"{{{ +.IR cpmls (1), +.IR chmod (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/cpmchmod.c b/Tools/unix/cpmtools/cpmchmod.c new file mode 100644 index 00000000..ad146965 --- /dev/null +++ b/Tools/unix/cpmtools/cpmchmod.c @@ -0,0 +1,89 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include + +#include "getopt_.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +const char cmd[]="cpmchmod"; + +int main(int argc, char *argv[]) /*{{{*/ +{ + /* variables */ /*{{{*/ + const char *err; + const char *image; + const char *format; + const char *devopts=NULL; + int c,i,usage=0,exitcode=0; + struct cpmSuperBlock drive; + struct cpmInode root; + int gargc; + char **gargv; + unsigned int mode; + /*}}}*/ + + /* parse options */ /*{{{*/ + if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT; + while ((c=getopt(argc,argv,"T:f:h?"))!=EOF) switch(c) + { + case 'T': devopts=optarg; break; + case 'f': format=optarg; break; + case 'h': + case '?': usage=1; break; + } + + if (optind>=(argc-2)) usage=1; + else + { + image=argv[optind++]; + if (!sscanf(argv[optind++], "%o", &mode)) usage=1; + } + + if (usage) + { + fprintf(stderr,"Usage: %s [-f format] image mode pattern ...\n",cmd); + exit(1); + } + /*}}}*/ + /* open image */ /*{{{*/ + if ((err=Device_open(&drive.dev, image, O_RDWR, devopts))) + { + fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err); + exit(1); + } + if (cpmReadSuper(&drive,&root,format)==-1) + { + fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo); + exit(1); + } + /*}}}*/ + cpmglob(optind,argc,argv,&root,&gargc,&gargv); + for (i=0; i. The Windows port is copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" \"{{{ +.IR cpmls (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/cpmcp.c b/Tools/unix/cpmtools/cpmcp.c new file mode 100644 index 00000000..561c451f --- /dev/null +++ b/Tools/unix/cpmtools/cpmcp.c @@ -0,0 +1,299 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "getopt_.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +const char cmd[]="cpmcp"; +static int text=0; +static int preserve=0; + +/** + * Return the user number. + * @param s CP/M filename in 0[0]:aaaaaaaa.bbb format. + * @returns The user number or -1 for no match. + */ +static int userNumber(const char *s) /*{{{*/ +{ + if (isdigit(*s) && *(s+1)==':') return (*s-'0'); + if (isdigit(*s) && isdigit(*(s+1)) && *(s+2)==':') return (10*(*s-'0')+(*(s+1)-'0')); + return -1; +} +/*}}}*/ + +/** + * Copy one file from CP/M to UNIX. + * @param root The inode for the root directory. + * @param src The CP/M filename in 00aaaaaaaabbb format. + * @param dest The UNIX filename. + * @returns 0 for success, 1 for error. + */ +static int cpmToUnix(const struct cpmInode *root, const char *src, const char *dest) /*{{{*/ +{ + struct cpmInode ino; + int exitcode=0; + + if (cpmNamei(root,src,&ino)==-1) { fprintf(stderr,"%s: can not open `%s': %s\n",cmd,src,boo); exitcode=1; } + else + { + struct cpmFile file; + FILE *ufp; + + cpmOpen(&ino,&file,O_RDONLY); + if ((ufp=fopen(dest,text ? "w" : "wb"))==(FILE*)0) { fprintf(stderr,"%s: can not create %s: %s\n",cmd,dest,strerror(errno)); exitcode=1; } + else + { + int crpending=0; + int ohno=0; + int res; + char buf[4096]; + + while ((res=cpmRead(&file,buf,sizeof(buf)))>0) + { + int j; + + for (j=0; j=argc) usage(); + image=argv[optind++]; + + if (userNumber(argv[optind])>=0) /* cpm -> unix? */ /*{{{*/ + { + int i; + struct stat statbuf; + + for (i=optind; i<(argc-1); ++i) if (userNumber(argv[i])==-1) usage(); + todir=((argc-optind)>2); + if (stat(argv[argc-1],&statbuf)==-1) { if (todir) usage(); } + else if (S_ISDIR(statbuf.st_mode)) todir=1; else if (todir) usage(); + readcpm=1; + } + /*}}}*/ + else if (userNumber(argv[argc-1])>=0) /* unix -> cpm */ /*{{{*/ + { + int i; + + todir=0; + for (i=optind; i<(argc-1); ++i) if (userNumber(argv[i])>=0) usage(); + if ((argc-optind)>2 && *(strchr(argv[argc-1],':')+1)!='\0') usage(); + if (*(strchr(argv[argc-1],':')+1)=='\0') todir=1; + readcpm=0; + } + /*}}}*/ + else usage(); + /*}}}*/ + /* open image file */ /*{{{*/ + if ((err=Device_open(&super.dev,image,readcpm ? O_RDONLY : O_RDWR, devopts))) + { + fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err); + exit(1); + } + if (cpmReadSuper(&super,&root,format)==-1) + { + fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo); + exit(1); + } + /*}}}*/ + if (readcpm) /* copy from CP/M to UNIX */ /*{{{*/ + { + int i; + char *last=argv[argc-1]; + + cpmglob(optind,argc-1,argv,&root,&gargc,&gargv); + /* trying to copy multiple files to a file? */ + if (gargc>1 && !todir) usage(); + for (i=0; i=' ' && !((c)&~0x7f) && (c)!='<' && (c)!='>' && (c)!='.' && (c)!=',' && (c)!=';' && (c)!=':' && (c)!='=' && (c)!='?' && (c)!='*' && (c)!= '[' && (c)!=']') +#define EXTENT(low,high) (((low)&0x1f)|(((high)&0x3f)<<5)) +#define EXTENTL(extent) ((extent)&0x1f) +#define EXTENTH(extent) (((extent>>5))&0x3f) + +#endif diff --git a/Tools/unix/cpmtools/cpmfs.c b/Tools/unix/cpmtools/cpmfs.c new file mode 100644 index 00000000..11e7d2e6 --- /dev/null +++ b/Tools/unix/cpmtools/cpmfs.c @@ -0,0 +1,1935 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cpmdir.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ +/* #defines */ /*{{{*/ +#undef CPMFS_DEBUG + +/* Number of _used_ bits per int */ + +#define INTBITS ((int)(sizeof(int)*8)) + +/* Convert BCD datestamp digits to binary */ + +#define BCD2BIN(x) ((((x)>>4)&0xf)*10 + ((x)&0xf)) + +#define BIN2BCD(x) (((((x)/10)&0xf)<<4) + (((x)%10)&0xf)) + +/* There are four reserved directory entries: ., .., [passwd] and [label]. +The first two of them refer to the same inode. */ + +#define RESERVED_ENTRIES 4 + +/* CP/M does not support any kind of inodes, so they are simulated. +Inode 0-(maxdir-1) correlate to the lowest extent number (not the first +extent of the file in the directory) of a file. Inode maxdir is the +root directory, inode maxdir+1 is the optional passwd file and inode +maxdir+2 the optional disk label. */ + +#define RESERVED_INODES 3 + +#define PASSWD_RECLEN 24 +/*}}}*/ + +extern char **environ; +const char *boo; +static mode_t s_ifdir=1; +static mode_t s_ifreg=1; + +/* memcpy7 -- Copy string, leaving 8th bit alone */ /*{{{*/ +static void memcpy7(char *dest, const char *src, int count) +{ + while (count--) + { + *dest = ((*dest) & 0x80) | ((*src) & 0x7F); + ++dest; + ++src; + } +} +/*}}}*/ + +/* file name conversions */ +/* splitFilename -- split file name into name and extension */ /*{{{*/ +static int splitFilename(const char *fullname, int type, char *name, char *ext, int *user) +{ + int i,j; + + assert(fullname!=(const char*)0); + assert(name!=(char*)0); + assert(ext!=(char*)0); + assert(user!=(int*)0); + memset(name,' ',8); + memset(ext,' ',3); + if (!isdigit(fullname[0]) || !isdigit(fullname[1])) + { + boo="illegal CP/M filename"; + return -1; + } + *user=10*(fullname[0]-'0')+(fullname[1]-'0'); + fullname+=2; + if ((fullname[0]=='\0') || *user>=((type&CPMFS_HI_USER) ? 32 : 16)) + { + boo="illegal CP/M filename"; + return -1; + } + for (i=0; i<8 && fullname[i] && fullname[i]!='.'; ++i) if (!ISFILECHAR(i,fullname[i])) + { + boo="illegal CP/M filename"; + return -1; + } + else name[i]=toupper(fullname[i]); + if (fullname[i]=='.') + { + ++i; + for (j=0; j<3 && fullname[i]; ++i,++j) if (!ISFILECHAR(1,fullname[i])) + { + boo="illegal CP/M filename"; + return -1; + } + else ext[j]=toupper(fullname[i]); + if (i==1 && j==0) + { + boo="illegal CP/M filename"; + return -1; + } + } + return 0; +} +/*}}}*/ +/* isMatching -- do two file names match? */ /*{{{*/ +static int isMatching(int user1, const char *name1, const char *ext1, int user2, const char *name2, const char *ext2) +{ + int i; + + assert(name1!=(const char*)0); + assert(ext1!=(const char*)0); + assert(name2!=(const char*)0); + assert(ext2!=(const char*)0); + if (user1!=user2) return 0; + for (i=0; i<8; ++i) if ((name1[i]&0x7f)!=(name2[i]&0x7f)) return 0; + for (i=0; i<3; ++i) if ((ext1[i]&0x7f)!=(ext2[i]&0x7f)) return 0; + return 1; +} +/*}}}*/ + +/* time conversions */ +/* cpm2unix_time -- convert CP/M time to UTC */ /*{{{*/ +static time_t cpm2unix_time(int days, int hour, int min) +{ + /* CP/M stores timestamps in local time. We don't know which */ + /* timezone was used and if DST was in effect. Assuming it was */ + /* the current offset from UTC is most sensible, but not perfect. */ + + int year,days_per_year; + static int days_per_month[]={31,0,31,30,31,30,31,31,30,31,30,31}; + char **old_environ; + static char gmt0[]="TZ=GMT0"; + static char *gmt_env[]={ gmt0, (char*)0 }; + struct tm tms; + time_t lt,t; + + time(<); + t=lt; + tms=*localtime(<); + old_environ=environ; + environ=gmt_env; + lt=mktime(&tms); + lt-=t; + tms.tm_sec=0; + tms.tm_min=((min>>4)&0xf)*10+(min&0xf); + tms.tm_hour=((hour>>4)&0xf)*10+(hour&0xf); + tms.tm_mday=1; + tms.tm_mon=0; + tms.tm_year=78; + tms.tm_isdst=-1; + for (;;) + { + year=tms.tm_year+1900; + days_per_year=((year%4)==0 && ((year%100) || (year%400)==0)) ? 366 : 365; + if (days>days_per_year) + { + days-=days_per_year; + ++tms.tm_year; + } + else break; + } + for (;;) + { + days_per_month[1]=(days_per_year==366) ? 29 : 28; + if (days>days_per_month[tms.tm_mon]) + { + days-=days_per_month[tms.tm_mon]; + ++tms.tm_mon; + } + else break; + } + t=mktime(&tms)+(days-1)*24*3600; + environ=old_environ; + t-=lt; + return t; +} +/*}}}*/ +/* unix2cpm_time -- convert UTC to CP/M time */ /*{{{*/ +static void unix2cpm_time(time_t now, int *days, int *hour, int *min) +{ + struct tm *tms; + int i; + + tms=localtime(&now); + *min=((tms->tm_min/10)<<4)|(tms->tm_min%10); + *hour=((tms->tm_hour/10)<<4)|(tms->tm_hour%10); + for (i=1978,*days=0; i<1900+tms->tm_year; ++i) + { + *days+=365; + if (i%4==0 && (i%100!=0 || i%400==0)) ++*days; + } + *days += tms->tm_yday+1; +} +/*}}}*/ +/* ds2unix_time -- convert DS to Unix time */ /*{{{*/ +static time_t ds2unix_time(const struct dsEntry *entry) +{ + struct tm tms; + int yr; + + if (entry->minute==0 && + entry->hour==0 && + entry->day==0 && + entry->month==0 && + entry->year==0) return 0; + + tms.tm_isdst = -1; + tms.tm_sec = 0; + tms.tm_min = BCD2BIN( entry->minute ); + tms.tm_hour = BCD2BIN( entry->hour ); + tms.tm_mday = BCD2BIN( entry->day ); + tms.tm_mon = BCD2BIN( entry->month ) - 1; + + yr = BCD2BIN(entry->year); + if (yr<70) yr+=100; + tms.tm_year = yr; + + return mktime(&tms); +} +/*}}}*/ +/* unix2ds_time -- convert Unix to DS time */ /*{{{*/ +static void unix2ds_time(time_t now, struct dsEntry *entry) +{ + struct tm *tms; + int yr; + + if ( now==0 ) + { + entry->minute=entry->hour=entry->day=entry->month=entry->year = 0; + } + else + { + tms=localtime(&now); + entry->minute = BIN2BCD( tms->tm_min ); + entry->hour = BIN2BCD( tms->tm_hour ); + entry->day = BIN2BCD( tms->tm_mday ); + entry->month = BIN2BCD( tms->tm_mon + 1 ); + + yr = tms->tm_year; + if ( yr>100 ) yr -= 100; + entry->year = BIN2BCD( yr ); + } +} +/*}}}*/ + +/* allocation vector bitmap functions */ +/* alvInit -- init allocation vector */ /*{{{*/ +static void alvInit(const struct cpmSuperBlock *d) +{ + int i,j,offset,block; + + assert(d!=(const struct cpmSuperBlock*)0); + /* clean bitmap */ /*{{{*/ + memset(d->alv,0,d->alvSize*sizeof(int)); + /*}}}*/ + /* mark directory blocks as used */ /*{{{*/ + *d->alv=(1<<((d->maxdir*32+d->blksiz-1)/d->blksiz))-1; + /*}}}*/ + for (i=0; imaxdir; ++i) /* mark file blocks as used */ /*{{{*/ + { + if (d->dir[i].status>=0 && d->dir[i].status<=(d->type&CPMFS_HI_USER ? 31 : 15)) + { +#ifdef CPMFS_DEBUG + fprintf(stderr,"alvInit: allocate extent %d\n",i); +#endif + for (j=0; j<16; ++j) + { + block=(unsigned char)d->dir[i].pointers[j]; + if (d->size>=256) block+=(((unsigned char)d->dir[i].pointers[++j])<<8); + if (block && blocksize) + { +#ifdef CPMFS_DEBUG + fprintf(stderr,"alvInit: allocate block %d\n",block); +#endif + offset=block/INTBITS; + d->alv[offset]|=(1<alvSize; ++i) + { + for (j=0,bits=drive->alv[i]; j=drive->size) + { + boo="device full"; + return -1; + } + drive->alv[i] |= (1<>= 1; + } + } + boo="device full"; + return -1; +} +/*}}}*/ + +/* logical block I/O */ +/* readBlock -- read a (partial) block */ /*{{{*/ +static int readBlock(const struct cpmSuperBlock *d, int blockno, char *buffer, int start, int end) +{ + int sect, track, counter; + + assert(d); + assert(blockno>=0); + assert(buffer); + if (blockno>=d->size) + { + boo="Attempting to access block beyond end of disk"; + return -1; + } + if (end<0) end=d->blksiz/d->secLength-1; + sect=(blockno*(d->blksiz/d->secLength)+ d->sectrk*d->boottrk)%d->sectrk; + track=(blockno*(d->blksiz/d->secLength)+ d->sectrk*d->boottrk)/d->sectrk; + for (counter=0; counter<=end; ++counter) + { + const char *err; + + assert(d->skewtab[sect]>=0); + assert(d->skewtab[sect]sectrk); + if (counter>=start && (err=Device_readSector(&d->dev,track,d->skewtab[sect],buffer+(d->secLength*counter)))) + { + boo=err; + return -1; + } + ++sect; + if (sect>=d->sectrk) + { + sect = 0; + ++track; + } + } + return 0; +} +/*}}}*/ +/* writeBlock -- write a (partial) block */ /*{{{*/ +static int writeBlock(const struct cpmSuperBlock *d, int blockno, const char *buffer, int start, int end) +{ + int sect, track, counter; + + assert(blockno>=0); + assert(blocknosize); + assert(buffer!=(const char*)0); + if (end < 0) end=d->blksiz/d->secLength-1; + sect = (blockno*(d->blksiz/d->secLength))%d->sectrk; + track = (blockno*(d->blksiz/d->secLength))/d->sectrk+d->boottrk; + for (counter = 0; counter<=end; ++counter) + { + const char *err; + + if (counter>=start && (err=Device_writeSector(&d->dev,track,d->skewtab[sect],buffer+(d->secLength*counter)))) + { + boo=err; + return -1; + } + ++sect; + if (sect>=d->sectrk) + { + sect=0; + ++track; + } + } + return 0; +} +/*}}}*/ + +/* directory management */ +/* findFileExtent -- find first/next extent for a file */ /*{{{*/ +static int findFileExtent(const struct cpmSuperBlock *sb, int user, const char *name, const char *ext, int start, int extno) +{ + boo="file already exists"; + for (; startmaxdir; ++start) + { + if + ( + ((unsigned char)sb->dir[start].status)<=(sb->type&CPMFS_HI_USER ? 31 : 15) + && (extno==-1 || (EXTENT(sb->dir[start].extnol,sb->dir[start].extnoh)/sb->extents)==(extno/sb->extents)) + && isMatching(user,name,ext,sb->dir[start].status,sb->dir[start].name,sb->dir[start].ext) + ) return start; + } + boo="file not found"; + return -1; +} +/*}}}*/ +/* findFreeExtent -- find first free extent */ /*{{{*/ +static int findFreeExtent(const struct cpmSuperBlock *drive) +{ + int i; + + for (i=0; imaxdir; ++i) if (drive->dir[i].status==(char)0xe5) return (i); + boo="directory full"; + return -1; +} +/*}}}*/ +/* updateTimeStamps -- convert time stamps to CP/M format */ /*{{{*/ +static void updateTimeStamps(const struct cpmInode *ino, int extent) +{ + struct PhysDirectoryEntry *date; + int i; + int ca_min,ca_hour,ca_days,u_min,u_hour,u_days; + + if (!S_ISREG(ino->mode)) return; +#ifdef CPMFS_DEBUG + fprintf(stderr,"CPMFS: updating time stamps for inode %d (%d)\n",extent,extent&3); +#endif + unix2cpm_time(ino->sb->cnotatime ? ino->ctime : ino->atime,&ca_days,&ca_hour,&ca_min); + unix2cpm_time(ino->mtime,&u_days,&u_hour,&u_min); + if ((ino->sb->type&CPMFS_CPM3_DATES) && (date=ino->sb->dir+(extent|3))->status==0x21) + { + ino->sb->dirtyDirectory=1; + switch (extent&3) + { + case 0: /* first entry */ /*{{{*/ + { + date->name[0]=ca_days&0xff; date->name[1]=ca_days>>8; + date->name[2]=ca_hour; + date->name[3]=ca_min; + date->name[4]=u_days&0xff; date->name[5]=u_days>>8; + date->name[6]=u_hour; + date->name[7]=u_min; + break; + } + /*}}}*/ + case 1: /* second entry */ /*{{{*/ + { + date->ext[2]=ca_days&0xff; date->extnol=ca_days>>8; + date->lrc=ca_hour; + date->extnoh=ca_min; + date->blkcnt=u_days&0xff; date->pointers[0]=u_days>>8; + date->pointers[1]=u_hour; + date->pointers[2]=u_min; + break; + } + /*}}}*/ + case 2: /* third entry */ /*{{{*/ + { + date->pointers[5]=ca_days&0xff; date->pointers[6]=ca_days>>8; + date->pointers[7]=ca_hour; + date->pointers[8]=ca_min; + date->pointers[9]=u_days&0xff; date->pointers[10]=u_days>>8; + date->pointers[11]=u_hour; + date->pointers[12]=u_min; + break; + } + /*}}}*/ + } + } +} +/*}}}*/ +/* updateDsStamps -- set time in datestamper file */ /*{{{*/ +static void updateDsStamps(const struct cpmInode *ino, int extent) +{ + int yr; + struct tm *cpm_time; + struct dsDate *stamp; + + if (!S_ISREG(ino->mode)) return; + if ( !(ino->sb->type&CPMFS_DS_DATES) ) return; + +#ifdef CPMFS_DEBUG + fprintf(stderr,"CPMFS: updating ds stamps for inode %d (%d)\n",extent,extent&3); +#endif + + /* Get datestamp struct */ + stamp = ino->sb->ds+extent; + + unix2ds_time( ino->mtime, &stamp->modify ); + unix2ds_time( ino->ctime, &stamp->create ); + unix2ds_time( ino->atime, &stamp->access ); + + ino->sb->dirtyDs = 1; +} +/*}}}*/ +/* readTimeStamps -- read CP/M time stamp */ /*{{{*/ +static int readTimeStamps(struct cpmInode *i, int lowestExt) +{ + /* variables */ /*{{{*/ + struct PhysDirectoryEntry *date; + int u_days=0,u_hour=0,u_min=0; + int ca_days=0,ca_hour=0,ca_min=0; + int protectMode=0; + /*}}}*/ + + if ( (i->sb->type&CPMFS_CPM3_DATES) && (date=i->sb->dir+(lowestExt|3))->status==0x21 ) + { + switch (lowestExt&3) + { + case 0: /* first entry of the four */ /*{{{*/ + { + ca_days=((unsigned char)date->name[0])+(((unsigned char)date->name[1])<<8); + ca_hour=(unsigned char)date->name[2]; + ca_min=(unsigned char)date->name[3]; + u_days=((unsigned char)date->name[4])+(((unsigned char)date->name[5])<<8); + u_hour=(unsigned char)date->name[6]; + u_min=(unsigned char)date->name[7]; + protectMode=(unsigned char)date->ext[0]; + break; + } + /*}}}*/ + case 1: /* second entry */ /*{{{*/ + { + ca_days=((unsigned char)date->ext[2])+(((unsigned char)date->extnol)<<8); + ca_hour=(unsigned char)date->lrc; + ca_min=(unsigned char)date->extnoh; + u_days=((unsigned char)date->blkcnt)+(((unsigned char)date->pointers[0])<<8); + u_hour=(unsigned char)date->pointers[1]; + u_min=(unsigned char)date->pointers[2]; + protectMode=(unsigned char)date->pointers[3]; + break; + } + /*}}}*/ + case 2: /* third one */ /*{{{*/ + { + ca_days=((unsigned char)date->pointers[5])+(((unsigned char)date->pointers[6])<<8); + ca_hour=(unsigned char)date->pointers[7]; + ca_min=(unsigned char)date->pointers[8]; + u_days=((unsigned char)date->pointers[9])+(((unsigned char)date->pointers[10])<<8); + u_hour=(unsigned char)date->pointers[11]; + u_min=(unsigned char)date->pointers[12]; + protectMode=(unsigned char)date->pointers[13]; + break; + } + /*}}}*/ + } + if (i->sb->cnotatime) + { + i->ctime=cpm2unix_time(ca_days,ca_hour,ca_min); + i->atime=0; + } + else + { + i->ctime=0; + i->atime=cpm2unix_time(ca_days,ca_hour,ca_min); + } + i->mtime=cpm2unix_time(u_days,u_hour,u_min); + } + else + { + i->atime=i->mtime=i->ctime=0; + protectMode=0; + } + + return protectMode; +} +/*}}}*/ +/* readDsStamps -- read datestamper time stamp */ /*{{{*/ +static void readDsStamps(struct cpmInode *i, int lowestExt) +{ + struct dsDate *stamp; + + if ( !(i->sb->type&CPMFS_DS_DATES) ) return; + + /* Get datestamp */ + stamp = i->sb->ds+lowestExt; + + i->mtime = ds2unix_time(&stamp->modify); + i->ctime = ds2unix_time(&stamp->create); + i->atime = ds2unix_time(&stamp->access); +} +/*}}}*/ + +/* match -- match filename against a pattern */ /*{{{*/ +static int recmatch(const char *a, const char *pattern) +{ + int first=1; + + assert(a); + assert(pattern); + while (*pattern) + { + switch (*pattern) + { + case '*': + { + if (*a=='.' && first) return 1; + ++pattern; + while (*a) if (recmatch(a,pattern)) return 1; else ++a; + break; + } + case '?': + { + if (*a) { ++a; ++pattern; } else return 0; + break; + } + default: if (tolower(*a)==tolower(*pattern)) { ++a; ++pattern; } else return 0; + } + first=0; + } + return (*pattern=='\0' && *a=='\0'); +} + +int match(const char *a, const char *pattern) +{ + int user; + char pat[255]; + + assert(a); + assert(pattern); + assert(strlen(pattern)<255); + if (isdigit(*pattern) && *(pattern+1)==':') { user=(*pattern-'0'); pattern+=2; } + else if (isdigit(*pattern) && isdigit(*(pattern+1)) && *(pattern+2)==':') { user=(10*(*pattern-'0')+(*(pattern+1)-'0')); pattern+=3; } + else user=-1; + if (user==-1) sprintf(pat,"??%s",pattern); + else sprintf(pat,"%02d%s",user,pattern); + return recmatch(a,pat); +} + +/*}}}*/ +/* cpmglob -- expand CP/M style wildcards */ /*{{{*/ +void cpmglob(int optin, int argc, char * const argv[], struct cpmInode *root, int *gargc, char ***gargv) +{ + struct cpmFile dir; + int entries,dirsize=0; + struct cpmDirent *dirent=(struct cpmDirent*)0; + int gargcap=0,i,j; + + *gargv=(char**)0; + *gargc=0; + cpmOpendir(root,&dir); + entries=0; + dirsize=8; + dirent=malloc(sizeof(struct cpmDirent)*dirsize); + while (cpmReaddir(&dir,&dirent[entries])) + { + ++entries; + if (entries==dirsize) dirent=realloc(dirent,sizeof(struct cpmDirent)*(dirsize*=2)); + } + for (i=optin; ilibdskGeometry[0] = '\0'; + d->type=0; + fp = open_diskdefs(); + + while (fgets(line,sizeof(line),fp)!=(char*)0) + { + int argc; + char *argv[2]; + char *s; + + /* Allow inline comments preceded by ; or # */ + s = strchr(line, '#'); + if (s) strcpy(s, "\n"); + s = strchr(line, ';'); + if (s) strcpy(s, "\n"); + + for (argc=0; argc<1 && (argv[argc]=strtok(argc ? (char*)0 : line," \t\n")); ++argc); + if ((argv[argc]=strtok((char*)0,"\n"))!=(char*)0) ++argc; + if (insideDef) + { + if (argc==1 && strcmp(argv[0],"end")==0) + { + insideDef=0; + d->size=(d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz; + if (d->extents==0) d->extents=((d->size>=256 ? 8 : 16)*d->blksiz)/16384; + if (d->extents==0) d->extents=1; + if (found) break; + } + else if (argc==2) + { + if (strcmp(argv[0],"seclen")==0) d->secLength=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"tracks")==0) d->tracks=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"sectrk")==0) d->sectrk=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"blocksize")==0) d->blksiz=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"maxdir")==0) d->maxdir=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"skew")==0) d->skew=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"skewtab")==0) + { + int pass,sectors; + + for (pass=0; pass<2; ++pass) + { + sectors=0; + for (s=argv[1]; *s; ) + { + int phys; + char *end; + + phys=strtol(s,&end,10); + if (pass==1) d->skewtab[sectors]=phys; + if (end==s) + { + fprintf(stderr,"%s: invalid skewtab `%s' at `%s'\n",cmd,argv[1],s); + exit(1); + } + s=end; + ++sectors; + if (*s==',') ++s; + } + if (pass==0) d->skewtab=malloc(sizeof(int)*sectors); + } + } + else if (strcmp(argv[0],"boottrk")==0) d->boottrk=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"offset")==0) + { + off_t val; + unsigned int multiplier; + char *endptr; + + errno=0; + multiplier=1; + val = strtol(argv[1],&endptr,10); + if ((errno==ERANGE && val==LONG_MAX)||(errno!=0 && val<=0)) + { + fprintf(stderr,"%s: invalid offset value \"%s\" - %s\n",cmd,argv[1],strerror(errno)); + exit(1); + } + if (endptr==argv[1]) + { + fprintf(stderr,"%s: offset value \"%s\" is not a number\n",cmd,argv[1]); + exit(1); + } + if (*endptr!='\0') + { + /* Have a unit specifier */ + switch (toupper(*endptr)) + { + case 'K': + multiplier=1024; + break; + case 'M': + multiplier=1024*1024; + break; + case 'T': + if (d->sectrk<0||d->tracks<0||d->secLength<0) + { + fprintf(stderr,"%s: offset must be specified after sectrk, tracks and secLength\n",cmd); + exit(1); + } + multiplier=d->sectrk*d->secLength; + break; + case 'S': + if (d->sectrk<0||d->tracks<0||d->secLength<0) + { + fprintf(stderr,"%s: offset must be specified after sectrk, tracks and secLength\n",cmd); + exit(1); + } + multiplier=d->secLength; + break; + default: + fprintf(stderr,"%s: unknown unit specifier \"%c\"\n",cmd,*endptr); + exit(1); + } + } + if (val*multiplier>INT_MAX) + { + fprintf(stderr,"%s: effective offset is out of range\n",cmd); + exit(1); + } + d->offset=val*multiplier; + } + else if (strcmp(argv[0],"logicalextents")==0) d->extents=strtol(argv[1],(char**)0,0); + else if (strcmp(argv[0],"os")==0) + { + if (strcmp(argv[1],"2.2" )==0) d->type|=CPMFS_DR22; + else if (strcmp(argv[1],"3" )==0) d->type|=CPMFS_DR3; + else if (strcmp(argv[1],"isx" )==0) d->type|=CPMFS_ISX; + else if (strcmp(argv[1],"p2dos")==0) d->type|=CPMFS_P2DOS; + else if (strcmp(argv[1],"zsys" )==0) d->type|=CPMFS_ZSYS; + else + { + fprintf(stderr, "%s: invalid OS type `%s'\n", cmd, argv[1]); + exit(1); + } + } + else if (strcmp(argv[0], "libdsk:format")==0) + { + strncpy(d->libdskGeometry, argv[1], sizeof(d->libdskGeometry) - 1); + d->libdskGeometry[sizeof(d->libdskGeometry) - 1] = 0; + } + } + else if (argc>0 && argv[0][0]!='#' && argv[0][0]!=';') + { + fprintf(stderr,"%s: invalid keyword `%s'\n",cmd,argv[0]); + exit(1); + } + } + else if (argc==2 && strcmp(argv[0],"diskdef")==0) + { + insideDef=1; + d->skew=1; + d->extents=0; + d->type=CPMFS_DR22; + d->skewtab=(int*)0; + d->offset=0; + d->boottrk=d->secLength=d->sectrk=d->tracks=-1; + d->libdskGeometry[0] = 0; + if (strcmp(argv[1],format)==0) found=1; + } + } + fclose(fp); + if (!found) + { + fprintf(stderr,"%s: unknown format %s\n",cmd,format); + exit(1); + } + if (d->boottrk<0) + { + fprintf(stderr, "%s: boottrk parameter invalid or missing from diskdef\n",cmd); + exit(1); + } + if (d->secLength<0) + { + fprintf(stderr, "%s: secLength parameter invalid or missing from diskdef\n",cmd); + exit(1); + } + if (d->sectrk<0) + { + fprintf(stderr, "%s: sectrk parameter invalid or missing from diskdef\n",cmd); + exit(1); + } + if (d->tracks<0) + { + fprintf(stderr, "%s: tracks parameter invalid or missing from diskdef\n",cmd); + exit(1); + } + return 0; +} +/*}}}*/ +/* amsReadSuper -- read super block from amstrad disk */ /*{{{*/ +static int amsReadSuper(struct cpmSuperBlock *d, const char *format) +{ + unsigned char boot_sector[512], *boot_spec; + const char *err; + + Device_setGeometry(&d->dev,512,9,40,0,"pcw180"); + if ((err=Device_readSector(&d->dev, 0, 0, (char *)boot_sector))) + { + fprintf(stderr,"%s: Failed to read Amstrad superblock (%s)\n",cmd,err); + exit(1); + } + boot_spec=(boot_sector[0] == 0 || boot_sector[0] == 3)?boot_sector:(unsigned char*)0; + /* Check for JCE's extension to allow Amstrad and MSDOS superblocks + * in the same sector (for the PCW16) + */ + if + ( + (boot_sector[0] == 0xE9 || boot_sector[0] == 0xEB) + && !memcmp(boot_sector + 0x2B, "CP/M", 4) + && !memcmp(boot_sector + 0x33, "DSK", 3) + && !memcmp(boot_sector + 0x7C, "CP/M", 4) + ) boot_spec = boot_sector + 128; + if (boot_spec==(unsigned char*)0) + { + fprintf(stderr,"%s: Amstrad superblock not present\n",cmd); + exit(1); + } + /* boot_spec[0] = format number: 0 for SS SD, 3 for DS DD + [1] = single/double sided and density flags + [2] = cylinders per side + [3] = sectors per cylinder + [4] = Physical sector shift, 2 => 512 + [5] = Reserved track count + [6] = Block shift + [7] = No. of directory blocks + */ + d->type = 0; + d->type |= CPMFS_DR3; /* Amstrads are CP/M 3 systems */ + d->secLength = 128 << boot_spec[4]; + d->tracks = boot_spec[2]; + if (boot_spec[1] & 3) d->tracks *= 2; + d->sectrk = boot_spec[3]; + d->blksiz = 128 << boot_spec[6]; + d->maxdir = (d->blksiz / 32) * boot_spec[7]; + d->skew = 1; /* Amstrads skew at the controller level */ + d->skewtab = (int*)0; + d->boottrk = boot_spec[5]; + d->offset = 0; + d->size = (d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz; + d->extents = ((d->size>=256 ? 8 : 16)*d->blksiz)/16384; + d->libdskGeometry[0] = 0; /* LibDsk can recognise an Amstrad superblock + * and autodect */ + + return 0; +} +/*}}}*/ +/* cpmCheckDs -- read all datestamper timestamps */ /*{{{*/ +int cpmCheckDs(struct cpmSuperBlock *sb) +{ + int dsoffset, dsblks, dsrecs, off, i; + unsigned char *buf; + + if (!isMatching(0,"!!!TIME&","DAT",sb->dir->status,sb->dir->name,sb->dir->ext)) return -1; + + /* Offset to ds file in alloc blocks */ + dsoffset=(sb->maxdir*32+(sb->blksiz-1))/sb->blksiz; + + dsrecs=(sb->maxdir+7)/8; + dsblks=(dsrecs*128+(sb->blksiz-1))/sb->blksiz; + + /* Allocate buffer */ + sb->ds=malloc(dsblks*sb->blksiz); + + /* Read ds file in its entirety */ + off=0; + for (i=dsoffset; ids)+off,0,-1)==-1) return -1; + off+=sb->blksiz; + } + + /* Verify checksums */ + buf = (unsigned char *)sb->ds; + for (i=0; ids); + sb->ds = (struct dsDate *)0; + return -1; + } + buf += 128; + } + return 0; +} +/*}}}*/ +/* cpmReadSuper -- get DPB and init in-core data for drive */ /*{{{*/ +int cpmReadSuper(struct cpmSuperBlock *d, struct cpmInode *root, const char *format) +{ + while (s_ifdir && !S_ISDIR(s_ifdir)) s_ifdir<<=1; + assert(s_ifdir); + while (s_ifreg && !S_ISREG(s_ifreg)) s_ifreg<<=1; + assert(s_ifreg); + if (strcmp(format,"amstrad")==0) amsReadSuper(d,format); + else diskdefReadSuper(d,format); + boo = Device_setGeometry(&d->dev,d->secLength,d->sectrk,d->tracks,d->offset,d->libdskGeometry); + if (boo) return -1; + + if (d->skewtab==(int*)0) /* generate skew table */ /*{{{*/ + { + int i,j,k; + + if (( d->skewtab = malloc(d->sectrk*sizeof(int))) == (int*)0) + { + boo=strerror(errno); + return -1; + } + memset(d->skewtab,0,d->sectrk*sizeof(int)); + for (i=j=0; isectrk; ++i,j=(j+d->skew)%d->sectrk) + { + while (1) + { + assert(isectrk); + assert(jsectrk); + for (k=0; kskewtab[k]!=j; ++k); + if (ksectrk; + else break; + } + d->skewtab[i]=j; + } + } + /*}}}*/ + /* initialise allocation vector bitmap */ /*{{{*/ + { + d->alvSize=((d->secLength*d->sectrk*(d->tracks-d->boottrk))/d->blksiz+INTBITS-1)/INTBITS; + if ((d->alv=malloc(d->alvSize*sizeof(int)))==(int*)0) + { + boo=strerror(errno); + return -1; + } + } + /*}}}*/ + /* allocate directory buffer */ /*{{{*/ + assert(sizeof(struct PhysDirectoryEntry)==32); + if ((d->dir=malloc(((d->maxdir*32+d->blksiz-1)/d->blksiz)*d->blksiz))==(struct PhysDirectoryEntry*)0) + { + boo=strerror(errno); + return -1; + } + /*}}}*/ + if (d->dev.opened==0) /* create empty directory in core */ /*{{{*/ + { + memset(d->dir,0xe5,d->maxdir*32); + } + /*}}}*/ + else /* read directory in core */ /*{{{*/ + { + int i,blocks,entry; + + blocks=(d->maxdir*32+d->blksiz-1)/d->blksiz; + entry=0; + for (i=0; idir+entry),0,-1)==-1) return -1; + entry+=(d->blksiz/32); + } + } + /*}}}*/ + alvInit(d); + if (d->type&CPMFS_CPM3_OTHER) /* read additional superblock information */ /*{{{*/ + { + int i; + + /* passwords */ /*{{{*/ + { + int passwords=0; + + for (i=0; imaxdir; ++i) if (d->dir[i].status>=16 && d->dir[i].status<=31) ++passwords; +#ifdef CPMFS_DEBUG + fprintf(stderr,"getformat: found %d passwords\n",passwords); +#endif + if ((d->passwdLength=passwords*PASSWD_RECLEN)) + { + if ((d->passwd=malloc(d->passwdLength))==(char*)0) + { + boo="out of memory"; + return -1; + } + for (i=0,passwords=0; imaxdir; ++i) if (d->dir[i].status>=16 && d->dir[i].status<=31) + { + int j,pb; + char *p=d->passwd+(passwords++*PASSWD_RECLEN); + + p[0]='0'+(d->dir[i].status-16)/10; + p[1]='0'+(d->dir[i].status-16)%10; + for (j=0; j<8; ++j) p[2+j]=d->dir[i].name[j]&0x7f; + p[10]=(d->dir[i].ext[0]&0x7f)==' ' ? ' ' : '.'; + for (j=0; j<3; ++j) p[11+j]=d->dir[i].ext[j]&0x7f; + p[14]=' '; + pb=(unsigned char)d->dir[i].lrc; + for (j=0; j<8; ++j) p[15+j]=((unsigned char)d->dir[i].pointers[7-j])^pb; +#ifdef CPMFS_DEBUG + p[23]='\0'; + fprintf(stderr,"getformat: %s\n",p); +#endif + p[23]='\n'; + } + } + } + /*}}}*/ + /* disc label */ /*{{{*/ + for (i=0; imaxdir; ++i) if (d->dir[i].status==(char)0x20) + { + int j; + + d->cnotatime=d->dir[i].extnol&0x10; + if (d->dir[i].extnol&0x1) + { + d->labelLength=12; + if ((d->label=malloc(d->labelLength))==(char*)0) + { + boo="out of memory"; + return -1; + } + for (j=0; j<8; ++j) d->label[j]=d->dir[i].name[j]&0x7f; + for (j=0; j<3; ++j) d->label[8+j]=d->dir[i].ext[j]&0x7f; + d->label[11]='\n'; + } + else + { + d->labelLength=0; + } + break; + } + if (i==d->maxdir) + { + d->cnotatime=1; + d->labelLength=0; + } + /*}}}*/ + } + /*}}}*/ + else + { + d->passwdLength=0; + d->cnotatime=1; + d->labelLength=0; + } + d->root=root; + d->dirtyDirectory = 0; + root->ino=d->maxdir; + root->sb=d; + root->mode=(s_ifdir|0777); + root->size=0; + root->atime=root->mtime=root->ctime=0; + + d->dirtyDs=0; + if (cpmCheckDs(d)==0) d->type|=CPMFS_DS_DATES; + else d->ds=(struct dsDate*)0; + + return 0; +} +/*}}}*/ +/* syncDs -- write all datestamper timestamps */ /*{{{*/ +static int syncDs(const struct cpmSuperBlock *sb) +{ + if (sb->dirtyDs) + { + int dsoffset, dsblks, dsrecs, off, i; + unsigned char *buf; + + dsrecs=(sb->maxdir+7)/8; + + /* Re-calculate checksums */ + buf = (unsigned char *)sb->ds; + for ( i=0; imaxdir*32+(sb->blksiz-1))/sb->blksiz; + dsblks=(dsrecs*128+(sb->blksiz-1))/sb->blksiz; + + off=0; + for (i=dsoffset; ids))+off,0,-1)==-1) return -1; + off+=sb->blksiz; + } + } + return 0; +} +/*}}}*/ +/* cpmSync -- write directory back */ /*{{{*/ +int cpmSync(struct cpmSuperBlock *sb) +{ + if (sb->dirtyDirectory) + { + int i,blocks,entry; + + blocks=(sb->maxdir*32+sb->blksiz-1)/sb->blksiz; + entry=0; + for (i=0; idir+entry),0,-1)==-1) return -1; + entry+=(sb->blksiz/32); + } + sb->dirtyDirectory=0; + } + if (sb->type&CPMFS_DS_DATES) syncDs(sb); + return 0; +} +/*}}}*/ +/* cpmUmount -- free super block */ /*{{{*/ +void cpmUmount(struct cpmSuperBlock *sb) +{ + cpmSync(sb); + if (sb->type&CPMFS_DS_DATES) free(sb->ds); + free(sb->alv); + free(sb->skewtab); + free(sb->dir); + if (sb->passwdLength) free(sb->passwd); +} +/*}}}*/ + +/* cpmNamei -- map name to inode */ /*{{{*/ +int cpmNamei(const struct cpmInode *dir, const char *filename, struct cpmInode *i) +{ + /* variables */ /*{{{*/ + int user; + char name[8],extension[3]; + int highestExtno,highestExt=-1,lowestExtno,lowestExt=-1; + int protectMode=0; + /*}}}*/ + + if (!S_ISDIR(dir->mode)) + { + boo="No such file"; + return -1; + } + if (strcmp(filename,".")==0 || strcmp(filename,"..")==0) /* root directory */ /*{{{*/ + { + *i=*dir; + return 0; + } + /*}}}*/ + else if (strcmp(filename,"[passwd]")==0 && dir->sb->passwdLength) /* access passwords */ /*{{{*/ + { + i->attr=0; + i->ino=dir->sb->maxdir+1; + i->mode=s_ifreg|0444; + i->sb=dir->sb; + i->atime=i->mtime=i->ctime=0; + i->size=i->sb->passwdLength; + return 0; + } + /*}}}*/ + else if (strcmp(filename,"[label]")==0 && dir->sb->labelLength) /* access label */ /*{{{*/ + { + i->attr=0; + i->ino=dir->sb->maxdir+2; + i->mode=s_ifreg|0444; + i->sb=dir->sb; + i->atime=i->mtime=i->ctime=0; + i->size=i->sb->labelLength; + return 0; + } + /*}}}*/ + if (splitFilename(filename,dir->sb->type,name,extension,&user)==-1) return -1; + /* find highest and lowest extent */ /*{{{*/ + { + int extent; + + i->size=0; + extent=-1; + highestExtno=-1; + lowestExtno=2049; + while ((extent=findFileExtent(dir->sb,user,name,extension,extent+1,-1))!=-1) + { + int extno=EXTENT(dir->sb->dir[extent].extnol,dir->sb->dir[extent].extnoh); + + if (extno>highestExtno) + { + highestExtno=extno; + highestExt=extent; + } + if (extnosize=highestExtno*16384; + if (dir->sb->size<256) for (block=15; block>=0; --block) + { + if (dir->sb->dir[highestExt].pointers[block]) break; + } + else for (block=7; block>=0; --block) + { + if (dir->sb->dir[highestExt].pointers[2*block] || dir->sb->dir[highestExt].pointers[2*block+1]) break; + } + if (dir->sb->dir[highestExt].blkcnt) i->size+=((dir->sb->dir[highestExt].blkcnt&0xff)-1)*128; + if (dir->sb->type & CPMFS_ISX) + { + i->size += (128 - dir->sb->dir[highestExt].lrc); + } + else + { + i->size+=dir->sb->dir[highestExt].lrc ? (dir->sb->dir[highestExt].lrc&0xff) : 128; + } +#ifdef CPMFS_DEBUG + fprintf(stderr,"cpmNamei: size=%ld\n",(long)i->size); +#endif + } + /*}}}*/ + i->ino=lowestExt; + i->mode=s_ifreg; + i->sb=dir->sb; + + /* read timestamps */ /*{{{*/ + protectMode = readTimeStamps(i,lowestExt); + /*}}}*/ + + /* Determine the inode attributes */ + i->attr = 0; + if (dir->sb->dir[lowestExt].name[0]&0x80) i->attr |= CPM_ATTR_F1; + if (dir->sb->dir[lowestExt].name[1]&0x80) i->attr |= CPM_ATTR_F2; + if (dir->sb->dir[lowestExt].name[2]&0x80) i->attr |= CPM_ATTR_F3; + if (dir->sb->dir[lowestExt].name[3]&0x80) i->attr |= CPM_ATTR_F4; + if (dir->sb->dir[lowestExt].ext [0]&0x80) i->attr |= CPM_ATTR_RO; + if (dir->sb->dir[lowestExt].ext [1]&0x80) i->attr |= CPM_ATTR_SYS; + if (dir->sb->dir[lowestExt].ext [2]&0x80) i->attr |= CPM_ATTR_ARCV; + if (protectMode&0x20) i->attr |= CPM_ATTR_PWDEL; + if (protectMode&0x40) i->attr |= CPM_ATTR_PWWRITE; + if (protectMode&0x80) i->attr |= CPM_ATTR_PWREAD; + + if (dir->sb->dir[lowestExt].ext[1]&0x80) i->mode|=01000; + i->mode|=0444; + if (!(dir->sb->dir[lowestExt].ext[0]&0x80)) i->mode|=0222; + if (extension[0]=='C' && extension[1]=='O' && extension[2]=='M') i->mode|=0111; + + readDsStamps(i,lowestExt); + + return 0; +} +/*}}}*/ +/* cpmStatFS -- statfs */ /*{{{*/ +void cpmStatFS(const struct cpmInode *ino, struct cpmStatFS *buf) +{ + int i; + struct cpmSuperBlock *d; + + d=ino->sb; + buf->f_bsize=d->blksiz; + buf->f_blocks=d->size; + buf->f_bfree=0; + buf->f_bused=-(d->maxdir*32+d->blksiz-1)/d->blksiz; + for (i=0; ialvSize; ++i) + { + int temp,j; + + temp = *(d->alv+i); + for (j=0; jsize) + { + if (1&temp) + { +#ifdef CPMFS_DEBUG + fprintf(stderr,"cpmStatFS: block %d allocated\n",(i*INTBITS+j)); +#endif + ++buf->f_bused; + } + else ++buf->f_bfree; + } + temp >>= 1; + } + } + buf->f_bavail=buf->f_bfree; + buf->f_files=d->maxdir; + buf->f_ffree=0; + for (i=0; imaxdir; ++i) + { + if (d->dir[i].status==(char)0xe5) ++buf->f_ffree; + } + buf->f_namelen=11; +} +/*}}}*/ +/* cpmUnlink -- unlink */ /*{{{*/ +int cpmUnlink(const struct cpmInode *dir, const char *fname) +{ + int user; + char name[8],extension[3]; + int extent; + struct cpmSuperBlock *drive; + + if (!S_ISDIR(dir->mode)) + { + boo="No such file"; + return -1; + } + drive=dir->sb; + if (splitFilename(fname,dir->sb->type,name,extension,&user)==-1) return -1; + if ((extent=findFileExtent(drive,user,name,extension,0,-1))==-1) return -1; + drive->dirtyDirectory=1; + drive->dir[extent].status=(char)0xe5; + do + { + drive->dir[extent].status=(char)0xe5; + } while ((extent=findFileExtent(drive,user,name,extension,extent+1,-1))>=0); + alvInit(drive); + return 0; +} +/*}}}*/ +/* cpmRename -- rename */ /*{{{*/ +int cpmRename(const struct cpmInode *dir, const char *old, const char *new) +{ + struct cpmSuperBlock *drive; + int extent; + int olduser; + char oldname[8], oldext[3]; + int newuser; + char newname[8], newext[3]; + + if (!S_ISDIR(dir->mode)) + { + boo="No such file"; + return -1; + } + drive=dir->sb; + if (splitFilename(old,dir->sb->type, oldname, oldext,&olduser)==-1) return -1; + if (splitFilename(new,dir->sb->type, newname, newext,&newuser)==-1) return -1; + if ((extent=findFileExtent(drive,olduser,oldname,oldext,0,-1))==-1) return -1; + if (findFileExtent(drive,newuser,newname, newext,0,-1)!=-1) + { + boo="file already exists"; + return -1; + } + do + { + drive->dirtyDirectory=1; + drive->dir[extent].status=newuser; + memcpy7(drive->dir[extent].name, newname, 8); + memcpy7(drive->dir[extent].ext, newext, 3); + } while ((extent=findFileExtent(drive,olduser,oldname,oldext,extent+1,-1))!=-1); + return 0; +} +/*}}}*/ +/* cpmOpendir -- opendir */ /*{{{*/ +int cpmOpendir(struct cpmInode *dir, struct cpmFile *dirp) +{ + if (!S_ISDIR(dir->mode)) + { + boo="No such file"; + return -1; + } + dirp->ino=dir; + dirp->pos=0; + dirp->mode=O_RDONLY; + return 0; +} +/*}}}*/ +/* cpmReaddir -- readdir */ /*{{{*/ +int cpmReaddir(struct cpmFile *dir, struct cpmDirent *ent) +{ + /* variables */ /*{{{*/ + struct PhysDirectoryEntry *cur=(struct PhysDirectoryEntry*)0; + char buf[2+8+1+3+1]; /* 00foobarxy.zzy\0 */ + char *bufp; + int hasext; + /*}}}*/ + + if (!(S_ISDIR(dir->ino->mode))) /* error: not a directory */ /*{{{*/ + { + boo="not a directory"; + return -1; + } + /*}}}*/ + while (1) + { + int i; + + if (dir->pos==0) /* first entry is . */ /*{{{*/ + { + ent->ino=dir->ino->sb->maxdir; + ent->reclen=1; + strcpy(ent->name,"."); + ent->off=dir->pos; + ++dir->pos; + return 1; + } + /*}}}*/ + else if (dir->pos==1) /* next entry is .. */ /*{{{*/ + { + ent->ino=dir->ino->sb->maxdir; + ent->reclen=2; + strcpy(ent->name,".."); + ent->off=dir->pos; + ++dir->pos; + return 1; + } + /*}}}*/ + else if (dir->pos==2) + { + if (dir->ino->sb->passwdLength) /* next entry is [passwd] */ /*{{{*/ + { + ent->ino=dir->ino->sb->maxdir+1; + ent->reclen=8; + strcpy(ent->name,"[passwd]"); + ent->off=dir->pos; + ++dir->pos; + return 1; + } + /*}}}*/ + } + else if (dir->pos==3) + { + if (dir->ino->sb->labelLength) /* next entry is [label] */ /*{{{*/ + { + ent->ino=dir->ino->sb->maxdir+2; + ent->reclen=7; + strcpy(ent->name,"[label]"); + ent->off=dir->pos; + ++dir->pos; + return 1; + } + /*}}}*/ + } + else if (dir->pos>=RESERVED_ENTRIES && dir->pos<(int)dir->ino->sb->maxdir+RESERVED_ENTRIES) + { + int first=dir->pos-RESERVED_ENTRIES; + + if ((cur=dir->ino->sb->dir+(dir->pos-RESERVED_ENTRIES))->status>=0 && cur->status<=(dir->ino->sb->type&CPMFS_HI_USER ? 31 : 15)) + { + /* determine first extent for the current file */ /*{{{*/ + for (i=0; iino->sb->maxdir; ++i) if (i!=(dir->pos-RESERVED_ENTRIES)) + { + if (isMatching(cur->status,cur->name,cur->ext,dir->ino->sb->dir[i].status,dir->ino->sb->dir[i].name,dir->ino->sb->dir[i].ext) && EXTENT(cur->extnol,cur->extnoh)>EXTENT(dir->ino->sb->dir[i].extnol,dir->ino->sb->dir[i].extnoh)) first=i; + } + /*}}}*/ + if (first==(dir->pos-RESERVED_ENTRIES)) + { + ent->ino=dir->pos-RESERVED_INODES; + /* convert file name to UNIX style */ /*{{{*/ + buf[0]='0'+cur->status/10; + buf[1]='0'+cur->status%10; + for (bufp=buf+2,i=0; i<8 && (cur->name[i]&0x7f)!=' '; ++i) *bufp++=tolower(cur->name[i]&0x7f); + for (hasext=0,i=0; i<3 && (cur->ext[i]&0x7f)!=' '; ++i) + { + if (!hasext) { *bufp++='.'; hasext=1; } + *bufp++=tolower(cur->ext[i]&0x7f); + } + *bufp='\0'; + /*}}}*/ + assert(bufp<=buf+sizeof(buf)); + ent->reclen=bufp-buf; + strcpy(ent->name,buf); + ent->off=dir->pos; + ++dir->pos; + return 1; + } + } + } + else return 0; + ++dir->pos; + } +} +/*}}}*/ +/* cpmStat -- stat */ /*{{{*/ +void cpmStat(const struct cpmInode *ino, struct cpmStat *buf) +{ + buf->ino=ino->ino; + buf->mode=ino->mode; + buf->size=ino->size; + buf->atime=ino->atime; + buf->mtime=ino->mtime; + buf->ctime=ino->ctime; +} +/*}}}*/ +/* cpmOpen -- open */ /*{{{*/ +int cpmOpen(struct cpmInode *ino, struct cpmFile *file, mode_t mode) +{ + if (S_ISREG(ino->mode)) + { + if ((mode&O_WRONLY) && (ino->mode&0222)==0) + { + boo="permission denied"; + return -1; + } + file->pos=0; + file->ino=ino; + file->mode=mode; + return 0; + } + else + { + boo="not a regular file"; + return -1; + } +} +/*}}}*/ +/* cpmRead -- read */ /*{{{*/ +int cpmRead(struct cpmFile *file, char *buf, int count) +{ + int findext=1,findblock=1,extent=-1,block=-1,extentno=-1,got=0,nextblockpos=-1,nextextpos=-1; + int blocksize=file->ino->sb->blksiz; + int extcap; + + extcap=(file->ino->sb->size<256 ? 16 : 8)*blocksize; + if (extcap>16384) extcap=16384*file->ino->sb->extents; + if (file->ino->ino==(ino_t)file->ino->sb->maxdir+1) /* [passwd] */ /*{{{*/ + { + if ((file->pos+count)>file->ino->size) count=file->ino->size-file->pos; + if (count) memcpy(buf,file->ino->sb->passwd+file->pos,count); + file->pos+=count; +#ifdef CPMFS_DEBUG + fprintf(stderr,"cpmRead passwd: read %d bytes, now at position %ld\n",count,(long)file->pos); +#endif + return count; + } + /*}}}*/ + else if (file->ino->ino==(ino_t)file->ino->sb->maxdir+2) /* [label] */ /*{{{*/ + { + if ((file->pos+count)>file->ino->size) count=file->ino->size-file->pos; + if (count) memcpy(buf,file->ino->sb->label+file->pos,count); + file->pos+=count; +#ifdef CPMFS_DEBUG + fprintf(stderr,"cpmRead label: read %d bytes, now at position %ld\n",count,(long)file->pos); +#endif + return count; + } + /*}}}*/ + else while (count>0 && file->posino->size) + { + char buffer[16384]; + + if (findext) + { + extentno=file->pos/16384; + extent=findFileExtent(file->ino->sb,file->ino->sb->dir[file->ino->ino].status,file->ino->sb->dir[file->ino->ino].name,file->ino->sb->dir[file->ino->ino].ext,0,extentno); + nextextpos=(file->pos/extcap)*extcap+extcap; + findext=0; + findblock=1; + } + if (findblock) + { + if (extent!=-1) + { + int start,end,ptr; + + ptr=(file->pos%extcap)/blocksize; + if (file->ino->sb->size>=256) ptr*=2; + block=(unsigned char)file->ino->sb->dir[extent].pointers[ptr]; + if (file->ino->sb->size>=256) block+=((unsigned char)file->ino->sb->dir[extent].pointers[ptr+1])<<8; + if (block==0) + { + memset(buffer,0,blocksize); + } + else + { + start=(file->pos%blocksize)/file->ino->sb->secLength; + end=((file->pos%blocksize+count)>blocksize ? blocksize-1 : (file->pos%blocksize+count-1))/file->ino->sb->secLength; + if (readBlock(file->ino->sb,block,buffer,start,end)==-1) + { + if (got==0) got=-1; + break; + } + } + } + nextblockpos=(file->pos/blocksize)*blocksize+blocksize; + findblock=0; + } + if (file->pospos%blocksize]; + ++file->pos; + ++got; + --count; + } + else if (file->pos==nextextpos) findext=1; else findblock=1; + } +#ifdef CPMFS_DEBUG + fprintf(stderr,"cpmRead: read %d bytes, now at position %ld\n",got,(long)file->pos); +#endif + return got; +} +/*}}}*/ +/* cpmWrite -- write */ /*{{{*/ +int cpmWrite(struct cpmFile *file, const char *buf, int count) +{ + int findext=1,findblock=-1,extent=-1,extentno=-1,got=0,nextblockpos=-1,nextextpos=-1; + int blocksize=file->ino->sb->blksiz; + int extcap=(file->ino->sb->size<256 ? 16 : 8)*blocksize; + int block=-1,start=-1,end=-1,ptr=-1,last=-1; + char buffer[16384]; + + while (count>0) + { + if (findext) /*{{{*/ + { + extentno=file->pos/16384; + extent=findFileExtent(file->ino->sb,file->ino->sb->dir[file->ino->ino].status,file->ino->sb->dir[file->ino->ino].name,file->ino->sb->dir[file->ino->ino].ext,0,extentno); + nextextpos=(file->pos/extcap)*extcap+extcap; + if (extent==-1) + { + if ((extent=findFreeExtent(file->ino->sb))==-1) return (got==0 ? -1 : got); + file->ino->sb->dir[extent]=file->ino->sb->dir[file->ino->ino]; + memset(file->ino->sb->dir[extent].pointers,0,16); + file->ino->sb->dir[extent].extnol=EXTENTL(extentno); + file->ino->sb->dir[extent].extnoh=EXTENTH(extentno); + file->ino->sb->dir[extent].blkcnt=0; + file->ino->sb->dir[extent].lrc=0; + time(&file->ino->ctime); + updateTimeStamps(file->ino,extent); + updateDsStamps(file->ino,extent); + } + findext=0; + findblock=1; + } + /*}}}*/ + if (findblock) /*{{{*/ + { + ptr=(file->pos%extcap)/blocksize; + if (file->ino->sb->size>=256) ptr*=2; + block=(unsigned char)file->ino->sb->dir[extent].pointers[ptr]; + if (file->ino->sb->size>=256) block+=((unsigned char)file->ino->sb->dir[extent].pointers[ptr+1])<<8; + if (block==0) /* allocate new block, set start/end to cover it */ /*{{{*/ + { + if ((block=allocBlock(file->ino->sb))==-1) return (got==0 ? -1 : got); + file->ino->sb->dir[extent].pointers[ptr]=block&0xff; + if (file->ino->sb->size>=256) file->ino->sb->dir[extent].pointers[ptr+1]=(block>>8)&0xff; + start=0; + end=(blocksize-1)/file->ino->sb->secLength; + memset(buffer,0,blocksize); + time(&file->ino->ctime); + updateTimeStamps(file->ino,extent); + updateDsStamps(file->ino,extent); + } + /*}}}*/ + else /* read existing block and set start/end to cover modified parts */ /*{{{*/ + { + start=(file->pos%blocksize)/file->ino->sb->secLength; + end=((file->pos%blocksize+count)>blocksize ? blocksize-1 : (file->pos%blocksize+count-1))/file->ino->sb->secLength; + if (file->pos%file->ino->sb->secLength) + { + if (readBlock(file->ino->sb,block,buffer,start,start)==-1) + { + if (got==0) got=-1; + break; + } + } + if (end!=start && (file->pos+count-1)ino->sb,block,buffer+end*file->ino->sb->secLength,end,end)==-1) + { + if (got==0) got=-1; + break; + } + } + } + /*}}}*/ + nextblockpos=(file->pos/blocksize)*blocksize+blocksize; + findblock=0; + } + /*}}}*/ + /* fill block and write it */ /*{{{*/ + file->ino->sb->dirtyDirectory=1; + while (file->pos!=nextblockpos && count) + { + buffer[file->pos%blocksize]=*buf++; + ++file->pos; + if (file->ino->sizepos) file->ino->size=file->pos; + ++got; + --count; + } + (void)writeBlock(file->ino->sb,block,buffer,start,end); + time(&file->ino->mtime); + if (file->ino->sb->size<256) for (last=15; last>=0; --last) + { + if (file->ino->sb->dir[extent].pointers[last]) + { + break; + } + } + else for (last=14; last>0; last-=2) + { + if (file->ino->sb->dir[extent].pointers[last] || file->ino->sb->dir[extent].pointers[last+1]) + { + last/=2; + break; + } + } + if (last>0) extentno+=(last*blocksize)/extcap; + file->ino->sb->dir[extent].extnol=EXTENTL(extentno); + file->ino->sb->dir[extent].extnoh=EXTENTH(extentno); + file->ino->sb->dir[extent].blkcnt=((file->pos-1)%16384)/128+1; + if (file->ino->sb->type & CPMFS_EXACT_SIZE) + { + file->ino->sb->dir[extent].lrc = (128 - (file->pos%128)) & 0x7F; + } + else + { + file->ino->sb->dir[extent].lrc=file->pos%128; + } + updateTimeStamps(file->ino,extent); + updateDsStamps(file->ino,extent); + /*}}}*/ + if (file->pos==nextextpos) findext=1; + else if (file->pos==nextblockpos) findblock=1; + } + return got; +} +/*}}}*/ +/* cpmClose -- close */ /*{{{*/ +int cpmClose(struct cpmFile *file) +{ + return 0; +} +/*}}}*/ +/* cpmCreat -- creat */ /*{{{*/ +int cpmCreat(struct cpmInode *dir, const char *fname, struct cpmInode *ino, mode_t mode) +{ + int user; + char name[8],extension[3]; + int extent; + struct cpmSuperBlock *drive; + struct PhysDirectoryEntry *ent; + + if (!S_ISDIR(dir->mode)) + { + boo="No such file or directory"; + return -1; + } + if (splitFilename(fname,dir->sb->type,name,extension,&user)==-1) return -1; +#ifdef CPMFS_DEBUG + fprintf(stderr,"cpmCreat: %s -> %d:%-.8s.%-.3s\n",fname,user,name,extension); +#endif + if (findFileExtent(dir->sb,user,name,extension,0,-1)!=-1) return -1; + drive=dir->sb; + if ((extent=findFreeExtent(dir->sb))==-1) return -1; + ent=dir->sb->dir+extent; + drive->dirtyDirectory=1; + memset(ent,0,32); + ent->status=user; + memcpy(ent->name,name,8); + memcpy(ent->ext,extension,3); + ino->ino=extent; + ino->mode=s_ifreg|mode; + ino->size=0; + + time(&ino->atime); + time(&ino->mtime); + time(&ino->ctime); + ino->sb=dir->sb; + updateTimeStamps(ino,extent); + updateDsStamps(ino,extent); + return 0; +} +/*}}}*/ + +/* cpmAttrGet -- get CP/M attributes */ /*{{{*/ +int cpmAttrGet(struct cpmInode *ino, cpm_attr_t *attrib) +{ + *attrib = ino->attr; + return 0; +} +/*}}}*/ +/* cpmAttrSet -- set CP/M attributes */ /*{{{*/ +int cpmAttrSet(struct cpmInode *ino, cpm_attr_t attrib) +{ + struct cpmSuperBlock *drive; + int extent; + int user; + char name[8], extension[3]; + + memset(name, 0, sizeof(name)); + memset(extension, 0, sizeof(extension)); + drive = ino->sb; + extent = ino->ino; + + drive->dirtyDirectory=1; + /* Strip off existing attribute bits */ + memcpy7(name, drive->dir[extent].name, 8); + memcpy7(extension, drive->dir[extent].ext, 3); + user = drive->dir[extent].status; + + /* And set new ones */ + if (attrib & CPM_ATTR_F1) name[0] |= 0x80; + if (attrib & CPM_ATTR_F2) name[1] |= 0x80; + if (attrib & CPM_ATTR_F3) name[2] |= 0x80; + if (attrib & CPM_ATTR_F4) name[3] |= 0x80; + if (attrib & CPM_ATTR_RO) extension[0] |= 0x80; + if (attrib & CPM_ATTR_SYS) extension[1] |= 0x80; + if (attrib & CPM_ATTR_ARCV) extension[2] |= 0x80; + + do + { + memcpy(drive->dir[extent].name, name, 8); + memcpy(drive->dir[extent].ext, extension, 3); + } while ((extent=findFileExtent(drive, user,name,extension,extent+1,-1))!=-1); + + /* Update the stored (inode) copies of the file attributes and mode */ + ino->attr=attrib; + if (attrib&CPM_ATTR_RO) ino->mode&=~(S_IWUSR|S_IWGRP|S_IWOTH); + else ino->mode|=(S_IWUSR|S_IWGRP|S_IWOTH); + + return 0; +} +/*}}}*/ +/* cpmChmod -- set CP/M r/o & sys */ /*{{{*/ +int cpmChmod(struct cpmInode *ino, mode_t mode) +{ + /* Convert the chmod() into a chattr() call that affects RO */ + int newatt = ino->attr & ~CPM_ATTR_RO; + + if (!(mode & (S_IWUSR|S_IWGRP|S_IWOTH))) newatt |= CPM_ATTR_RO; + return cpmAttrSet(ino, newatt); +} +/*}}}*/ +/* cpmUtime -- set timestamps */ /*{{{*/ +void cpmUtime(struct cpmInode *ino, struct utimbuf *times) +{ + ino->atime = times->actime; + ino->mtime = times->modtime; + time(&ino->ctime); + updateTimeStamps(ino,ino->ino); + updateDsStamps(ino,ino->ino); +} +/*}}}*/ diff --git a/Tools/unix/cpmtools/cpmfs.h b/Tools/unix/cpmtools/cpmfs.h new file mode 100644 index 00000000..06126c86 --- /dev/null +++ b/Tools/unix/cpmtools/cpmfs.h @@ -0,0 +1,206 @@ +#ifndef CPMFS_H +#define CPMFS_H + +#include +#include +#include + +#ifdef _WIN32 + #include + #include + /* To make it compile on NT: extracts from Linux 2.0 * + * and */ + #define __S_IFMT 0170000 /* These bits determine file type. */ + #define __S_IFDIR 0040000 /* Directory. */ + #define __S_IFREG 0100000 /* Regular file. */ + #define __S_IWUSR 0000200 /* Writable for user. */ + #define __S_IWGRP 0000200 /* Writable for group. */ + #define __S_IWOTH 0000200 /* Writable for others. */ + + #define __S_ISTYPE(mode, mask) (((mode) & __S_IFMT) == (mask)) + #define __S_ISTYPE(mode, mask) (((mode) & __S_IFMT) == (mask)) + /* These bits are defined in Borland C++ 5 but not in MS Visual C++ */ + #ifndef S_ISDIR + # define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR) + #endif + #ifndef S_ISREG + # define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG) + #endif + #ifndef S_IWUSR + #define S_IWUSR __S_IWUSR + #endif + #ifndef S_IWGRP + #define S_IWGRP __S_IWGRP + #endif + #ifndef S_IWOTH + #define S_IWOTH __S_IWOTH + #endif + + #include /* For open(), lseek() etc. */ + #ifndef HAVE_MODE_T + typedef int mode_t; + #endif +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +#include "device.h" + +/* CP/M file attributes */ +#define CPM_ATTR_F1 1 +#define CPM_ATTR_F2 2 +#define CPM_ATTR_F3 4 +#define CPM_ATTR_F4 8 +/* F5-F8 are banned in CP/M 2 & 3, F7 is used by ZSDOS */ +#define CPM_ATTR_RO 256 /* Read-only */ +#define CPM_ATTR_SYS 512 /* System */ +#define CPM_ATTR_ARCV 1024 /* Archive */ +#define CPM_ATTR_PWDEL 2048 /* Password required to delete */ +#define CPM_ATTR_PWWRITE 4096 /* Password required to write */ +#define CPM_ATTR_PWREAD 8192 /* Password required to read */ + +typedef int cpm_attr_t; + +struct cpmInode +{ + ino_t ino; + mode_t mode; + off_t size; + cpm_attr_t attr; + time_t atime; + time_t mtime; + time_t ctime; + struct cpmSuperBlock *sb; +}; + +struct cpmFile +{ + mode_t mode; + off_t pos; + struct cpmInode *ino; +}; + +struct cpmDirent +{ + ino_t ino; + off_t off; + size_t reclen; + char name[2+8+1+3+1]; /* 00foobarxy.zzy\0 */ +}; + +struct cpmStat +{ + ino_t ino; + mode_t mode; + off_t size; + time_t atime; + time_t mtime; + time_t ctime; +}; + +#define CPMFS_HI_USER (0x1<<0) /* has user numbers up to 31 */ +#define CPMFS_CPM3_DATES (0x1<<1) /* has CP/M+ style time stamps */ +#define CPMFS_CPM3_OTHER (0x1<<2) /* has passwords and disc label */ +#define CPMFS_DS_DATES (0x1<<3) /* has datestamper timestamps */ +#define CPMFS_EXACT_SIZE (0x1<<4) /* has reverse exact file size */ + +#define CPMFS_DR22 0 +#define CPMFS_P2DOS (CPMFS_CPM3_DATES|CPMFS_HI_USER) +#define CPMFS_DR3 (CPMFS_CPM3_DATES|CPMFS_CPM3_OTHER|CPMFS_HI_USER) +#define CPMFS_ISX (CPMFS_EXACT_SIZE) +#define CPMFS_ZSYS (CPMFS_HI_USER) + +struct dsEntry +{ + char year; + char month; + char day; + char hour; + char minute; +}; + +struct dsDate +{ + struct dsEntry create; + struct dsEntry access; + struct dsEntry modify; + char checksum; +}; + +struct cpmSuperBlock +{ + struct Device dev; + + int secLength; + int tracks; + int sectrk; + int blksiz; + int maxdir; + int skew; + int boottrk; + off_t offset; + int type; + int size; + int extents; /* logical extents per physical extent */ + struct PhysDirectoryEntry *dir; + int alvSize; + int *alv; + int *skewtab; + int cnotatime; + char *label; + size_t labelLength; + char *passwd; + size_t passwdLength; + struct cpmInode *root; + int dirtyDirectory; + struct dsDate *ds; + int dirtyDs; + char libdskGeometry[256]; +}; + +struct cpmStatFS +{ + long f_bsize; + long f_blocks; + long f_bfree; + long f_bused; + long f_bavail; + long f_files; + long f_ffree; + long f_namelen; +}; + +extern const char cmd[]; +extern const char *boo; + +int match(const char *a, const char *pattern); +void cpmglob(int opti, int argc, char * const argv[], struct cpmInode *root, int *gargc, char ***gargv); + +int cpmReadSuper(struct cpmSuperBlock *drive, struct cpmInode *root, const char *format); +int cpmNamei(const struct cpmInode *dir, const char *filename, struct cpmInode *i); +void cpmStatFS(const struct cpmInode *ino, struct cpmStatFS *buf); +int cpmUnlink(const struct cpmInode *dir, const char *fname); +int cpmRename(const struct cpmInode *dir, const char *old, const char *newname); +int cpmOpendir(struct cpmInode *dir, struct cpmFile *dirp); +int cpmReaddir(struct cpmFile *dir, struct cpmDirent *ent); +void cpmStat(const struct cpmInode *ino, struct cpmStat *buf); +int cpmAttrGet(struct cpmInode *ino, cpm_attr_t *attrib); +int cpmAttrSet(struct cpmInode *ino, cpm_attr_t attrib); +int cpmChmod(struct cpmInode *ino, mode_t mode); +int cpmOpen(struct cpmInode *ino, struct cpmFile *file, mode_t mode); +int cpmRead(struct cpmFile *file, char *buf, int count); +int cpmWrite(struct cpmFile *file, const char *buf, int count); +int cpmClose(struct cpmFile *file); +int cpmCreat(struct cpmInode *dir, const char *fname, struct cpmInode *ino, mode_t mode); +void cpmUtime(struct cpmInode *ino, struct utimbuf *times); +int cpmSync(struct cpmSuperBlock *sb); +void cpmUmount(struct cpmSuperBlock *sb); +int cpmCheckDs(struct cpmSuperBlock *sb); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/Tools/unix/cpmtools/cpmls.1 b/Tools/unix/cpmtools/cpmls.1 new file mode 100644 index 00000000..91326400 --- /dev/null +++ b/Tools/unix/cpmtools/cpmls.1 @@ -0,0 +1,75 @@ +.TH CPMLS 1 "October 25, 2014" "CP/M tools" "User commands" +.SH NAME \"{{{roff}}}\"{{{ +cpmls \- list sorted contents of directory +.\"}}} +.SH SYNOPSIS \"{{{ +.ad l +.B cpmls +.RB [ \-f +.IR format ] +.RB [ \-T +.IR libdsk-type ] +.RB [ \-d | \-D | \-F | \-A | \-l [ \-c ][ \-i ]] +.I image +.RI [ file-pattern "...]" +.ad b +.\"}}} +.SH DESCRIPTION \"{{{ +\fBCpmls\fP lists the sorted contents of the directory. +.\"}}} +.SH OPTIONS \"{{{ +.IP "\fB\-f\fP \fIformat\fP" +Use the given CP/M disk \fIformat\fP instead of the default format. +.IP "\fB\-T\fP \fIlibdsk-type\fP" +libdsk driver type, e.g. \fBtele\fP for Teledisk images or \fBraw\fP for raw images +(requires building cpmtools with support for libdsk). +.IP \fB\-d\fP +Old CP/M 2.2 dir output. +.IP \fB\-D\fP +P2DOS 2.3 ddir-like output. +.IP \fB\-F\fp +CP/M 3.x dir output. +.IP \fB\-A\fp +E2fs lsattr-like output. +.IP \fB\-l\fP +Long UNIX-style directory listing including size, time stamp and user number. +.IP \fB\-c\fP +Output the creation time, not the modification time. +.IP \fB\-i\fP +Print index number of each file. +.\"}}} +.SH "RETURN VALUE" \"{{{ +Upon successful completion, exit code 0 is returned. +.\"}}} +.SH ERRORS \"{{{ +Any errors are indicated by exit code 1. +.\"}}} +.SH ENVIRONMENT \"{{{ +CPMTOOLSFMT Default format +.\"}}} +.SH FILES \"{{{ +${prefix}/share/diskdefs CP/M disk format definitions +.\"}}} +.SH AUTHORS \"{{{ +This program is copyright 1997\(en2012 Michael Haardt +. The Windows port is copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" \"{{{ +.IR cpmcp (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/cpmls.c b/Tools/unix/cpmtools/cpmls.c new file mode 100644 index 00000000..3721d2e0 --- /dev/null +++ b/Tools/unix/cpmtools/cpmls.c @@ -0,0 +1,400 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "getopt_.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +/* variables */ /*{{{*/ +static const char * const month[12]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" }; +/*}}}*/ + +/* namecmp -- compare two entries */ /*{{{*/ +static int namecmp(const void *a, const void *b) +{ + if (**((const char * const *)a)=='[') return -1; + return strcmp(*((const char * const *)a),*((const char * const *)b)); +} +/*}}}*/ +/* olddir -- old style output */ /*{{{*/ +static void olddir(char **dirent, int entries) +{ + int i,j,k,l,user,announce; + + announce=0; + for (user=0; user<32; ++user) + { + for (i=l=0; itm_mday,month[tmp->tm_mon],tmp->tm_year+1900,tmp->tm_hour,tmp->tm_min); + } + else if (statbuf.ctime) printf(" "); + if (statbuf.ctime) + { + tmp=localtime(&statbuf.ctime); + printf(" %02d-%s-%04d %02d:%02d",tmp->tm_mday,month[tmp->tm_mon],tmp->tm_year+1900,tmp->tm_hour,tmp->tm_min); + } + putchar('\n'); + ++l; + } + } + if (announce==2) announce=1; + } + printf("%5.1d Files occupying %6.1ldK",l,(buf.f_bused*buf.f_bsize)/1024); + printf(", %7.1ldK Free.\n",(buf.f_bfree*buf.f_bsize)/1024); + } + else printf("No files found\n"); +} +/*}}}*/ +/* old3dir -- old CP/M Plus style long output */ /*{{{*/ +static void old3dir(char **dirent, int entries, struct cpmInode *ino) +{ + struct cpmStatFS buf; + struct cpmStat statbuf; + struct cpmInode file; + + if (entries) + { + int i,j,k,l,announce,user, attrib; + int totalBytes=0,totalRecs=0; + + qsort(dirent,entries,sizeof(char*),namecmp); + cpmStatFS(ino,&buf); + announce=1; + for (l=0,user=0; user<32; ++user) + { + for (i=0; itm_mon+1,tmp->tm_mday,tmp->tm_year%100,tmp->tm_hour,tmp->tm_min); + } + else if (statbuf.ctime) printf(" "); + if (statbuf.ctime) + { + tmp=localtime(&statbuf.ctime); + printf("%02d/%02d/%02d %02d:%02d",tmp->tm_mon+1,tmp->tm_mday,tmp->tm_year%100,tmp->tm_hour,tmp->tm_min); + } + putchar('\n'); + ++l; + } + } + if (announce==2) announce=1; + } + printf("\nTotal Bytes = %6.1dk ",(totalBytes+1023)/1024); + printf("Total Records = %7.1d ",totalRecs); + printf("Files Found = %4.1d\n",l); + printf("Total 1k Blocks = %6.1ld ",(buf.f_bused*buf.f_bsize)/1024); + printf("Used/Max Dir Entries For Drive A: %4.1ld/%4.1ld\n",buf.f_files-buf.f_ffree,buf.f_files); + } + else printf("No files found\n"); +} +/*}}}*/ +/* ls -- UNIX style output */ /*{{{*/ +static void ls(char **dirent, int entries, struct cpmInode *ino, int l, int c, int iflag) +{ + int i,user,announce,any; + time_t now; + struct cpmStat statbuf; + struct cpmInode file; + + time(&now); + qsort(dirent,entries,sizeof(char*),namecmp); + announce=0; + any=0; + for (user=0; user<32; ++user) + { + announce=0; + for (i=0; itm_mon],tmp->tm_mday); + if ((c ? statbuf.ctime : statbuf.mtime)<(now-182*24*3600)) printf("%04d ",tmp->tm_year+1900); + else printf("%02d:%02d ",tmp->tm_hour,tmp->tm_min); + } + printf("%s\n",dirent[i]+2); + } + } + } +} +/*}}}*/ +/* lsattr -- output something like e2fs lsattr */ /*{{{*/ +static void lsattr(char **dirent, int entries, struct cpmInode *ino) +{ + int i,user,announce,any; + struct cpmStat statbuf; + struct cpmInode file; + cpm_attr_t attrib; + + qsort(dirent,entries,sizeof(char*),namecmp); + announce=0; + any=0; + for (user=0; user<32; ++user) + { + announce=0; + for (i=0; i. The Windows port is copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" \"{{{ +.IR cpmls (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/cpmrm.c b/Tools/unix/cpmtools/cpmrm.c new file mode 100644 index 00000000..65430da6 --- /dev/null +++ b/Tools/unix/cpmtools/cpmrm.c @@ -0,0 +1,77 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include + +#include "getopt_.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +const char cmd[]="cpmrm"; + +int main(int argc, char *argv[]) /*{{{*/ +{ + /* variables */ /*{{{*/ + const char *err; + const char *image; + const char *format; + const char *devopts=NULL; + int c,i,usage=0,exitcode=0; + struct cpmSuperBlock drive; + struct cpmInode root; + int gargc; + char **gargv; + /*}}}*/ + + /* parse options */ /*{{{*/ + if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT; + while ((c=getopt(argc,argv,"T:f:h?"))!=EOF) switch(c) + { + case 'T': devopts=optarg; break; + case 'f': format=optarg; break; + case 'h': + case '?': usage=1; break; + } + + if (optind>=(argc-1)) usage=1; + else image=argv[optind++]; + + if (usage) + { + fprintf(stderr,"Usage: %s [-f format] [-T dsktype] image pattern ...\n",cmd); + exit(1); + } + /*}}}*/ + /* open image */ /*{{{*/ + if ((err=Device_open(&drive.dev, image, O_RDWR, devopts))) + { + fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err); + exit(1); + } + if (cpmReadSuper(&drive,&root,format)==-1) + { + fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo); + exit(1); + } + /*}}}*/ + cpmglob(optind,argc,argv,&root,&gargc,&gargv); + for (i=0; i +#include +#include +#include + +#include "device.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +static const char *lookupFormat(DSK_GEOMETRY *geom, const char *name) +{ + dsk_format_t fmt = FMT_180K; + const char *fname; + + while (dg_stdformat(NULL, fmt, &fname, NULL) == DSK_ERR_OK) + { + if (!strcmp(name, fname)) + { + dg_stdformat(geom, fmt, &fname, NULL); + return NULL; + } + ++fmt; + } + return "Unrecognised LibDsk geometry specification"; +} + +/* Device_open -- Open an image file */ /*{{{*/ +const char *Device_open(struct Device *this, const char *filename, int mode, const char *deviceOpts) +{ + char *format; + char driverName[80]; + const char *boo; + dsk_err_t e; + + /* Assume driver name & format name both fit in 80 characters, rather than + * malloccing the exact size */ + if (deviceOpts == NULL) + { + e = dsk_open(&this->dev, filename, NULL, NULL); + format = NULL; + } + else + { + strncpy(driverName, deviceOpts, 79); + driverName[79] = 0; + format = strchr(driverName, ','); + if (format) + { + *format = 0; + ++format; + } + e = dsk_open(&this->dev, filename, driverName, NULL); + } + this->opened = 0; + if (e) return dsk_strerror(e); + this->opened = 1; + if (format) + { + boo = lookupFormat(&this->geom, format); + if (boo) return boo; + } + else + { + dsk_getgeom(this->dev, &this->geom); + } + return NULL; +} +/*}}}*/ +/* Device_setGeometry -- Set disk geometry */ /*{{{*/ +const char *Device_setGeometry(struct Device *this, int secLength, int sectrk, int tracks, off_t offset, const char *libdskGeometry) +{ + char *boo; + + this->secLength=secLength; + this->sectrk=sectrk; + this->tracks=tracks; + /* Must be an even multiple of sector size */ + assert(offset%secLength==0); + this->offset=offset; + /* If a geometry is named in diskdefs, use it */ + if (libdskGeometry && libdskGeometry[0]) + { + return lookupFormat(&this->geom, libdskGeometry); + } + + this->geom.dg_secsize = secLength; + this->geom.dg_sectors = sectrk; + /* Did the autoprobe guess right about the number of sectors & cylinders? */ + if (this->geom.dg_cylinders * this->geom.dg_heads == tracks) return NULL; + /* Otherwise we guess: <= 43 tracks: single-sided. Else double. This + * fails for 80-track single-sided if there are any such beasts */ + if (tracks <= 43) + { + this->geom.dg_cylinders = tracks; + this->geom.dg_heads = 1; + } + else + { + this->geom.dg_cylinders = tracks/2; + this->geom.dg_heads = 2; + } + return NULL; +} +/*}}}*/ +/* Device_close -- Close an image file */ /*{{{*/ +const char *Device_close(struct Device *this) +{ + dsk_err_t e; + this->opened=0; + e = dsk_close(&this->dev); + return (e?dsk_strerror(e):(const char*)0); +} +/*}}}*/ +/* Device_readSector -- read a physical sector */ /*{{{*/ +const char *Device_readSector(const struct Device *this, int track, int sector, char *buf) +{ + dsk_err_t e; + e = dsk_lread(this->dev, &this->geom, buf, (track * this->sectrk) + sector + this->offset/this->secLength); + return (e?dsk_strerror(e):(const char*)0); +} +/*}}}*/ +/* Device_writeSector -- write physical sector */ /*{{{*/ +const char *Device_writeSector(const struct Device *this, int track, int sector, const char *buf) +{ + dsk_err_t e; + e = dsk_lwrite(this->dev, &this->geom, buf, (track * this->sectrk) + sector + this->offset/this->secLength); + return (e?dsk_strerror(e):(const char*)0); +} +/*}}}*/ diff --git a/Tools/unix/cpmtools/device_posix.c b/Tools/unix/cpmtools/device_posix.c new file mode 100644 index 00000000..5a28dcdc --- /dev/null +++ b/Tools/unix/cpmtools/device_posix.c @@ -0,0 +1,82 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include + +#include "device.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +/* Device_open -- Open an image file */ /*{{{*/ +const char *Device_open(struct Device *this, const char *filename, int mode, const char *deviceOpts) +{ + this->fd=open(filename,mode); + this->opened=(this->fd==-1?0:1); + return ((this->fd==-1)?strerror(errno):(const char*)0); +} +/*}}}*/ +/* Device_setGeometry -- Set disk geometry */ /*{{{*/ +const char *Device_setGeometry(struct Device *this, int secLength, int sectrk, int tracks, off_t offset, const char *libdskGeometry) +{ + this->secLength=secLength; + this->sectrk=sectrk; + this->tracks=tracks; + this->offset=offset; + return NULL; +} +/*}}}*/ +/* Device_close -- Close an image file */ /*{{{*/ +const char *Device_close(struct Device *this) +{ + this->opened=0; + return ((close(this->fd)==-1)?strerror(errno):(const char*)0); +} +/*}}}*/ +/* Device_readSector -- read a physical sector */ /*{{{*/ +const char *Device_readSector(const struct Device *this, int track, int sector, char *buf) +{ + int res; + + assert(this); + assert(sector>=0); + assert(sectorsectrk); + assert(track>=0); + assert(tracktracks); + assert(buf); + if (lseek(this->fd,(off_t)(((sector+track*this->sectrk)*this->secLength)+this->offset),SEEK_SET)==-1) + { + return strerror(errno); + } + if ((res=read(this->fd, buf, this->secLength)) != this->secLength) + { + if (res==-1) + { + return strerror(errno); + } + else memset(buf+res,0,this->secLength-res); /* hit end of disk image */ + } + return (const char*)0; +} +/*}}}*/ +/* Device_writeSector -- write physical sector */ /*{{{*/ +const char *Device_writeSector(const struct Device *this, int track, int sector, const char *buf) +{ + assert(sector>=0); + assert(sectorsectrk); + assert(track>=0); + assert(tracktracks); + if (lseek(this->fd,(off_t)(((sector+track*this->sectrk)*this->secLength)+this->offset),SEEK_SET)==-1) + { + return strerror(errno); + } + if (write(this->fd, buf, this->secLength) == this->secLength) return (const char*)0; + return strerror(errno); +} +/*}}}*/ diff --git a/Tools/unix/cpmtools/device_win32.c b/Tools/unix/cpmtools/device_win32.c new file mode 100644 index 00000000..18294432 --- /dev/null +++ b/Tools/unix/cpmtools/device_win32.c @@ -0,0 +1,670 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include + +#include "cpmdir.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ +/* types */ /*{{{*/ +#define PHYSICAL_SECTOR_1 1 /* First physical sector */ + +/* Use the INT13 interface rather than INT25/INT26. This appears to + * improve performance, but is less well tested. */ +#define USE_INT13 + +/* Windows 95 disk I/O functions - based on Stan Mitchell's DISKDUMP.C */ +#define VWIN32_DIOC_DOS_IOCTL 1 /* DOS ioctl calls 4400h-4411h */ +#define VWIN32_DIOC_DOS_INT25 2 /* absolute disk read, DOS int 25h */ +#define VWIN32_DIOC_DOS_INT26 3 /* absolute disk write, DOS int 26h */ +#define VWIN32_DIOC_DOS_INT13 4 /* BIOS INT13 functions */ + +typedef struct _DIOC_REGISTERS { + DWORD reg_EBX; + DWORD reg_EDX; + DWORD reg_ECX; + DWORD reg_EAX; + DWORD reg_EDI; + DWORD reg_ESI; + DWORD reg_Flags; + } + DIOC_REGISTERS, *PDIOC_REGISTERS; + +#define LEVEL0_LOCK 0 +#define LEVEL1_LOCK 1 +#define LEVEL2_LOCK 2 +#define LEVEL3_LOCK 3 +#define LEVEL1_LOCK_MAX_PERMISSION 0x0001 + +#define DRIVE_IS_REMOTE 0x1000 +#define DRIVE_IS_SUBST 0x8000 + +/********************************************************* + **** Note: all MS-DOS data structures must be packed **** + **** on a one-byte boundary. **** + *********************************************************/ +#pragma pack(1) + +typedef struct _DISKIO { + DWORD diStartSector; /* sector number to start at */ + WORD diSectors; /* number of sectors */ + DWORD diBuffer; /* address of buffer */ + } + DISKIO, *PDISKIO; + +typedef struct MID { + WORD midInfoLevel; /* information level, must be 0 */ + DWORD midSerialNum; /* serial number for the medium */ + char midVolLabel[11]; /* volume label for the medium */ + char midFileSysType[8]; /* type of file system as 8-byte ASCII */ + } + MID, *PMID; + +typedef struct driveparams { /* Disk geometry */ + BYTE special; + BYTE devicetype; + WORD deviceattrs; + WORD cylinders; + BYTE mediatype; + /* BPB starts here */ + WORD bytespersector; + BYTE sectorspercluster; + WORD reservedsectors; + BYTE numberofFATs; + WORD rootdirsize; + WORD totalsectors; + BYTE mediaid; + WORD sectorsperfat; + WORD sectorspertrack; + WORD heads; + DWORD hiddensectors; + DWORD bigtotalsectors; + BYTE reserved[6]; + /* BPB ends here */ + WORD sectorcount; + WORD sectortable[80]; + } DRIVEPARAMS, *PDRIVEPARAMS; +/*}}}*/ + +static char *strwin32error(void) /*{{{*/ +{ + static char buffer[1024]; + + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */ + (LPTSTR)buffer, + 1023, NULL); + return buffer; +} +/*}}}*/ +static BOOL LockVolume( HANDLE hDisk ) /*{{{*/ +{ + DWORD ReturnedByteCount; + + return DeviceIoControl( hDisk, FSCTL_LOCK_VOLUME, NULL, 0, NULL, + 0, &ReturnedByteCount, NULL ); +} +/*}}}*/ +static BOOL UnlockVolume( HANDLE hDisk ) /*{{{*/ +{ + DWORD ReturnedByteCount; + + return DeviceIoControl( hDisk, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, + 0, &ReturnedByteCount, NULL ); +} +/*}}}*/ +static BOOL DismountVolume( HANDLE hDisk ) /*{{{*/ +{ + DWORD ReturnedByteCount; + + return DeviceIoControl( hDisk, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, + 0, &ReturnedByteCount, NULL ); +} +/*}}}*/ +static int GetDriveParams( HANDLE hVWin32Device, int volume, DRIVEPARAMS* pParam ) /*{{{*/ + { + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + + reg.reg_EAX = 0x440d; /* IOCTL for block device */ + reg.reg_EBX = volume; /* one-based drive number */ + reg.reg_ECX = 0x0860; /* Get Device params */ + reg.reg_EDX = (DWORD)pParam; + reg.reg_Flags = 1; /* preset the carry flag */ + + bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); + + if ( !bResult || (reg.reg_Flags & 1) ) + return (reg.reg_EAX & 0xffff); + + return 0; + } +/*}}}*/ +static int SetDriveParams( HANDLE hVWin32Device, int volume, DRIVEPARAMS* pParam ) /*{{{*/ + { + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + + reg.reg_EAX = 0x440d; /* IOCTL for block device */ + reg.reg_EBX = volume; /* one-based drive number */ + reg.reg_ECX = 0x0840; /* Set Device params */ + reg.reg_EDX = (DWORD)pParam; + reg.reg_Flags = 1; /* preset the carry flag */ + + bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); + + if ( !bResult || (reg.reg_Flags & 1) ) + return (reg.reg_EAX & 0xffff); + + return 0; + } +/*}}}*/ +static int GetMediaID( HANDLE hVWin32Device, int volume, MID* pMid ) /*{{{*/ + { + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + + reg.reg_EAX = 0x440d; /* IOCTL for block device */ + reg.reg_EBX = volume; /* one-based drive number */ + reg.reg_ECX = 0x0866; /* Get Media ID */ + reg.reg_EDX = (DWORD)pMid; + reg.reg_Flags = 1; /* preset the carry flag */ + + bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); + + if ( !bResult || (reg.reg_Flags & 1) ) + return (reg.reg_EAX & 0xffff); + + return 0; + } +/*}}}*/ +static int VolumeCheck(HANDLE hVWin32Device, int volume, WORD* flags ) /*{{{*/ +{ + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + + reg.reg_EAX = 0x4409; /* Is Drive Remote */ + reg.reg_EBX = volume; /* one-based drive number */ + reg.reg_Flags = 1; /* preset the carry flag */ + + bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); + + if ( !bResult || (reg.reg_Flags & 1) ) + return (reg.reg_EAX & 0xffff); + + *flags = (WORD)(reg.reg_EDX & 0xffff); + return 0; +} +/*}}}*/ +static int LockLogicalVolume(HANDLE hVWin32Device, int volume, int lock_level, int permissions) /*{{{*/ +{ + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + + reg.reg_EAX = 0x440d; /* generic IOCTL */ + reg.reg_ECX = 0x084a; /* lock logical volume */ + reg.reg_EBX = volume | (lock_level << 8); + reg.reg_EDX = permissions; + reg.reg_Flags = 1; /* preset the carry flag */ + + bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); + + if ( !bResult || (reg.reg_Flags & 1) ) + return (reg.reg_EAX & 0xffff); + + return 0; +} +/*}}}*/ +static int UnlockLogicalVolume( HANDLE hVWin32Device, int volume ) /*{{{*/ +{ + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + + reg.reg_EAX = 0x440d; + reg.reg_ECX = 0x086a; /* lock logical volume */ + reg.reg_EBX = volume; + reg.reg_Flags = 1; /* preset the carry flag */ + + bResult = DeviceIoControl( hVWin32Device, VWIN32_DIOC_DOS_IOCTL, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); + + if ( !bResult || (reg.reg_Flags & 1) ) return -1; + return 0; +} +/*}}}*/ +static int w32mode(int mode) /*{{{*/ +{ + switch(mode) + { + case O_RDONLY: return GENERIC_READ; + case O_WRONLY: return GENERIC_WRITE; + } + return GENERIC_READ | GENERIC_WRITE; +} +/*}}}*/ + +/* Device_open -- Open an image file */ /*{{{*/ +const char *Device_open(struct Device *sb, const char *filename, int mode, const char *deviceOpts) +{ + /* Windows 95/NT: floppy drives using handles */ + if (strlen(filename) == 2 && filename[1] == ':') /* Drive name */ + { + char vname[20]; + DWORD dwVers; + + sb->fd = -1; + dwVers = GetVersion(); + + if (dwVers & 0x80000000L) /* Win32s (3.1) or Win32c (Win95) */ + { + int lock, driveno, res, permissions; + unsigned short drive_flags; + MID media; + + vname[0] = toupper(filename[0]); + driveno = vname[0] - 'A' + 1; /* 1=A: 2=B: */ + sb->drvtype = CPMDRV_WIN95; + sb->hdisk = CreateFile( "\\\\.\\vwin32", + 0, + 0, + NULL, + 0, + FILE_FLAG_DELETE_ON_CLOSE, + NULL ); + if (!sb->hdisk) + { + return "Failed to open VWIN32 driver."; + } + if (VolumeCheck(sb->hdisk, driveno, &drive_flags)) + { + CloseHandle(sb->hdisk); + return "Invalid drive"; + } + res = GetMediaID( sb->hdisk, driveno, &media ); + if ( res ) + { + const char *lboo = NULL; + + if ( res == ERROR_INVALID_FUNCTION && + (drive_flags & DRIVE_IS_REMOTE )) + lboo = "Network drive"; + else if (res == ERROR_ACCESS_DENIED) lboo = "Access denied"; + /* nb: It's perfectly legitimate for GetMediaID() to fail; most CP/M */ + /* CP/M disks won't have a media ID. */ + + if (lboo != NULL) + { + CloseHandle(sb->hdisk); + return lboo; + } + } + if (!res && + (!memcmp( media.midFileSysType, "CDROM", 5 ) || + !memcmp( media.midFileSysType, "CD001", 5 ) || + !memcmp( media.midFileSysType, "CDAUDIO", 5 ))) + { + CloseHandle(sb->hdisk); + return "CD-ROM drive"; + } + if (w32mode(mode) & GENERIC_WRITE) + { + lock = LEVEL0_LOCK; /* Exclusive access */ + permissions = 0; + } + else + { + lock = LEVEL1_LOCK; /* Allow other processes access */ + permissions = LEVEL1_LOCK_MAX_PERMISSION; + } + if (LockLogicalVolume( sb->hdisk, driveno, lock, permissions)) + { + CloseHandle(sb->hdisk); + return "Could not acquire a lock on the drive."; + } + + sb->fd = driveno; /* 1=A: 2=B: etc - we will need this later */ + + } + else + { + sprintf(vname, "\\\\.\\%s", filename); + sb->drvtype = CPMDRV_WINNT; + sb->hdisk = CreateFile(vname, /* Name */ + w32mode(mode), /* Access mode */ + FILE_SHARE_READ|FILE_SHARE_WRITE, /*Sharing*/ + NULL, /* Security attributes */ + OPEN_EXISTING, /* See MSDN */ + 0, /* Flags & attributes */ + NULL); /* Template file */ + + if (sb->hdisk != INVALID_HANDLE_VALUE) + { + sb->fd = 1; /* Arbitrary value >0 */ + if (LockVolume(sb->hdisk) == FALSE) /* Lock drive */ + { + char *lboo = strwin32error(); + CloseHandle(sb->hdisk); + sb->fd = -1; + return lboo; + } + } + else return strwin32error(); + } + sb->opened = 1; + return NULL; + } + + /* Not a floppy. Treat it as a normal file */ + + mode |= O_BINARY; + sb->fd = open(filename, mode); + if (sb->fd == -1) return strerror(errno); + sb->drvtype = CPMDRV_FILE; + sb->opened = 1; + return NULL; +} +/*}}}*/ +/* Device_setGeometry -- Set disk geometry */ /*{{{*/ +const char * Device_setGeometry(struct Device *this, int secLength, int sectrk, int tracks, off_t offset, const char *libdskGeometry) +{ + int n; + + this->secLength=secLength; + this->sectrk=sectrk; + this->tracks=tracks; + // Bill Buckels - add this->offset + this->offset=offset; + + + // Bill Buckels - not sure what to do here + if (this->drvtype == CPMDRV_WIN95) + { + DRIVEPARAMS drvp; + memset(&drvp, 0, sizeof(drvp)); + if (GetDriveParams( this->hdisk, this->fd, &drvp )) return "GetDriveParams failed"; + + drvp.bytespersector = secLength; + drvp.sectorspertrack = sectrk; + drvp.totalsectors = sectrk * tracks; + +/* Guess the cylinder/head configuration from the track count. This will + * get single-sided 80-track discs wrong, but it's that or double-sided + * 40-track (or add cylinder/head counts to diskdefs) + */ + if (tracks < 44) + { + drvp.cylinders = tracks; + drvp.heads = 1; + } + else + { + drvp.cylinders = tracks / 2; + drvp.heads = 2; + } + +/* Set up "reasonable" values for the other members */ + + drvp.sectorspercluster = 1024 / secLength; + drvp.reservedsectors = 1; + drvp.numberofFATs = 2; + drvp.sectorcount = sectrk; + drvp.rootdirsize = 64; + drvp.mediaid = 0xF0; + drvp.hiddensectors = 0; + drvp.sectorsperfat = 3; + for (n = 0; n < sectrk; n++) + { + drvp.sectortable[n*2] = n + PHYSICAL_SECTOR_1; /* Physical sector numbers */ + drvp.sectortable[n*2+1] = secLength; + } + drvp.special = 6; +/* We have not set: + + drvp.mediatype + drvp.devicetype + drvp.deviceattrs + + which should have been read correctly by GetDriveParams(). + */ + SetDriveParams( this->hdisk, this->fd, &drvp ); + } + return NULL; +} +/*}}}*/ +/* Device_close -- Close an image file */ /*{{{*/ +const char *Device_close(struct Device *sb) +{ + sb->opened = 0; + switch(sb->drvtype) + { + case CPMDRV_WIN95: + UnlockLogicalVolume(sb->hdisk, sb->fd ); + if (!CloseHandle( sb->hdisk )) return strwin32error(); + return NULL; + + case CPMDRV_WINNT: + DismountVolume(sb->hdisk); + UnlockVolume(sb->hdisk); + if (!CloseHandle(sb->hdisk)) return strwin32error(); + return NULL; + } + if (close(sb->fd)) return strerror(errno); + return NULL; +} +/*}}}*/ +/* Device_readSector -- read a physical sector */ /*{{{*/ +const char *Device_readSector(const struct Device *drive, int track, int sector, char *buf) +{ + int res; + off_t offset; + + assert(sector>=0); + assert(sectorsectrk); + assert(track>=0); + assert(tracktracks); + + offset = ((sector+track*drive->sectrk)*drive->secLength); + + if (drive->drvtype == CPMDRV_WINNT) + { + LPVOID iobuffer; + DWORD bytesread; + + // Bill Buckels - add drive->offset + if (SetFilePointer(drive->hdisk, offset+drive->offset, NULL, FILE_BEGIN) == INVALID_FILE_SIZE) + { + return strwin32error(); + } + iobuffer = VirtualAlloc(NULL, drive->secLength, MEM_COMMIT, PAGE_READWRITE); + if (!iobuffer) + { + return strwin32error(); + } + res = ReadFile(drive->hdisk, iobuffer, drive->secLength, &bytesread, NULL); + if (!res) + { + char *lboo = strwin32error(); + VirtualFree(iobuffer, drive->secLength, MEM_RELEASE); + return lboo; + } + + memcpy(buf, iobuffer, drive->secLength); + VirtualFree(iobuffer, drive->secLength, MEM_RELEASE); + + if (bytesread < (unsigned)drive->secLength) + { + memset(buf + bytesread, 0, drive->secLength - bytesread); + } + return NULL; + } + + // Bill Buckels - not sure what to do here + if (drive->drvtype == CPMDRV_WIN95) + { + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + +#ifdef USE_INT13 + int cyl, head; + + if (drive->tracks < 44) { cyl = track; head = 0; } + else { cyl = track/2; head = track & 1; } + + reg.reg_EAX = 0x0201; /* Read 1 sector */ + reg.reg_EBX = (DWORD)buf; + reg.reg_ECX = (cyl << 8) | (sector + PHYSICAL_SECTOR_1); + reg.reg_EDX = (head << 8) | (drive->fd - 1); + reg.reg_Flags = 1; /* preset the carry flag */ + bResult = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT13, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); +#else + DISKIO di; + + reg.reg_EAX = drive->fd - 1; /* zero-based volume number */ + reg.reg_EBX = (DWORD)&di; + reg.reg_ECX = 0xffff; /* use DISKIO structure */ + reg.reg_Flags = 1; /* preset the carry flag */ + di.diStartSector = sector+track*drive->sectrk; + di.diSectors = 1; + di.diBuffer = (DWORD)buf; + bResult = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT25, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); + +#endif + if ( !bResult || (reg.reg_Flags & 1) ) + { + if (GetLastError()) return strwin32error(); + return "Unknown read error."; + } + return 0; + } + + // Bill Buckels - add drive->offset + if (lseek(drive->fd,offset+drive->offset,SEEK_SET)==-1) + { + return strerror(errno); + } + if ((res=read(drive->fd, buf, drive->secLength)) != drive->secLength) + { + if (res==-1) + { + return strerror(errno); + } + else memset(buf+res,0,drive->secLength-res); /* hit end of disk image */ + } + return NULL; +} +/*}}}*/ +/* Device_writeSector -- write physical sector */ /*{{{*/ +const char *Device_writeSector(const struct Device *drive, int track, int sector, const char *buf) +{ + off_t offset; + int res; + + assert(sector>=0); + assert(sectorsectrk); + assert(track>=0); + assert(tracktracks); + + offset = ((sector+track*drive->sectrk)*drive->secLength); + + if (drive->drvtype == CPMDRV_WINNT) + { + LPVOID iobuffer; + DWORD byteswritten; + + // Bill Buckels - add drive->offset + if (SetFilePointer(drive->hdisk, offset+drive->offset, NULL, FILE_BEGIN) == INVALID_FILE_SIZE) + { + return strwin32error(); + } + iobuffer = VirtualAlloc(NULL, drive->secLength, MEM_COMMIT, PAGE_READWRITE); + if (!iobuffer) + { + return strwin32error(); + } + memcpy(iobuffer, buf, drive->secLength); + res = WriteFile(drive->hdisk, iobuffer, drive->secLength, &byteswritten, NULL); + if (!res || (byteswritten < (unsigned)drive->secLength)) + { + char *lboo = strwin32error(); + VirtualFree(iobuffer, drive->secLength, MEM_RELEASE); + return lboo; + } + + VirtualFree(iobuffer, drive->secLength, MEM_RELEASE); + return NULL; + } + + // Bill Buckels - not sure what to do here + if (drive->drvtype == CPMDRV_WIN95) + { + DIOC_REGISTERS reg; + BOOL bResult; + DWORD cb; + +#ifdef USE_INT13 + int cyl, head; + + if (drive->tracks < 44) { cyl = track; head = 0; } + else { cyl = track/2; head = track & 1; } + + reg.reg_EAX = 0x0301; /* Write 1 sector */ + reg.reg_EBX = (DWORD)buf; + reg.reg_ECX = (cyl << 8) | (sector + PHYSICAL_SECTOR_1); + reg.reg_EDX = (head << 8) | (drive->fd - 1); + reg.reg_Flags = 1; /* preset the carry flag */ + bResult = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT13, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); +#else + DISKIO di; + + reg.reg_EAX = drive->fd - 1; /* zero-based volume number */ + reg.reg_EBX = (DWORD)&di; + reg.reg_ECX = 0xffff; /* use DISKIO structure */ + reg.reg_Flags = 1; /* preset the carry flag */ + di.diStartSector = sector+track*drive->sectrk; + di.diSectors = 1; + di.diBuffer = (DWORD)buf; + bResult = DeviceIoControl( drive->hdisk, VWIN32_DIOC_DOS_INT26, + ®, sizeof( reg ), ®, sizeof( reg ), &cb, 0 ); +#endif + + if ( !bResult || (reg.reg_Flags & 1) ) + { + if (GetLastError()) return strwin32error(); + return "Unknown write error."; + } + return NULL; + } + + // Bill Buckels - add drive->offset + if (lseek(drive->fd,offset+drive->offset, SEEK_SET)==-1) + { + return strerror(errno); + } + if (write(drive->fd, buf, drive->secLength) == drive->secLength) return NULL; + return strerror(errno); +} +/*}}}*/ diff --git a/Tools/unix/cpmtools/diskdefs b/Tools/unix/cpmtools/diskdefs new file mode 100644 index 00000000..630317a0 --- /dev/null +++ b/Tools/unix/cpmtools/diskdefs @@ -0,0 +1,1396 @@ +diskdef ibm-3740 + seclen 128 + tracks 77 + sectrk 26 + blocksize 1024 + maxdir 64 + skew 6 + boottrk 2 + os 2.2 +end + +diskdef 4mb-hd + seclen 128 + tracks 1024 + sectrk 32 + blocksize 2048 + maxdir 256 + skew 1 + boottrk 0 + os p2dos +end + +diskdef pcw + seclen 512 + tracks 40 + sectrk 9 + blocksize 1024 + maxdir 64 + skew 1 + boottrk 1 + os 3 + libdsk:format pcw180 +end + +diskdef pc1.2m + seclen 512 + tracks 80 + # this format uses 15 sectors per track, but 30 per cylinder + sectrk 30 + blocksize 4096 + maxdir 256 + skew 1 + boottrk 0 + os 3 +end + +# CP/M 86 on 1.44MB floppies +diskdef cpm86-144feat + seclen 512 + tracks 160 + sectrk 18 + blocksize 4096 + maxdir 256 + skew 1 + boottrk 2 + os 3 + libdsk:format ibm1440 +end + +# CP/M 86 on 720KB floppies +diskdef cpm86-720 + seclen 512 + tracks 160 + sectrk 9 + blocksize 2048 + maxdir 256 + skew 1 + boottrk 2 + os 3 +end + +diskdef cf2dd + seclen 512 + tracks 160 + sectrk 9 + blocksize 2048 + maxdir 256 + skew 1 + boottrk 1 + os 3 + libdsk:format pcw720 +end + +#amstrad: values are read from super block (special name hardcoded) + +# Royal alphatronic +# setfdprm /dev/fd1 dd ssize=256 cyl=40 sect=16 head=2 +diskdef alpha + seclen 256 + tracks 40 + sectrk 32 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 2 + os 2.2 +end + +# Apple II CP/M skew o Apple II DOS 3.3 skew +diskdef apple-do + seclen 256 + tracks 35 + sectrk 16 + blocksize 1024 + maxdir 64 + skewtab 0,6,12,3,9,15,14,5,11,2,8,7,13,4,10,1 + boottrk 3 + os 2.2 +end + +# Apple II CP/M skew o Apple II PRODOS skew +diskdef apple-po + seclen 256 + tracks 35 + sectrk 16 + blocksize 1024 + maxdir 64 + skewtab 0,9,3,12,6,15,1,10,4,13,7,8,2,11,5,14 + boottrk 3 + os 2.2 +end + +# MYZ80 hard drive (only works with libdsk, because it has a 256-byte header) +diskdef myz80 + seclen 1024 + tracks 64 + sectrk 128 + blocksize 4096 + maxdir 1024 + skew 1 + boottrk 0 + os 3 + libdsk:format pcw720 +end + +# Despite being Amstrad formats, CPC System and CPC Data don't have an Amstrad +# superblock. You'll need to use libdsk to access them because the Linux +# and Windows kernel drivers won't touch them. +diskdef cpcsys + seclen 512 + tracks 40 + sectrk 9 + blocksize 1024 + maxdir 64 + skew 1 + boottrk 2 + os 3 + libdsk:format cpcsys +end +diskdef cpcdata + seclen 512 + tracks 40 + sectrk 9 + blocksize 1024 + maxdir 64 + skew 1 + boottrk 0 + os 3 + libdsk:format cpcdata +end + +# after being read in with no sector skew. +diskdef nigdos + seclen 512 + # NigDos double sided disk format, 42 tracks * 2 sides + tracks 84 + sectrk 10 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 0 + # this format wastes half of the directory entry + logicalextents 1 + os 3 +end + +diskdef epsqx10 + seclen 512 + tracks 40 + sectrk 20 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 +end + +diskdef ibm-8ss + seclen 512 + tracks 40 + sectrk 8 + blocksize 1024 + maxdir 64 + skew 0 + boottrk 1 + os 2.2 +end + +diskdef ibm-8ds + seclen 512 + tracks 40 + sectrk 8 + blocksize 1024 + maxdir 64 + skew 0 + boottrk 1 + os 2.2 +end + +diskdef electroglas + seclen 512 + tracks 80 + sectrk 10 + blocksize 2048 + maxdir 256 + skew 0 + boottrk 1 + os 3 +end + +# IBM CP/M-86 +# setfdprm /dev/fd1 sect=8 dtr=1 hd ssize=512 tpi=48 head=1 +diskdef ibmpc-514ss + seclen 512 + tracks 40 + sectrk 8 + blocksize 1024 + maxdir 64 + skew 1 + boottrk 1 + os 2.2 + libdsk:format ibm160 +end + +# IBM CP/M-86 +# setfdprm /dev/fd1 sect=8 dtr=1 hd ssize=512 tpi=48 +diskdef ibmpc-514ds + seclen 512 + tracks 80 + sectrk 8 + blocksize 2048 + maxdir 64 + skew 0 + boottrk 2 + os 2.2 + libdsk:format ibm320 +end + +diskdef p112 + seclen 512 + tracks 160 + sectrk 18 + blocksize 2048 + maxdir 256 + skew 1 + boottrk 2 + os 3 +end + +diskdef p112-old + seclen 512 + tracks 160 + sectrk 18 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 1 + os 3 +end + +diskdef gide-cfa + seclen 512 + tracks 1000 + sectrk 16 + blocksize 4096 + maxdir 1024 + skew 0 + boottrk 2 + os 3 +end + +diskdef gide-cfb + seclen 512 + tracks 1000 + sectrk 16 + blocksize 4096 + maxdir 1024 + skew 0 + boottrk 0 +# Start of second partition + offset 1000trk + os 3 +end + +# AT&T/Olivetti Word Processor +diskdef attwp + seclen 256 + tracks 80 + sectrk 32 + blocksize 2048 + maxdir 128 + boottrk 1 + logicalextents 1 + skewtab 0,2,4,6,8,10,12,14,1,3,5,7,9,11,13,15,16,18,20,22,24,26,28,30,17,19,21,23,25,27,29,31 + os 2.2 +end + +# setfdprm /dev/fd0 zerobased SS DD ssize=512 cyl=40 sect=10 head=1 +# Kaypro II +diskdef kpii + seclen 512 + tracks 40 + sectrk 10 + blocksize 1024 + maxdir 64 + skew 0 + boottrk 1 + os 2.2 +end + +# setfdprm /dev/fd0 zerobased DS DD ssize=512 cyl=40 sect=10 head=2 +# Kayro IV +diskdef kpiv + seclen 512 + tracks 80 + sectrk 10 + blocksize 2048 + maxdir 64 + skew 0 + boottrk 1 + os 2.2 +end + +# setfdprm /dev/fd0 dd sect=10 +diskdef interak + seclen 512 + tracks 80 + sectrk 20 + blocksize 4096 + maxdir 256 + skew 1 + boottrk 2 + os 2.2 +end + +# Timex FDD3000 3" +diskdef fdd3000 + seclen 256 + tracks 40 + sectrk 16 + blocksize 1024 + maxdir 128 + boottrk 4 + os 2.2 + skew 7 +end + +# Timex FDD3000 3" +diskdef fdd3000_2 + seclen 256 + tracks 40 + sectrk 16 + blocksize 1024 + maxdir 128 + boottrk 2 + os 2.2 + skew 5 +end + +# Robotron 1715 +diskdef 1715 + seclen 1024 + tracks 40 + sectrk 5 + blocksize 1024 + maxdir 64 + skew 0 + boottrk 3 + os 2.2 +end + +# Robotron 1715 with SCP3 +diskdef 17153 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 4 + os 3 +end + +#DDR +diskdef scp624 + seclen 256 + tracks 160 + sectrk 16 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 +end + +diskdef scp640 + seclen 256 + tracks 160 + sectrk 16 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 0 + os 2.2 +end + +diskdef scp780 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 +end + +diskdef scp800 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 0 + os 2.2 +end + +diskdef z9001 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 192 + skew 0 + boottrk 0 + os 2.2 +end + +# Visual Technology Visual 1050 computer +diskdef v1050 + seclen 512 + tracks 80 + sectrk 10 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 3 +end + +# Microbee 40 track 5.25" disks +diskdef microbee40 + seclen 512 + tracks 80 + sectrk 10 + blocksize 2048 + maxdir 128 + skewtab 1,4,7,0,3,6,9,2,5,8 + boottrk 2 + os 2.2 +end + +diskdef dreamdisk40 + seclen 512 + tracks 80 + sectrk 10 + blocksize 2048 + maxdir 128 + skewtab 1,4,7,0,3,6,9,2,5,8 + boottrk 2 + os 2.2 +end + +diskdef dreamdisk80 + seclen 512 + tracks 160 + sectrk 10 + blocksize 2048 + maxdir 256 + skewtab 1,4,7,0,3,6,9,2,5,8 + boottrk 2 + os 2.2 +end + +diskdef rc759 + seclen 1024 + tracks 154 + sectrk 8 + blocksize 2048 + maxdir 512 + boottrk 4 + os 3 +end + +# ICL Comet: 40 track 5.25" Single Sided +# +diskdef icl-comet-525ss + seclen 512 + tracks 40 + sectrk 10 + blocksize 1024 + maxdir 64 + skewtab 0,3,6,9,2,5,8,1,4,7 + boottrk 2 + os 2.2 +end + +diskdef z80pack-hd + seclen 128 + tracks 255 + sectrk 128 + blocksize 2048 + maxdir 1024 + skew 0 + boottrk 0 + os 2.2 +end + +diskdef z80pack-hdb + seclen 128 + tracks 256 + sectrk 16384 + blocksize 16384 + maxdir 8192 + skew 0 + boottrk 0 + os 2.2 +end + +# Bondwell 12 and 14 disk images in IMD raw binary format +diskdef bw12 + seclen 256 + tracks 40 + sectrk 18 + blocksize 2048 + maxdir 64 + skew 1 + boottrk 2 + os 2.2 +end + +diskdef bw14 + seclen 256 + tracks 80 + sectrk 18 + blocksize 2048 + maxdir 64 + skew 1 + boottrk 2 + os 2.2 +end + +############################ +# north star cp/m disks +############################ + +#North Star floppy 360K + +diskdef nsfd + seclen 512 + tracks 70 + sectrk 10 + blocksize 2048 + maxdir 64 + skew 5 + boottrk 2 + os 2.2 +end + + +#North Star CP/M Virtual-Disk file on Hard Disk +# prepared with allocation factor = 4 +# as in "CR CPMB 4000 4" +# needs to be copied off hard drive before you can +# work on it with cpmtools + +diskdef nshd4 + seclen 512 + tracks 512 + sectrk 16 + blocksize 4096 + maxdir 256 + skew 0 + boottrk 0 + os 2.2 +end + + +#North Star CP/M Virtual-Disk file on Hard Disk +# prepared with allocation factor = 8 +# as in "CR CPMB 6000 8" +# needs to be copied off hard drive before you can +# work on it with cpmtools + +diskdef nshd8 + seclen 512 + tracks 1024 + sectrk 16 + blocksize 8192 + maxdir 256 + skew 0 + boottrk 0 + os 2.2 +end + +# Northstar Micro-Disk System MDS-A-D 175 +diskdef mdsad175 + seclen 512 + blocksize 1024 + tracks 35 + maxdir 64 + boottrk 2 + sectrk 10 + skew 5 + os 2.2 +end + + +# Northstar Micro-Disk System MDS-A-D 350 +diskdef mdsad350 + seclen 512 + blocksize 2048 + tracks 70 + maxdir 64 + boottrk 2 + sectrk 10 + skew 5 + os 2.2 +end + + +# Osborne 1 +diskdef osborne1 + seclen 1024 + tracks 40 + sectrk 5 + blocksize 1024 + maxdir 64 + boottrk 3 + os 2.2 +end + +# Osborne Nuevo/Vixen/4 +diskdef osborne4 + seclen 1024 + tracks 80 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 2 + boottrk 2 + os 2.2 +end + +# Lobo Max-80 8" CP/M 2 +diskdef lobo2 + seclen 256 + tracks 77 + sectrk 30 + blocksize 2048 + maxdir 64 + skew 0 + boottrk 2 + os 2.2 +end + +#Lobo Max-80 8" CP/M 3 +diskdef lobo3 + seclen 512 + tracks 77 + sectrk 17 + blocksize 2048 + maxdir 64 + skew 0 + boottrk 2 + os 3 +end + +# PRO CP/M RZ50 DZ format (Perhaps only 79 tracks should be used?) +diskdef dec_pro + seclen 512 + tracks 80 + sectrk 10 + blocksize 2048 + maxdir 128 + skew 2 + boottrk 2 + os 2.2 +end + +# TDOS with DateStamper +diskdef tdos-ds + seclen 1024 + tracks 77 + sectrk 16 + blocksize 2048 + maxdir 256 + skew 0 + boottrk 1 + os zsys +end + +# The following entires are tested and working +# Most of the images are either from Don Maslin's archive or from +# Dave Dunfield's site, but not all - they are noted as well as +# their size. + +# PMC Micromate +# Dave Dunfield's Imagedisk information from DSK conversion from IMD: +# IMageDisk Utility 1.18 / Mar 07 2012 +# IMD 1.14: 10/03/2007 11:13:27 +# PMC-101 MicroMate +# CP/M Plus +# System Master +# Assuming 1:1 for Binary output +# 0/0 250 kbps DD 5x1024 +# 80 tracks(40/40), 400 sectors (12 Compressed) +# Entry derived from above - image size = 409,600, from Dave Dunfield +diskdef pmc101 + seclen 1024 + tracks 80 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 3 +end + +# BEGIN td143ssdd8 Turbo Dos 1.43 - SSDD 8" - 512 x 16 +# Test OK - image size = 630,784, from Don Maslin's archive +diskdef td143ssdd8 + seclen 512 + tracks 77 + sectrk 9 + blocksize 1024 + maxdir 64 + skew 0 + boottrk 0 + os 2.2 +# DENSITY MFM ,LOW +end + +# BEGIN headsdd8 Heath H89, Magnolia CP/M - SSDD 8" - 512 x 16 +# Test OK - image size = 630,784, from Don Maslin's archive +diskdef heassdd8 + seclen 512 + tracks 77 + sectrk 16 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 +# DENSITY MFM ,LOW +end + +# Morrow Designs Micro-Decision DOUBLE +# 64k CP/M Vers. 2.2 Rev.2.3 SIDED +# Copyright '76, '77, '78, '79, '80 +# Digital Research +# Copyright 1982,1983 Morrow Designs, Inc. +# Assuming 1:1 for Binary output +# 0/0 250 kbps DD 5x1024 +# 80 tracks(40/40), 400 sectors (128 Compressed) +# Entry derived from above data +# Test OK - image siae = 409600, from Dave Dunfield +diskdef mordsdd + seclen 1024 + tracks 80 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 3 + boottrk 2 + OS 2.2 +end + + +# BEGIN morsddd Morrow MD2 - SSDD 48 tpi 5.25" - 1024 x 5 +# Test OK - image size = 204,800, from Don Maslin's archive +# Also tested with image from Dave Dunfield +diskdef morsddd + seclen 1024 + tracks 40 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 3 + boottrk 2 + os 2.2 +# DENSITY MFM ,LOW +# BSH 4 BLM 15 EXM 1 DSM 94 DRM 127 AL0 0C0H AL1 0 OFS 2 +end + +# BEGIN osb1sssd Osborne 1 - SSSD 48 tpi 5.25" - 256 x 10 +# Test OK - image size = 102,400, from Don Maslin's archive +diskdef osb1sssd + seclen 256 + tracks 40 + sectrk 10 + blocksize 2048 + maxdir 64 + skew 2 + boottrk 3 + os 2.2 +# DENSITY MFM ,LOW +# BSH 4 BLM 15 EXM 1 DSM 45 DRM 63 AL0 080H AL1 0 OFS 3 +end + +# BEGIN ampdsdd Ampro - DSDD 48 tpi 5.25" - 512 x 10 +# Test OK - image size = 409,600, from Don Maslin's archive +diskdef ampdsdd + seclen 1024 + tracks 80 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 + libdsk:format ampro400d +# DENSITY MFM ,LOW +# BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2 +end + +# BEGIN ampdsdd80 Ampro - DSDD 96 tpi 5.25" - 512 x 10 +# Test OK - image size = 819,200, from Don Maslin's archive +diskdef ampdsdd80 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 + libdsk:format ampro800 +# DENSITY MFM ,LOW +# BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2 +end + +# BEGIN altdsdd Altos - DSDD 5" - 512 x 9 +# Test OK - both CP/M and MP/M - image size = 737,280, from Dave Dunfield +diskdef altdsdd + seclen 512 + tracks 160 + sectrk 9 + blocksize 4096 + maxdir 177 + skew 0 + boottrk 2 + os 3 +# DENSITY MFM ,HIGH +# BSH 5 BLM 31 EXM 3 DSM 176 DRM 176 AL0 0C0H AL1 0 OFS 2 +end + +# BEGIN trsomsssd TRS-80 Model 1, Omikron CP/M - SSSD 48 tpi 5.25" - 128 x 18 +# Test OK - image size = 80,640, from TRS-80 Yahoo Group posting +diskdef trsomsssd + seclen 128 + tracks 35 + sectrk 18 + blocksize 1024 + maxdir 64 + skew 4 + boottrk 3 + os 2.2 +# DENSITY FM ,LOW +# BSH 3 BLM 7 EXM 0 DSM 71 DRM 63 AL0 0C0H AL1 0 OFS 3 +end + +# Memotech type 03, ie: 3.5" or 5.25", D/S, D/D, S/T +# 40 tracks, 2 sides, 16 sectors/track, 256 bytes/sector +# Bytes on the media = 2*40*16*256 = 327680 +# CP/M sees 26 128 byte records per track (similar to 8" disks). +# Tracks = 327680/(26*128) = 98 +# Data is in 2048 byte blocks, on track 2 onwards +# Blocks = ((98-2)*26*128)/2048 = 156, which agrees with DPB + +diskdef memotech-type03 + seclen 128 + tracks 98 + sectrk 26 + blocksize 2048 + maxdir 64 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 07, ie: 3.5" or 5.25", D/S, D/D, D/T +# 80 tracks, 2 sides, 16 sectors/track, 256 bytes/sector +# Bytes on the media = 2*80*16*256 = 655360 +# CP/M sees 26 128 byte records per track (similar to 8" disks). +# Tracks = 655360/(26*128) = 196 +# Data is in 2048 byte blocks, on track 2 onwards +# Blocks = ((196-2)*26*128)/2048 = 315, which agrees with DPB + +diskdef memotech-type07 + seclen 128 + tracks 196 + sectrk 26 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 43, ie: 1MB Silicon Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for Silicon Discs includes blocks on the last incomplete track +# Tracks = 1048576/(26*128) = 315.07 +# Data is in 4096 byte blocks, on track 2 onwards +# Blocks = (1048576-2*26*128)/4096 = 254, which agrees with DPB +# Blocks = ((315-2)*26*128)/4096 = 254, so we don't need the 0.07 track +diskdef memotech-type43 + seclen 128 + tracks 315 + sectrk 26 + blocksize 4096 + maxdir 256 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 47, ie: 2MB Silicon Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for Silicon Discs includes blocks on the last incomplete track +# Tracks = 2097152/(26*128) = 630.15 +# Data is in 4096 byte blocks, on track 2 onwards +# Blocks = (2097152-2*26*128)/4096 = 510, which agrees with DPB +# Blocks = ((630-2)*26*128)/4096 = 510, so we don't need the 0.15 track +diskdef memotech-type47 + seclen 128 + tracks 630 + sectrk 26 + blocksize 4096 + maxdir 256 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 4B, ie: 4MB Silicon Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for Silicon Discs includes blocks on the last incomplete track +# Tracks = 4194304/(26*128) = 1260.3 +# Data is in 4096 byte blocks, on track 2 onwards +# Blocks = (4194304-2*26*128)/4096 = 1022, which agrees with DPB +# Blocks = ((1260-2)*26*128)/4096 = 1022, so we don't need the 0.3 track +diskdef memotech-type4B + seclen 128 + tracks 1260 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 4F, ie: 8MB Silicon Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for Silicon Discs includes blocks on the last incomplete track +# Tracks = 8388608/(26*128) = 2520.61 +# Data is in 4096 byte blocks, on track 2 onwards +# Blocks = (8388608-2*26*128)/4096 = 2046, which agrees with DPB +# Blocks = ((2520-2)*26*128)/4096 = 2045, so we need the extra 0.61 track +diskdef memotech-type4F + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 18, ie: 8MB SD Card +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for SD Cards includes blocks on the last incomplete track +# Tracks = 8388608/(26*128) = 2520.61 +# Data is in 4096 byte blocks, on track 2 onwards +# Blocks = (8388608-2*26*128)/4096 = 2046, which agrees with DPB +# Blocks = ((2520-2)*26*128)/4096 = 2045, so we need the extra 0.61 track +diskdef memotech-type18 + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 19, ie: 8MB SD Card +diskdef memotech-type19 + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 + offset 8M +end + +# Memotech type 1A, ie: 8MB SD Card +diskdef memotech-type1A + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 + offset 16M +end + +# Memotech type 1B, ie: 8MB SD Card +diskdef memotech-type1B + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 + offset 24M +end + +# Memotech type 1C, ie: 8MB SD Card +diskdef memotech-type1C + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 + offset 32M +end + +# Memotech type 1D, ie: 8MB SD Card +diskdef memotech-type1D + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 + offset 40M +end + +# Memotech type 1E, ie: 8MB SD Card +diskdef memotech-type1E + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 + offset 48M +end + +# Memotech type 1F, ie: 8MB SD Card +diskdef memotech-type1F + seclen 128 + tracks 2521 + sectrk 26 + blocksize 4096 + maxdir 512 + skew 1 + boottrk 2 + os 2.2 + offset 56M +end + +# Memotech type 50, ie: 256KB RAM Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for RAM Discs includes blocks on the last incomplete track +# Tracks = 262144/(26*128) = 78.76 +# Data is in 1024 byte blocks, on track 2 onwards +# Blocks = (262144-2*26*128)/1024 = 249, which agrees with DPB +# Blocks = ((78-2)*26*128)/1024 = 247, so we need the extra 0.76 track +diskdef memotech-type50 + seclen 128 + tracks 79 + sectrk 26 + blocksize 1024 + maxdir 64 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 51, ie: 512KB RAM Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for RAM Discs includes blocks on the last incomplete track +# Tracks = 524288/(26*128) = 157.53 +# Data is in 2048 byte blocks, on track 2 onwards +# Blocks = (524288-2*26*128)/2048 = 252, which agrees with DPB +# Blocks = ((157-2)*26*128)/2048 = 251, so we need the extra 0.53 track +diskdef memotech-type51 + seclen 128 + tracks 158 + sectrk 26 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 51, as used in Italy, ie: 480KB RAM Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for RAM Discs includes blocks on the last incomplete track +# Tracks = 491520/(26*128) = 147.69 +# Data is in 2048 byte blocks, on track 2 onwards +# Blocks = (491520-2*26*128)/2048 = 236, which agrees with DPB +# Blocks = ((147-2)*26*128)/2048 = 235, so we need the extra 0.69 track +diskdef memotech-type51-italy + seclen 128 + tracks 148 + sectrk 26 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 51, after S2R64.COM, ie: 448KB RAM Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for RAM Discs includes blocks on the last incomplete track +# Tracks = 458752/(26*128) = 137.84 +# Data is in 2048 byte blocks, on track 2 onwards +# Blocks = (458752-2*26*128)/2048 = 220, which agrees with DPB, after S2R64.COM +# Blocks = ((137-2)*26*128)/2048 = 219, so we need the extra 0.84 track +diskdef memotech-type51-s2r64 + seclen 128 + tracks 138 + sectrk 26 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 51, after S2R.COM, ie: 144KB RAM Disc +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for RAM Discs includes blocks on the last incomplete track +# Tracks = 147456/(26*128) = 44.3 +# Data is in 2048 byte blocks, on track 2 onwards +# Blocks = (147456-2*26*128)/2048 = 68, which agrees with DPB, after S2R.COM +# Blocks = ((44-2)*26*128)/2048 = 68, so we don't need the extra 0.3 track +diskdef memotech-type51-s2r + seclen 128 + tracks 44 + sectrk 26 + blocksize 2048 + maxdir 128 + skew 1 + boottrk 2 + os 2.2 +end + +# Memotech type 52, ie: 320KB RAM Disc +# Added for REMEMOTECH +# CP/M sees 26 128 byte records per track +# Note: Unlike common practice with real physical disks, with real geometry, +# the DPB for RAM Discs includes blocks on the last incomplete track +# Tracks = 327680/(26*128) = 98.46 +# Data is in 2048 byte blocks, on track 2 onwards +# Blocks = (327680-2*26*128)/2048 = 156 +# Blocks = ((98-2)*26*128)/2048 = 156, so we don't need the extra 0.46 track +# This type very deliberately and conveniently exactly matches type 03 +diskdef memotech-type52 + seclen 128 + tracks 98 + sectrk 26 + blocksize 2048 + maxdir 64 + skew 1 + boottrk 2 + os 2.2 +end + +# Research Machines 380Z/480Z 5.25" "Single Density" or "MDS" format. +# All tracks are formatted FM 16x128. +diskdef rm-sd + seclen 128 + tracks 40 + sectrk 16 + blocksize 1024 + maxdir 64 + skew 3 + boottrk 3 + os 2.2 +end + +# Research Machines 380Z/480Z 5.25" "Double Density" or "MD" format. +# Track 0 is formatted FM 16x128; 1+ are MFM 9x512. +# If you're working with an image file, make sure that track 0 is +# padded to be the same size as the other tracks. +diskdef rm-dd + seclen 512 + tracks 40 + sectrk 9 + blocksize 1024 + maxdir 64 + skew 5 + boottrk 3 + os 2.2 +end + +# Research Machines 380Z/480Z 5.25" "Quad Density" or "MQ" format. +# Track 0 is formatted FM 16x128; 1+ are MFM 9x512. +diskdef rm-qd + seclen 512 + tracks 80 + sectrk 9 + blocksize 2048 + maxdir 128 + skew 5 + boottrk 3 + os 2.2 +end + +# Ampro Little Board Z80 running CP/M 2.21 +# BEGIN AMP1 Ampro - SSDD 48 tpi 5.25" +# DENSITY MFM, LOW +# CYLINDERS 40 SIDES 1 SECTORS 10,512 SKEW 2 +# SIDE1 0 1,2,3,4,5,6,7,8,9,10 +# BSH 4 BLM 15 EXM 1 DSM 94 DRM 63 AL0 080H AL1 0 OFS 2 +# END + +diskdef amp1 + seclen 512 #= Sectors xx,512 + tracks 40 #= (Cylinders * Sides) = 40*1 = 40 + sectrk 10 #= Sectors 10,xxx + blocksize 2048 #= (128*(BLM+1)) = 2048 + maxdir 64 #(DRM+1) = 64 + skew 0 #= SKEW = 0 + boottrk 2 #= OFS = 2 + os 2.2 +end + +#BEGIN AMP2 Ampro - DSDD 48 tpi 5.25" +#DENSITY MFM, LOW +#CYLINDERS 40 SIDES 2 +#SECTORS 10,512 +#SKEW 2 +#SIDE1 0 17,18,19,20,21,22,23,24,25,26 +#SIDE2 1 17,18,19,20,21,22,23,24,25,26 +#ORDER SIDES +#BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2 +#END + +# setfdprm /dev/fd0 DS DD ssize=512 cyl=40 sect=10 head=2 +diskdef amp2 + seclen 512 + tracks 80 + sectrk 10 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 +end + +#BEGIN AMP3 Ampro - SSDD 96 tpi 3.5" +#DENSITY MFM, LOW +#CYLINDERS 80 SIDES 1 SECTORS 5,1024 SKEW 2 +#SIDE1 0 1,2,3,4,5 +#BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2 +#END + +# setfdprm /dev/fd0 SS DD ssize=1024 cyl=80 sect=5 head=1 +diskdef amp3 + seclen 1024 + tracks 80 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 +end + +#BEGIN AMP4 Ampro - DSDD 96 tpi 3.5" +#DENSITY MFM, LOW +#CYLINDERS 80 SIDES 2 SECTORS 5,1024 SKEW 2 +#SIDE1 0 17,18,19,20,21 +#SIDE2 1 17,18,19,20,21 +#ORDER SIDES +#BSH 4 BLM 15 EXM 0 DSM 394 DRM 255 AL0 0F0H AL1 0 OFS 2 +#END + +# setfdprm /dev/fd0 DS DD ssize=1024 cyl=80 sect=5 head=2 +diskdef amp4 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 256 + skew 0 + boottrk 2 + os 2.2 +end + +#BEGIN AMP5 Ampro - SSDD 3.5" +#DENSITY MFM, LOW +#CYLINDERS 80 SIDES 1 SECTORS 5,1024 SKEW 2 +#SIDE1 0 1,2,3,4,5 +#BSH 4 BLM 15 EXM 1 DSM 194 DRM 127 AL0 0C0H AL1 0 OFS 2 +#END + +# setfdprm /dev/fd0 SS DD ssize=1024 cyl=80 sect=5 head=1 +diskdef amp5 + seclen 1024 + tracks 80 + sectrk 5 + blocksize 2048 + maxdir 128 + skew 0 + boottrk 2 + os 2.2 +end + +#BEGIN AMP6 Ampro - DSDD 3.5" +#DENSITY MFM, LOW +#CYLINDERS 80 SIDES 2 SECTORS 5,1024 SKEW 2 +#SIDE1 0 17,18,19,20,21 +#SIDE2 1 17,18,19,20,21 +#ORDER SIDES +#BSH 4 BLM 15 EXM 0 DSM 394 DRM 255 AL0 0F0H AL1 0 OFS 2 +#END + +# setfdprm /dev/fd0 DS DD ssize=1024 cyl=80 sect=5 head=2 +diskdef amp6 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 256 + skew 0 + boottrk 2 + os 2.2 +end + +diskdef ampro800 + seclen 1024 + tracks 160 + sectrk 5 + blocksize 2048 + maxdir 256 + skew 0 + boottrk 2 + os 2.2 +end diff --git a/Tools/unix/cpmtools/fsck.cpm.1 b/Tools/unix/cpmtools/fsck.cpm.1 new file mode 100644 index 00000000..a167ef0e --- /dev/null +++ b/Tools/unix/cpmtools/fsck.cpm.1 @@ -0,0 +1,80 @@ +.TH FSCK.CPM 1 "October 25, 2014" "CP/M tools" "User commands" +.SH NAME ..\"{{{roff}}}\"{{{ +fsck.cpm \- check a CP/M file system +.\"}}} +.SH SYNOPSIS .\"{{{ +.ad l +.B fsck.cpm +.RB [ \-f +.IR format ] +.RB [ \-n ] +.I image +.ad b +.\"}}} +.SH DESCRIPTION .\"{{{ +\fBfsck.cpm\fP is used to check and repair a CP/M file system. After +reading the directory, it makes two passes. The first pass checks extent +fields for range and format violations (bad status, extent number, last +record byte count, file name, extension, block number, record count, +size of \&.COM files, time stamp format, invalid password characters, +invalid time stamp mode). The second pass checks extent connectivity +(multiple allocated blocks and duplicate directory entries). +.P +\fBfsck.cpm\fP can not yet repair all errors. +.\"}}} +.SH OPTIONS .\"{{{ +.IP "\fB\-f\fP \fIformat\fP" +Use the given CP/M disk \fIformat\fP instead of the default format. +.IP "\fB\-T\fP \fIlibdsk-type\fP" +libdsk driver type, e.g. \fBtele\fP for Teledisk images or \fBraw\fP for raw images +(requires building cpmtools with support for libdsk). +.IP "\fB\-n\fP" +Open the file system read-only and do not repair any errors. +.\"}}} +.SH "RETURN VALUE" .\"{{{ +Upon successful completion, exit code 0 is returned. +.\"}}} +.SH ERRORS .\"{{{ +Any errors are indicated by exit code 1. +.\"}}} +.SH FILES .\"{{{ +${prefix}/share/diskdefs CP/M disk format definitions +.\"}}} +.SH ENVIRONMENT \"{{{ +CPMTOOLSFMT Default format +.\"}}} +.SH DIAGNOSTICS .\"{{{ +.IP "\fIimage\fP: \fIused\fP/\fItotal\fP files (\fIn\fP.\fIn\fP% non-contiguos), \fIused\fP/\fItotal\fP blocks" +No inconsistencies could be found. The number of used files actually +is the number of used extents. Since a file may use more than +one extent, this may be greather than the actual number of files, but a +correct measure would not reflect how many files could still be created +at most. A file is considered fragmented, if sequential data blocks +pointed to by the same extent do not have sequential block numbers. +The number of used blocks includes the blocks used for system tracks +and the directory. +.\"}}} +.SH AUTHORS .\"{{{ +This program is copyright 1997\(en2012 Michael Haardt +. The Windows port is copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" .\"{{{ +.IR fsck (8), +.IR mkfs.cpm (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/fsck.cpm.c b/Tools/unix/cpmtools/fsck.cpm.c new file mode 100644 index 00000000..585015b0 --- /dev/null +++ b/Tools/unix/cpmtools/fsck.cpm.c @@ -0,0 +1,632 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "getopt_.h" +#include "cpmdir.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ +/* #defines */ /*{{{*/ +/* your favourite password *:-) */ + +#define T0 'G' +#define T1 'E' +#define T2 'H' +#define T3 'E' +#define T4 'I' +#define T5 'M' +#define T6 ' ' +#define T7 ' ' + +#define PB ((char)(T0+T1+T2+T3+T4+T5+T6+T7)) +#define P0 ((char)(T7^PB)) +#define P1 ((char)(T6^PB)) +#define P2 ((char)(T5^PB)) +#define P3 ((char)(T4^PB)) +#define P4 ((char)(T3^PB)) +#define P5 ((char)(T2^PB)) +#define P6 ((char)(T1^PB)) +#define P7 ((char)(T0^PB)) +/*}}}*/ + +/* types */ /*{{{*/ +enum Result { OK=0, MODIFIED=1, BROKEN=2 }; +/*}}}*/ +/* variables */ /*{{{*/ +static int norepair=0; +/*}}}*/ + +/* bcdCheck -- check format and range of BCD digit */ /*{{{*/ +static int bcdCheck(int n, int max, const char *msg, const char *unit, int extent1, int extent2) +{ + if (((n>>4)&0xf)>10 || (n&0xf)>10 || (((n>>4)&0xf)*10+(n&0xf))>=max) + { + printf("Error: Bad %s %s (extent=%d/%d, %s=%02x)\n",msg,unit,extent1,extent2,unit,n&0xff); + return -1; + } + else return 0; +} +/*}}}*/ +/* pwdCheck -- check password */ /*{{{*/ +static int pwdCheck(int extent, const char *pwd, char decode) +{ + char c; + int i; + + for (i=0; i<8; ++i) if ((c=((char)(pwd[7-i]^decode)))<' ' || c&0x80) + { + printf("Error: non-printable character in password (extent=%d, password=",extent); + for (i=0; i<8; ++i) + { + c=pwd[7-i]^decode; + if (c<' ' || c&0x80) + { + putchar('\\'); putchar('0'+((c>>6)&0x01)); + putchar('0'+((c>>3)&0x03)); + putchar('0'+(c&0x03)); + } + else putchar(c); + } + printf(")\n"); + return -1; + } + return 0; +} +/*}}}*/ +/* ask -- ask user and return answer */ /*{{{*/ +static int ask(const char *msg) +{ + while (1) + { + char buf[80]; + + if (norepair) return 0; + printf("%s [Y]? ",msg); fflush(stdout); + if (fgets(buf,sizeof(buf),stdin)==(char*)0) exit(1); + switch (toupper(buf[0])) + { + case '\n': + case 'Y': return 1; + case 'N': return 0; + } + } +} +/*}}}*/ +/* prfile -- print file name */ /*{{{*/ +static char *prfile(struct cpmSuperBlock *sb, int extent) +{ + struct PhysDirectoryEntry *dir; + static char name[80]; + char *s=name; + int i; + char c; + + dir=sb->dir+extent; + for (i=0; i<8; ++i) + { + c=dir->name[i]; + if ((c&0x7f)<' ') + { + *s++='\\'; *s++=('0'+((c>>6)&0x01)); + *s++=('0'+((c>>3)&0x03)); + *s++=('0'+(c&0x03)); + } + else *s++=(c&0x7f); + } + *s++='.'; + for (i=0; i<3; ++i) + { + c=dir->ext[i]; + if ((c&0x7f)<' ') + { + *s++='\\'; *s++=('0'+((c>>6)&0x01)); + *s++=('0'+((c>>3)&0x03)); + *s++=('0'+(c&0x03)); + } + else *s++=(c&0x7f); + } + *s='\0'; + return name; +} +/*}}}*/ +/* fsck -- file system check */ /*{{{*/ +static int fsck(struct cpmInode *root, const char *image) +{ + /* variables */ /*{{{*/ + enum Result ret=OK; + int extent,extent2; + struct PhysDirectoryEntry *dir,*dir2; + struct cpmSuperBlock *sb=root->sb; + /*}}}*/ + + /* Phase 1: check extent fields */ /*{{{*/ + printf("Phase 1: check extent fields\n"); + for (extent=0; extentmaxdir; ++extent) + { + char *status; + int usedBlocks=0; + + dir=sb->dir+extent; + status=&dir->status; + if (*status>=0 && *status<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* directory entry */ /*{{{*/ + { + /* check name and extension */ /*{{{*/ + { + int i; + char *c; + + for (i=0; i<8; ++i) + { + c=&(dir->name[i]); + if (!ISFILECHAR(i,*c&0x7f) || islower(*c&0x7f)) + { + printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i); + if (ask("Remove file")) + { + *status=(char)0xE5; + ret|=MODIFIED; + break; + } + else ret|=BROKEN; + } + } + if (*status==(char)0xe5) continue; + for (i=0; i<3; ++i) + { + c=&(dir->ext[i]); + if (!ISFILECHAR(1,*c&0x7f) || islower(*c&0x7f)) + { + printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i); + if (ask("Remove file")) + { + *status=(char)0xE5; + ret|=MODIFIED; + break; + } + else ret|=BROKEN; + } + } + if (*status==(char)0xe5) continue; + } + /*}}}*/ + /* check extent number */ /*{{{*/ + if ((dir->extnol&0xff)>0x1f) + { + printf("Error: Bad lower bits of extent number (extent=%d, name=\"%s\", low bits=%d)\n",extent,prfile(sb,extent),dir->extnol&0xff); + if (ask("Remove file")) + { + *status=(char)0xE5; + ret|=MODIFIED; + } + else ret|=BROKEN; + } + if (*status==(char)0xe5) continue; + if ((dir->extnoh&0xff)>0x3f) + { + printf("Error: Bad higher bits of extent number (extent=%d, name=\"%s\", high bits=%d)\n",extent,prfile(sb,extent),dir->extnoh&0xff); + if (ask("Remove file")) + { + *status=(char)0xE5; + ret|=MODIFIED; + } + else ret|=BROKEN; + } + if (*status==(char)0xe5) continue; + /*}}}*/ + /* check last record byte count */ /*{{{*/ + if ((dir->lrc&0xff)>128) + { + printf("Error: Bad last record byte count (extent=%d, name=\"%s\", lrc=%d)\n",extent,prfile(sb,extent),dir->lrc&0xff); + if (ask("Clear last record byte count")) + { + dir->lrc=(char)0; + ret|=MODIFIED; + } + else ret|=BROKEN; + } + if (*status==(char)0xe5) continue; + /*}}}*/ + /* check block number range */ /*{{{*/ + { + int block,min,max,i; + + min=(sb->maxdir*32+sb->blksiz-1)/sb->blksiz; + max=sb->size; + for (i=0; i<16; ++i) + { + block=dir->pointers[i]&0xff; + if (sb->size>=256) block+=(dir->pointers[++i]&0xff)<<8; + if (block>0) + { + ++usedBlocks; + if (block=max) + { + printf("Error: Bad block number (extent=%d, name=\"%s\", block=%d)\n",extent,prfile(sb,extent),block); + if (ask("Remove file")) + { + *status=(char)0xE5; + ret|=MODIFIED; + break; + } + else ret|=BROKEN; + } + } + } + if (*status==(char)0xe5) continue; + } + /*}}}*/ + /* check number of used blocks ? */ /*{{{*/ + /*}}}*/ + /* check record count */ /*{{{*/ + { + int i,min,max,recordsInBlocks,used=0; + + min=(dir->extnol%sb->extents)*16/sb->extents; + max=((dir->extnol%sb->extents)+1)*16/sb->extents; + assert(minpointers[i] || (sb->size>=256 && dir->pointers[i+1])) ++used; + if (sb->size >= 256) ++i; + } + recordsInBlocks=(((unsigned char)dir->blkcnt)*128+sb->blksiz-1)/sb->blksiz; + if (recordsInBlocks!=used) + { + printf("Error: Bad record count (extent=%d, name=\"%s\", record count=%d)\n",extent,prfile(sb,extent),dir->blkcnt&0xff); + if (ask("Remove file")) + { + *status=(char)0xE5; + ret|=MODIFIED; + } + else ret|=BROKEN; + } + if (*status==(char)0xe5) continue; + } + /*}}}*/ + /* check for too large .com files */ /*{{{*/ + if (((EXTENT(dir->extnol,dir->extnoh)==3 && dir->blkcnt>=126) || EXTENT(dir->extnol,dir->extnoh)>=4) && (dir->ext[0]&0x7f)=='C' && (dir->ext[1]&0x7f)=='O' && (dir->ext[2]&0x7f)=='M') + { + printf("Warning: Oversized .COM file (extent=%d, name=\"%s\")\n",extent,prfile(sb,extent)); + } + /*}}}*/ + } + /*}}}*/ + else if ((sb->type==CPMFS_P2DOS || sb->type==CPMFS_DR3) && *status==33) /* check time stamps ? */ /*{{{*/ + { + unsigned long created,modified; + char s; + + if ((s=sb->dir[extent2=(extent&~3)].status)>=0 && s<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* time stamps for first of the three extents */ /*{{{*/ + { + bcdCheck(dir->name[2],24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent2); + bcdCheck(dir->name[3],60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent2); + bcdCheck(dir->name[6],24,"modification date","hour",extent,extent2); + bcdCheck(dir->name[7],60,"modification date","minute",extent,extent2); + created=(dir->name[4]+(dir->name[1]<<8))*(0x60*0x60)+dir->name[2]*0x60+dir->name[3]; + modified=(dir->name[0]+(dir->name[5]<<8))*(0x60*0x60)+dir->name[6]*0x60+dir->name[7]; + if (sb->cnotatime && modifieddir[extent2=(extent&~3)+1].status)>=0 && s<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* time stamps for second */ /*{{{*/ + { + bcdCheck(dir->lrc,24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent2); + bcdCheck(dir->extnoh,60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent2); + bcdCheck(dir->pointers[1],24,"modification date","hour",extent,extent2); + bcdCheck(dir->pointers[2],60,"modification date","minute",extent,extent2); + created=(dir->ext[2]+(dir->extnol<<8))*(0x60*0x60)+dir->lrc*0x60+dir->extnoh; + modified=(dir->blkcnt+(dir->pointers[0]<<8))*(0x60*0x60)+dir->pointers[1]*0x60+dir->pointers[2]; + if (sb->cnotatime && modifieddir[extent2=(extent&~3)+2].status)>=0 && s<=(sb->type==CPMFS_P2DOS ? 31 : 15)) /* time stamps for third */ /*{{{*/ + { + bcdCheck(dir->pointers[7],24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent2); + bcdCheck(dir->pointers[8],60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent2); + bcdCheck(dir->pointers[11],24,"modification date","hour",extent,extent2); + bcdCheck(dir->pointers[12],60,"modification date","minute",extent,extent2); + created=(dir->pointers[5]+(dir->pointers[6]<<8))*(0x60*0x60)+dir->pointers[7]*0x60+dir->pointers[8]; + modified=(dir->pointers[9]+(dir->pointers[10]<<8))*(0x60*0x60)+dir->pointers[11]*0x60+dir->pointers[12]; + if (sb->cnotatime && modifiedtype==CPMFS_DR3 && *status==32) /* disc label */ /*{{{*/ + { + unsigned long created,modified; + + bcdCheck(dir->pointers[10],24,sb->cnotatime ? "creation date" : "access date","hour",extent,extent); + bcdCheck(dir->pointers[11],60,sb->cnotatime ? "creation date" : "access date","minute",extent,extent); + bcdCheck(dir->pointers[14],24,"modification date","hour",extent,extent); + bcdCheck(dir->pointers[15],60,"modification date","minute",extent,extent); + created=(dir->pointers[8]+(dir->pointers[9]<<8))*(0x60*0x60)+dir->pointers[10]*0x60+dir->pointers[11]; + modified=(dir->pointers[12]+(dir->pointers[13]<<8))*(0x60*0x60)+dir->pointers[14]*0x60+dir->pointers[15]; + if (sb->cnotatime && modifiedextnol&0x40 && dir->extnol&0x10) + { + printf("Error: Bit 4 and 6 can only be exclusively be set (extent=%d, label byte=0x%02x)\n",extent,(unsigned char)dir->extnol); + if (ask("Time stamp on creation")) + { + dir->extnol&=~0x40; + ret|=MODIFIED; + } + else if (ask("Time stamp on access")) + { + dir->extnol&=~0x10; + ret|=MODIFIED; + } + else ret|=BROKEN; + } + if (dir->extnol&0x80 && pwdCheck(extent,dir->pointers,dir->lrc)) + { + char msg[80]; + + sprintf(msg,"Set password to %c%c%c%c%c%c%c%c",T0,T1,T2,T3,T4,T5,T6,T7); + if (ask(msg)) + { + dir->pointers[0]=P0; + dir->pointers[1]=P1; + dir->pointers[2]=P2; + dir->pointers[3]=P3; + dir->pointers[4]=P4; + dir->pointers[5]=P5; + dir->pointers[6]=P6; + dir->pointers[7]=P7; + dir->lrc=PB; + ret|=MODIFIED; + } + else ret|=BROKEN; + } + } + /*}}}*/ + else if (sb->type==CPMFS_DR3 && *status>=16 && *status<=31) /* password */ /*{{{*/ + { + /* check name and extension */ /*{{{*/ + { + int i; + char *c; + + for (i=0; i<8; ++i) + { + c=&(dir->name[i]); + if (!ISFILECHAR(i,*c&0x7f) || islower(*c&0x7f)) + { + printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i); + if (ask("Clear password entry")) + { + *status=(char)0xE5; + ret|=MODIFIED; + break; + } + else ret|=BROKEN; + } + } + if (*status==(char)0xe5) continue; + for (i=0; i<3; ++i) + { + c=&(dir->ext[i]); + if (!ISFILECHAR(1,*c&0x7f) || islower(*c&0x7f)) + { + printf("Error: Bad name (extent=%d, name=\"%s\", position=%d)\n",extent,prfile(sb,extent),i); + if (ask("Clear password entry")) + { + *status=(char)0xE5; + ret|=MODIFIED; + break; + } + else ret|=BROKEN; + } + } + if (*status==(char)0xe5) continue; + } + /*}}}*/ + /* check password */ /*{{{*/ + if (dir->extnol&(0x80|0x40|0x20) && pwdCheck(extent,dir->pointers,dir->lrc)) + { + char msg[80]; + + sprintf(msg,"Set password to %c%c%c%c%c%c%c%c",T0,T1,T2,T3,T4,T5,T6,T7); + if (ask(msg)) + { + dir->pointers[0]=P0; + dir->pointers[1]=P1; + dir->pointers[2]=P2; + dir->pointers[3]=P3; + dir->pointers[4]=P4; + dir->pointers[5]=P5; + dir->pointers[6]=P6; + dir->pointers[7]=P7; + dir->lrc=PB; + ret|=MODIFIED; + } + else ret|=BROKEN; + } + /*}}}*/ + } + /*}}}*/ + else if (*status!=(char)0xe5) /* bad status */ /*{{{*/ + { + printf("Error: Bad status (extent=%d, name=\"%s\", status=0x%02x)\n",extent,prfile(sb,extent),*status&0xff); + if (ask("Clear entry")) + { + *status=(char)0xE5; + ret|=MODIFIED; + } + else ret|=BROKEN; + continue; + } + /*}}}*/ + } + /*}}}*/ + /* Phase 2: check extent connectivity */ /*{{{*/ + printf("Phase 2: check extent connectivity\n"); + /* check multiple allocated blocks */ /*{{{*/ + for (extent=0; extentmaxdir; ++extent) if ((dir=sb->dir+extent)->status>=0 && dir->status<=(sb->type==CPMFS_P2DOS ? 31 : 15)) + { + int i,j,block,block2; + + for (i=0; i<16; ++i) + { + block=dir->pointers[i]&0xff; + if (sb->size>=256) block+=(dir->pointers[++i]&0xff)<<8; + for (extent2=0; extent2maxdir; ++extent2) if ((dir2=sb->dir+extent2)->status>=0 && dir2->status<=(sb->type==CPMFS_P2DOS ? 31 : 15)) + { + for (j=0; j<16; ++j) + { + block2=dir2->pointers[j]&0xff; + if (sb->size>=256) block2+=(dir2->pointers[++j]&0xff)<<8; + if (block!=0 && block2!=0 && block==block2 && !(extent==extent2 && i==j)) + { + printf("Error: Multiple allocated block (extent=%d,%d, name=\"%s\"",extent,extent2,prfile(sb,extent)); + printf(",\"%s\" block=%d)\n",prfile(sb,extent2),block); + ret|=BROKEN; + } + } + } + } + } + /*}}}*/ + /* check multiple extents */ /*{{{*/ + for (extent=0; extentmaxdir; ++extent) if ((dir=sb->dir+extent)->status>=0 && dir->status<=(sb->type==CPMFS_P2DOS ? 31 : 15)) + { + for (extent2=0; extent2maxdir; ++extent2) if ((dir2=sb->dir+extent2)->status>=0 && dir2->status<=(sb->type==CPMFS_P2DOS ? 31 : 15)) + { + if (extent!=extent2 && EXTENT(dir->extnol,dir->extnoh)==EXTENT(dir2->extnol,dir2->extnoh) && dir->status==dir2->status) + { + int i; + + for (i=0; i<8 && (dir->name[i]&0x7f)==(dir2->name[i]&0x7f); ++i); + if (i==8) + { + for (i=0; i<3 && (dir->ext[i]&0x7f)==(dir2->ext[i]&0x7f); ++i); + if (i==3) + { + printf("Error: Duplicate extent (extent=%d,%d)\n",extent,extent2); + ret|=BROKEN; + } + } + } + } + } + /*}}}*/ + /*}}}*/ + if (ret==0) /* print statistics */ /*{{{*/ + { + struct cpmStatFS statfsbuf; + int fragmented=0,borders=0; + + cpmStatFS(root,&statfsbuf); + for (extent=0; extentmaxdir; ++extent) if ((dir=sb->dir+extent)->status>=0 && dir->status<=(sb->type==CPMFS_P2DOS ? 31 : 15)) + { + int i,block,previous=-1; + + for (i=0; i<16; ++i) + { + block=dir->pointers[i]&0xff; + if (sb->size>=256) block+=(dir->pointers[++i]&0xff)<<8; + if (previous!=-1) + { + if (block!=0 && block!=(previous+1)) ++fragmented; + ++borders; + } + previous=block; + } + } + fragmented=(borders ? (1000*fragmented)/borders : 0); + printf("%s: %ld/%ld files (%d.%d%% non-contigous), %ld/%ld blocks\n",image,statfsbuf.f_files-statfsbuf.f_ffree,statfsbuf.f_files,fragmented/10,fragmented%10,statfsbuf.f_blocks-statfsbuf.f_bfree,statfsbuf.f_blocks); + } + /*}}}*/ + return ret; +} +/*}}}*/ + +const char cmd[]="fsck.cpm"; + +/* main */ /*{{{*/ +int main(int argc, char *argv[]) +{ + const char *err; + const char *image; + const char *format; + const char *devopts=NULL; + int c,usage=0; + struct cpmSuperBlock sb; + struct cpmInode root; + enum Result ret; + + if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT; + while ((c=getopt(argc,argv,"T:f:nh?"))!=EOF) switch(c) + { + case 'f': format=optarg; break; + case 'T': devopts=optarg; break; + case 'n': norepair=1; break; + case 'h': + case '?': usage=1; break; + } + + if (optind!=(argc-1)) usage=1; + else image=argv[optind++]; + + if (usage) + { + fprintf(stderr,"Usage: %s [-f format] [-n] image\n",cmd); + exit(1); + } + if ((err=Device_open(&sb.dev, image, (norepair ? O_RDONLY : O_RDWR), devopts))) + { + if ((err=Device_open(&sb.dev, image,O_RDONLY, devopts))) + { + fprintf(stderr,"%s: cannot open %s: %s\n",cmd,image,err); + exit(1); + } + else + { + fprintf(stderr,"%s: cannot open %s for writing, no repair possible\n",cmd,image); + } + } + if (cpmReadSuper(&sb,&root,format)==-1) + { + fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo); + exit(1); + } + ret=fsck(&root,image); + if (ret&MODIFIED) + { + if (cpmSync(&sb)==-1) + { + fprintf(stderr,"%s: write error on %s: %s\n",cmd,image,strerror(errno)); + ret|=BROKEN; + } + fprintf(stderr,"%s: FILE SYSTEM ON %s MODIFIED",cmd,image); + if (ret&BROKEN) fprintf(stderr,", PLEASE CHECK AGAIN"); + fprintf(stderr,"\n"); + } + cpmUmount(&sb); + if (ret&BROKEN) return 2; + else return 0; +} +/*}}}*/ diff --git a/Tools/unix/cpmtools/fsed.cpm.1 b/Tools/unix/cpmtools/fsed.cpm.1 new file mode 100644 index 00000000..a6d58738 --- /dev/null +++ b/Tools/unix/cpmtools/fsed.cpm.1 @@ -0,0 +1,62 @@ +.TH FSED.CPM 1 "October 25, 2014" "CP/M tools" "User commands" +.SH NAME ..\"{{{roff}}}\"{{{ +fsed.cpm \- edit a CP/M file system +.\"}}} +.SH SYNOPSIS .\"{{{ +.ad l +.B fsed.cpm +.RB [ \-f +.IR format ] +.I image +.ad b +.\"}}} +.SH DESCRIPTION .\"{{{ +\fBfsed.cpm\fP edits a CP/M file system on an image file or device. +It knows about the system, directory and data area, using sector skew on +the last two. Directory entries are decoded. The interactive usage is +self-explanatory. +.\"}}} +.SH OPTIONS .\"{{{ +.IP "\fB\-f\fP \fIformat\fP" +Use the given CP/M disk \fIformat\fP instead of the default format. +.IP "\fB\-T\fP \fIlibdsk-type\fP" +libdsk driver type, e.g. \fBtele\fP for Teledisk images or \fBraw\fP for raw images +(requires building cpmtools with support for libdsk). +.\"}}} +.SH "RETURN VALUE" .\"{{{ +Upon successful completion, exit code 0 is returned. +.\"}}} +.SH ERRORS .\"{{{ +Any errors are indicated by exit code 1. +.\"}}} +.SH ENVIRONMENT \"{{{ +CPMTOOLSFMT Default format +.\"}}} +.SH FILES .\"{{{ +${prefix}/share/diskdefs CP/M disk format definitions +.\"}}} +.SH AUTHORS \"{{{ +This program is copyright 1997\(en2012 Michael Haardt +. The Windows port is copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" .\"{{{ +.IR fsck.cpm (1), +.IR mkfs.cpm (1), +.IR cpmls (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/fsed.cpm.c b/Tools/unix/cpmtools/fsed.cpm.c new file mode 100644 index 00000000..f120b77c --- /dev/null +++ b/Tools/unix/cpmtools/fsed.cpm.c @@ -0,0 +1,748 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#if NEED_NCURSES +#if HAVE_NCURSES_NCURSES_H +#include +#else +#include +#endif +#else +#include +#endif +#include +#include +#include +#include +#include + +#include "cpmfs.h" +#include "getopt_.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ + +extern char **environ; + +static char *mapbuf; + +static struct tm *cpmtime(char lday, char hday, char hour, char min) /*{{{*/ +{ + static struct tm tm; + unsigned long days=(lday&0xff)|((hday&0xff)<<8); + int d; + unsigned int md[12]={31,0,31,30,31,30,31,31,30,31,30,31}; + + tm.tm_sec=0; + tm.tm_min=((min>>4)&0xf)*10+(min&0xf); + tm.tm_hour=((hour>>4)&0xf)*10+(hour&0xf); + tm.tm_mon=0; + tm.tm_year=1978; + tm.tm_isdst=-1; + if (days) --days; + while (days>=(d=(((tm.tm_year%400)==0 || ((tm.tm_year%4)==0 && (tm.tm_year%100))) ? 366 : 365))) + { + days-=d; + ++tm.tm_year; + } + md[1]=((tm.tm_year%400)==0 || ((tm.tm_year%4)==0 && (tm.tm_year%100))) ? 29 : 28; + while (days>=md[tm.tm_mon]) + { + days-=md[tm.tm_mon]; + ++tm.tm_mon; + } + tm.tm_mday=days+1; + tm.tm_year-=1900; + return &tm; +} +/*}}}*/ +static void info(struct cpmSuperBlock *sb, const char *format, const char *image) /*{{{*/ +{ + const char *msg; + + clear(); + msg="File system characteristics"; + move(0,(COLS-strlen(msg))/2); printw(msg); + move(2,0); printw(" Image: %s",image); + move(3,0); printw(" Format: %s",format); + move(4,0); printw(" File system: "); + switch (sb->type) + { + case CPMFS_DR22: printw("CP/M 2.2"); break; + case CPMFS_P2DOS: printw("P2DOS 2.3"); break; + case CPMFS_DR3: printw("CP/M Plus"); break; + } + + move(6,0); printw(" Sector length: %d",sb->secLength); + move(7,0); printw(" Number of tracks: %d",sb->tracks); + move(8,0); printw(" Sectors per track: %d",sb->sectrk); + + move(10,0);printw(" Block size: %d",sb->blksiz); + move(11,0);printw("Number of directory entries: %d",sb->maxdir); + move(12,0);printw(" Logical sector skew: %d",sb->skew); + move(13,0);printw(" Number of system tracks: %d",sb->boottrk); + move(14,0);printw(" Logical extents per extent: %d",sb->extents); + move(15,0);printw(" Allocatable data blocks: %d",sb->size-(sb->maxdir*32+sb->blksiz-1)/sb->blksiz); + + msg="Any key to continue"; + move(23,(COLS-strlen(msg))/2); printw(msg); + getch(); +} +/*}}}*/ +static void map(struct cpmSuperBlock *sb) /*{{{*/ +{ + const char *msg; + char bmap[18*80]; + int secmap,sys,directory; + int pos; + + clear(); + msg="Data map"; + move(0,(COLS-strlen(msg))/2); printw(msg); + + secmap=(sb->tracks*sb->sectrk+80*18-1)/(80*18); + memset(bmap,' ',sizeof(bmap)); + sys=sb->boottrk*sb->sectrk; + memset(bmap,'S',sys/secmap); + directory=(sb->maxdir*32+sb->secLength-1)/sb->secLength; + memset(bmap+sys/secmap,'D',directory/secmap); + memset(bmap+(sys+directory)/secmap,'.',sb->sectrk*sb->tracks/secmap); + + for (pos=0; pos<(sb->maxdir*32+sb->secLength-1)/sb->secLength; ++pos) + { + int entry; + + Device_readSector(&sb->dev,sb->boottrk+pos/(sb->sectrk*sb->secLength),pos/sb->secLength,mapbuf); + for (entry=0; entrysecLength/32 && (pos*sb->secLength/32)+entrymaxdir; ++entry) + { + int i; + + if (mapbuf[entry*32]>=0 && mapbuf[entry*32]<=(sb->type==CPMFS_P2DOS ? 31 : 15)) + { + for (i=0; i<16; ++i) + { + int sector; + + sector=mapbuf[entry*32+16+i]&0xff; + if (sb->size>=256) sector|=(((mapbuf[entry*32+16+ ++i]&0xff)<<8)); + if (sector>0 && sector<=sb->size) + { + /* not entirely correct without the last extent record count */ + sector=sector*(sb->blksiz/sb->secLength)+sb->sectrk*sb->boottrk; + memset(bmap+sector/secmap,'#',sb->blksiz/(sb->secLength*secmap)); + } + } + } + } + } + + for (pos=0; pos<(int)sizeof(bmap); ++pos) + { + move(2+pos%18,pos/18); + addch(bmap[pos]); + } + move(21,0); printw("S=System area D=Directory area #=File data .=Free"); + msg="Any key to continue"; + move(23,(COLS-strlen(msg))/2); printw(msg); + getch(); +} +/*}}}*/ +static void data(struct cpmSuperBlock *sb, const char *buf, unsigned long int pos) /*{{{*/ +{ + int offset=(pos%sb->secLength)&~0x7f; + unsigned int i; + + for (i=0; i<128; ++i) + { + move(4+(i>>4),(i&0x0f)*3+!!(i&0x8)); printw("%02x",buf[i+offset]&0xff); + if (pos%sb->secLength==i+offset) attron(A_REVERSE); + move(4+(i>>4),50+(i&0x0f)); printw("%c",isprint(buf[i+offset]) ? buf[i+offset] : '.'); + attroff(A_REVERSE); + } + move(4+((pos&0x7f)>>4),((pos&0x7f)&0x0f)*3+!!((pos&0x7f)&0x8)+1); +} +/*}}}*/ + +const char cmd[]="fsed.cpm"; + +int main(int argc, char *argv[]) /*{{{*/ +{ + /* variables */ /*{{{*/ + const char *devopts=(const char*)0; + char *image; + const char *err; + struct cpmSuperBlock drive; + struct cpmInode root; + const char *format; + int c,usage=0; + off_t pos; + chtype ch; + int reload; + char *buf; + /*}}}*/ + + /* parse options */ /*{{{*/ + if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT; + while ((c=getopt(argc,argv,"T:f:h?"))!=EOF) switch(c) + { + case 'f': format=optarg; break; + case 'T': devopts=optarg; break; + case 'h': + case '?': usage=1; break; + } + + if (optind!=(argc-1)) usage=1; + else image=argv[optind++]; + + if (usage) + { + fprintf(stderr,"Usage: fsed.cpm [-f format] image\n"); + exit(1); + } + /*}}}*/ + /* open image */ /*{{{*/ + if ((err=Device_open(&drive.dev,image,O_RDONLY,devopts))) + { + fprintf(stderr,"%s: cannot open %s (%s)\n",cmd,image,err); + exit(1); + } + if (cpmReadSuper(&drive,&root,format)==-1) + { + fprintf(stderr,"%s: cannot read superblock (%s)\n",cmd,boo); + exit(1); + } + /*}}}*/ + /* alloc sector buffers */ /*{{{*/ + if ((buf=malloc(drive.secLength))==(char*)0 || (mapbuf=malloc(drive.secLength))==(char*)0) + { + fprintf(stderr,"fsed.cpm: can not allocate sector buffer (%s).\n",strerror(errno)); + exit(1); + } + /*}}}*/ + /* init curses */ /*{{{*/ + initscr(); + noecho(); + raw(); + nonl(); + idlok(stdscr,TRUE); + idcok(stdscr,TRUE); + keypad(stdscr,TRUE); + clear(); + /*}}}*/ + + pos=0; + reload=1; + do + { + /* display position and load data */ /*{{{*/ + clear(); + move(2,0); printw("Byte %8lu (0x%08lx) ",pos,pos); + if (pos<(drive.boottrk*drive.sectrk*drive.secLength)) + { + printw("Physical sector %3lu ",((pos/drive.secLength)%drive.sectrk)+1); + } + else + { + printw("Sector %3lu ",((pos/drive.secLength)%drive.sectrk)+1); + printw("(physical %3d) ",drive.skewtab[(pos/drive.secLength)%drive.sectrk]+1); + } + printw("Offset %5lu ",pos%drive.secLength); + printw("Track %5lu",pos/(drive.secLength*drive.sectrk)); + move(LINES-3,0); printw("N)ext track P)revious track"); + move(LINES-2,0); printw("n)ext record p)revious record f)orward byte b)ackward byte"); + move(LINES-1,0); printw("i)nfo q)uit"); + if (reload) + { + if (pos<(drive.boottrk*drive.sectrk*drive.secLength)) + { + err=Device_readSector(&drive.dev,pos/(drive.secLength*drive.sectrk),(pos/drive.secLength)%drive.sectrk,buf); + } + else + { + err=Device_readSector(&drive.dev,pos/(drive.secLength*drive.sectrk),drive.skewtab[(pos/drive.secLength)%drive.sectrk],buf); + } + if (err) + { + move(4,0); printw("Data can not be read: %s",err); + } + else reload=0; + } + /*}}}*/ + + if /* position before end of system area */ /*{{{*/ + (pos<(drive.boottrk*drive.sectrk*drive.secLength)) + { + const char *msg; + + msg="System area"; move(0,(COLS-strlen(msg))/2); printw(msg); + move(LINES-3,36); printw("F)orward 16 byte B)ackward 16 byte"); + if (!reload) data(&drive,buf,pos); + switch (ch=getch()) + { + case 'F': /* next 16 byte */ /*{{{*/ + { + if (pos+16<(drive.sectrk*drive.tracks*(off_t)drive.secLength)) + { + if (pos/drive.secLength!=(pos+16)/drive.secLength) reload=1; + pos+=16; + } + break; + } + /*}}}*/ + case 'B': /* previous 16 byte */ /*{{{*/ + { + if (pos>=16) + { + if (pos/drive.secLength!=(pos-16)/drive.secLength) reload=1; + pos-=16; + } + break; + } + /*}}}*/ + } + } + /*}}}*/ + else if /* position before end of directory area */ /*{{{*/ + (pos<(drive.boottrk*drive.sectrk*drive.secLength+drive.maxdir*32)) + { + const char *msg; + unsigned long entrystart=(pos&~0x1f)%drive.secLength; + int entry=(pos-(drive.boottrk*drive.sectrk*drive.secLength))>>5; + int offset=pos&0x1f; + + msg="Directory area"; move(0,(COLS-strlen(msg))/2); printw(msg); + move(LINES-3,36); printw("F)orward entry B)ackward entry"); + + move(13,0); printw("Entry %3d: ",entry); + if /* free or used directory entry */ /*{{{*/ + ((buf[entrystart]>=0 && buf[entrystart]<=(drive.type==CPMFS_P2DOS ? 31 : 15)) || buf[entrystart]==(char)0xe5) + { + int i; + + if (buf[entrystart]==(char)0xe5) + { + if (offset==0) attron(A_REVERSE); + printw("Free"); + attroff(A_REVERSE); + } + else printw("Directory entry"); + move(15,0); + if (buf[entrystart]!=(char)0xe5) + { + printw("User: "); + if (offset==0) attron(A_REVERSE); + printw("%2d",buf[entrystart]); + attroff(A_REVERSE); + printw(" "); + } + printw("Name: "); + for (i=0; i<8; ++i) + { + if (offset==1+i) attron(A_REVERSE); + printw("%c",buf[entrystart+1+i]&0x7f); + attroff(A_REVERSE); + } + printw(" Extension: "); + for (i=0; i<3; ++i) + { + if (offset==9+i) attron(A_REVERSE); + printw("%c",buf[entrystart+9+i]&0x7f); + attroff(A_REVERSE); + } + move(16,0); printw("Extent: %3d",((buf[entrystart+12]&0xff)+((buf[entrystart+14]&0xff)<<5))/drive.extents); + printw(" (low: "); + if (offset==12) attron(A_REVERSE); + printw("%2d",buf[entrystart+12]&0xff); + attroff(A_REVERSE); + printw(", high: "); + if (offset==14) attron(A_REVERSE); + printw("%2d",buf[entrystart+14]&0xff); + attroff(A_REVERSE); + printw(")"); + move(17,0); printw("Last extent record count: "); + if (offset==15) attron(A_REVERSE); + printw("%3d",buf[entrystart+15]&0xff); + attroff(A_REVERSE); + move(18,0); printw("Last record byte count: "); + if (offset==13) attron(A_REVERSE); + printw("%3d",buf[entrystart+13]&0xff); + attroff(A_REVERSE); + move(19,0); printw("Data blocks:"); + for (i=0; i<16; ++i) + { + unsigned int block=buf[entrystart+16+i]&0xff; + if (drive.size>=256) + { + printw(" "); + if (offset==16+i || offset==16+i+1) attron(A_REVERSE); + printw("%5d",block|(((buf[entrystart+16+ ++i]&0xff)<<8))); + attroff(A_REVERSE); + } + else + { + printw(" "); + if (offset==16+i) attron(A_REVERSE); + printw("%3d",block); + attroff(A_REVERSE); + } + } + } + /*}}}*/ + else if /* disc label */ /*{{{*/ + (buf[entrystart]==0x20 && drive.type==CPMFS_DR3) + { + int i; + const struct tm *tm; + char s[30]; + + if (offset==0) attron(A_REVERSE); + printw("Disc label"); + attroff(A_REVERSE); + move(15,0); + printw("Label: "); + for (i=0; i<11; ++i) + { + if (i+1==offset) attron(A_REVERSE); + printw("%c",buf[entrystart+1+i]&0x7f); + attroff(A_REVERSE); + } + move(16,0); + printw("Bit 0,7: "); + if (offset==12) attron(A_REVERSE); + printw("Label %s",buf[entrystart+12]&1 ? "set" : "not set"); + printw(", password protection %s",buf[entrystart+12]&0x80 ? "set" : "not set"); + attroff(A_REVERSE); + move(17,0); + printw("Bit 4,5,6: "); + if (offset==12) attron(A_REVERSE); + printw("Time stamp "); + if (buf[entrystart+12]&0x10) printw("on create, "); + else printw("not on create, "); + if (buf[entrystart+12]&0x20) printw("on modification, "); + else printw("not on modifiction, "); + if (buf[entrystart+12]&0x40) printw("on access"); + else printw("not on access"); + attroff(A_REVERSE); + move(18,0); + printw("Password: "); + for (i=0; i<8; ++i) + { + char printable; + + if (offset==16+(7-i)) attron(A_REVERSE); + printable=(buf[entrystart+16+(7-i)]^buf[entrystart+13])&0x7f; + printw("%c",isprint(printable) ? printable : ' '); + attroff(A_REVERSE); + } + printw(" XOR value: "); + if (offset==13) attron(A_REVERSE); + printw("0x%02x",buf[entrystart+13]&0xff); + attroff(A_REVERSE); + move(19,0); + printw("Created: "); + tm=cpmtime(buf[entrystart+24],buf[entrystart+25],buf[entrystart+26],buf[entrystart+27]); + if (offset==24 || offset==25) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==26) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==27) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + printw(" Updated: "); + tm=cpmtime(buf[entrystart+28],buf[entrystart+29],buf[entrystart+30],buf[entrystart+31]); + if (offset==28 || offset==29) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==30) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==31) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + } + /*}}}*/ + else if /* time stamp */ /*{{{*/ + (buf[entrystart]==0x21 && (drive.type==CPMFS_P2DOS || drive.type==CPMFS_DR3)) + { + const struct tm *tm; + char s[30]; + + if (offset==0) attron(A_REVERSE); + printw("Time stamps"); + attroff(A_REVERSE); + move(15,0); + printw("3rd last extent: Created/Accessed "); + tm=cpmtime(buf[entrystart+1],buf[entrystart+2],buf[entrystart+3],buf[entrystart+4]); + if (offset==1 || offset==2) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==3) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==4) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + printw(" Modified "); + tm=cpmtime(buf[entrystart+5],buf[entrystart+6],buf[entrystart+7],buf[entrystart+8]); + if (offset==5 || offset==6) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==7) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==8) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + + move(16,0); + printw("2nd last extent: Created/Accessed "); + tm=cpmtime(buf[entrystart+11],buf[entrystart+12],buf[entrystart+13],buf[entrystart+14]); + if (offset==11 || offset==12) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==13) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==14) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + printw(" Modified "); + tm=cpmtime(buf[entrystart+15],buf[entrystart+16],buf[entrystart+17],buf[entrystart+18]); + if (offset==15 || offset==16) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==17) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==18) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + + move(17,0); + printw(" Last extent: Created/Accessed "); + tm=cpmtime(buf[entrystart+21],buf[entrystart+22],buf[entrystart+23],buf[entrystart+24]); + if (offset==21 || offset==22) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==23) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==24) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + printw(" Modified "); + tm=cpmtime(buf[entrystart+25],buf[entrystart+26],buf[entrystart+27],buf[entrystart+28]); + if (offset==25 || offset==26) attron(A_REVERSE); + strftime(s,sizeof(s),"%x",tm); + printw("%s",s); + attroff(A_REVERSE); + printw(" "); + if (offset==27) attron(A_REVERSE); + printw("%2d",tm->tm_hour); + attroff(A_REVERSE); + printw(":"); + if (offset==28) attron(A_REVERSE); + printw("%02d",tm->tm_min); + attroff(A_REVERSE); + } + /*}}}*/ + else if /* password */ /*{{{*/ + (buf[entrystart]>=16 && buf[entrystart]<=31 && drive.type==CPMFS_DR3) + { + int i; + + if (offset==0) attron(A_REVERSE); + printw("Password"); + attroff(A_REVERSE); + + move(15,0); + printw("Name: "); + for (i=0; i<8; ++i) + { + if (offset==1+i) attron(A_REVERSE); + printw("%c",buf[entrystart+1+i]&0x7f); + attroff(A_REVERSE); + } + printw(" Extension: "); + for (i=0; i<3; ++i) + { + if (offset==9+i) attron(A_REVERSE); + printw("%c",buf[entrystart+9+i]&0x7f); + attroff(A_REVERSE); + } + + move(16,0); + printw("Password required for: "); + if (offset==12) attron(A_REVERSE); + if (buf[entrystart+12]&0x80) printw("Reading "); + if (buf[entrystart+12]&0x40) printw("Writing "); + if (buf[entrystart+12]&0x20) printw("Deleting "); + attroff(A_REVERSE); + + move(17,0); + printw("Password: "); + for (i=0; i<8; ++i) + { + char printable; + + if (offset==16+(7-i)) attron(A_REVERSE); + printable=(buf[entrystart+16+(7-i)]^buf[entrystart+13])&0x7f; + printw("%c",isprint(printable) ? printable : ' '); + attroff(A_REVERSE); + } + printw(" XOR value: "); + if (offset==13) attron(A_REVERSE); + printw("0x%02x",buf[entrystart+13]&0xff); + attroff(A_REVERSE); + } + /*}}}*/ + else /* bad status */ /*{{{*/ + { + printw("Bad status "); + if (offset==0) attron(A_REVERSE); + printw("0x%02x",buf[entrystart]); + attroff(A_REVERSE); + } + /*}}}*/ + if (!reload) data(&drive,buf,pos); + switch (ch=getch()) + { + case 'F': /* next entry */ /*{{{*/ + { + if (pos+32<(drive.sectrk*drive.tracks*(off_t)drive.secLength)) + { + if (pos/drive.secLength!=(pos+32)/drive.secLength) reload=1; + pos+=32; + } + break; + } + /*}}}*/ + case 'B': /* previous entry */ /*{{{*/ + { + if (pos>=32) + { + if (pos/drive.secLength!=(pos-32)/drive.secLength) reload=1; + pos-=32; + } + break; + } + /*}}}*/ + } + } + /*}}}*/ + else /* data area */ /*{{{*/ + { + const char *msg; + + msg="Data area"; move(0,(COLS-strlen(msg))/2); printw(msg); + if (!reload) data(&drive,buf,pos); + ch=getch(); + } + /*}}}*/ + + /* process common commands */ /*{{{*/ + switch (ch) + { + case 'n': /* next record */ /*{{{*/ + { + if (pos+128<(drive.sectrk*drive.tracks*(off_t)drive.secLength)) + { + if (pos/drive.secLength!=(pos+128)/drive.secLength) reload=1; + pos+=128; + } + break; + } + /*}}}*/ + case 'p': /* previous record */ /*{{{*/ + { + if (pos>=128) + { + if (pos/drive.secLength!=(pos-128)/drive.secLength) reload=1; + pos-=128; + } + break; + } + /*}}}*/ + case 'N': /* next track */ /*{{{*/ + { + if ((pos+drive.sectrk*drive.secLength)<(drive.sectrk*drive.tracks*drive.secLength)) + { + pos+=drive.sectrk*drive.secLength; + reload=1; + } + break; + } + /*}}}*/ + case 'P': /* previous track */ /*{{{*/ + { + if (pos>=drive.sectrk*drive.secLength) + { + pos-=drive.sectrk*drive.secLength; + reload=1; + } + break; + } + /*}}}*/ + case 'b': /* byte back */ /*{{{*/ + { + if (pos) + { + if (pos/drive.secLength!=(pos-1)/drive.secLength) reload=1; + --pos; + } + break; + } + /*}}}*/ + case 'f': /* byte forward */ /*{{{*/ + { + if (pos+1 +#include +#include +#include + +#ifdef __VMS +# include +#endif + +#ifdef _LIBC +# include +#else +#if 0 +# include +# define _(msgid) gettext (msgid) +#else +# define _(msgid) (msgid) +#endif +#endif + +#if defined _LIBC && defined USE_IN_LIBIO +# include +#endif + +#ifndef attribute_hidden +# define attribute_hidden +#endif + +/* Unlike standard Unix `getopt', functions like `getopt_long' + let the user intersperse the options with the other arguments. + + As `getopt_long' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Using `getopt' or setting the environment variable POSIXLY_CORRECT + disables permutation. + Then the application's behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#include "getopt_int.h" + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* 1003.2 says this must be 1 before any call. */ +int optind = 1; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int optopt = '?'; + +/* Keep a global copy of all internal members of getopt_data. */ + +static struct _getopt_data getopt_data; + + +#if defined HAVE_DECL_GETENV && !HAVE_DECL_GETENV +extern char *getenv (); +#endif + +#ifdef _LIBC +/* Stored original parameters. + XXX This is no good solution. We should rather copy the args so + that we can compare them later. But we must not use malloc(3). */ +extern int __libc_argc; +extern char **__libc_argv; + +/* Bash 2.0 gives us an environment variable containing flags + indicating ARGV elements that should not be considered arguments. */ + +# ifdef USE_NONOPTION_FLAGS +/* Defined in getopt_init.c */ +extern char *__getopt_nonoption_flags; +# endif + +# ifdef USE_NONOPTION_FLAGS +# define SWAP_FLAGS(ch1, ch2) \ + if (d->__nonoption_flags_len > 0) \ + { \ + char __tmp = __getopt_nonoption_flags[ch1]; \ + __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \ + __getopt_nonoption_flags[ch2] = __tmp; \ + } +# else +# define SWAP_FLAGS(ch1, ch2) +# endif +#else /* !_LIBC */ +# define SWAP_FLAGS(ch1, ch2) +#endif /* _LIBC */ + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +static void +exchange (char **argv, struct _getopt_data *d) +{ + int bottom = d->__first_nonopt; + int middle = d->__last_nonopt; + int top = d->optind; + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + +#if defined _LIBC && defined USE_NONOPTION_FLAGS + /* First make sure the handling of the `__getopt_nonoption_flags' + string can work normally. Our top argument must be in the range + of the string. */ + if (d->__nonoption_flags_len > 0 && top >= d->__nonoption_flags_max_len) + { + /* We must extend the array. The user plays games with us and + presents new arguments. */ + char *new_str = malloc (top + 1); + if (new_str == NULL) + d->__nonoption_flags_len = d->__nonoption_flags_max_len = 0; + else + { + memset (__mempcpy (new_str, __getopt_nonoption_flags, + d->__nonoption_flags_max_len), + '\0', top + 1 - d->__nonoption_flags_max_len); + d->__nonoption_flags_max_len = top + 1; + __getopt_nonoption_flags = new_str; + } + } +#endif + + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + SWAP_FLAGS (bottom + i, top - (middle - bottom) + i); + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } + else + { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + SWAP_FLAGS (bottom + i, middle + i); + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + d->__first_nonopt += (d->optind - d->__last_nonopt); + d->__last_nonopt = d->optind; +} + +/* Initialize the internal data when the first call is made. */ + +static const char * +_getopt_initialize (int argc, char **argv, const char *optstring, + int posixly_correct, struct _getopt_data *d) +{ + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + d->__first_nonopt = d->__last_nonopt = d->optind; + + d->__nextchar = NULL; + + d->__posixly_correct = posixly_correct || !!getenv ("POSIXLY_CORRECT"); + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + d->__ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + d->__ordering = REQUIRE_ORDER; + ++optstring; + } + else if (d->__posixly_correct) + d->__ordering = REQUIRE_ORDER; + else + d->__ordering = PERMUTE; + +#if defined _LIBC && defined USE_NONOPTION_FLAGS + if (!d->__posixly_correct + && argc == __libc_argc && argv == __libc_argv) + { + if (d->__nonoption_flags_max_len == 0) + { + if (__getopt_nonoption_flags == NULL + || __getopt_nonoption_flags[0] == '\0') + d->__nonoption_flags_max_len = -1; + else + { + const char *orig_str = __getopt_nonoption_flags; + int len = d->__nonoption_flags_max_len = strlen (orig_str); + if (d->__nonoption_flags_max_len < argc) + d->__nonoption_flags_max_len = argc; + __getopt_nonoption_flags = + (char *) malloc (d->__nonoption_flags_max_len); + if (__getopt_nonoption_flags == NULL) + d->__nonoption_flags_max_len = -1; + else + memset (__mempcpy (__getopt_nonoption_flags, orig_str, len), + '\0', d->__nonoption_flags_max_len - len); + } + } + d->__nonoption_flags_len = d->__nonoption_flags_max_len; + } + else + d->__nonoption_flags_len = 0; +#endif + + return optstring; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns -1. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + LONGOPTS is a vector of `struct option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. + + If POSIXLY_CORRECT is nonzero, behave as if the POSIXLY_CORRECT + environment variable were set. */ + +int +_getopt_internal_r (int argc, char **argv, const char *optstring, + const struct option *longopts, int *longind, + int long_only, int posixly_correct, struct _getopt_data *d) +{ + int print_errors = d->opterr; + if (optstring[0] == ':') + print_errors = 0; + + if (argc < 1) + return -1; + + d->optarg = NULL; + + if (d->optind == 0 || !d->__initialized) + { + if (d->optind == 0) + d->optind = 1; /* Don't scan ARGV[0], the program name. */ + optstring = _getopt_initialize (argc, argv, optstring, + posixly_correct, d); + d->__initialized = 1; + } + + /* Test whether ARGV[optind] points to a non-option argument. + Either it does not have option syntax, or there is an environment flag + from the shell indicating it is not an option. The later information + is only used when the used in the GNU libc. */ +#if defined _LIBC && defined USE_NONOPTION_FLAGS +# define NONOPTION_P (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0' \ + || (d->optind < d->__nonoption_flags_len \ + && __getopt_nonoption_flags[d->optind] == '1')) +#else +# define NONOPTION_P (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0') +#endif + + if (d->__nextchar == NULL || *d->__nextchar == '\0') + { + /* Advance to the next ARGV-element. */ + + /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been + moved back by the user (who may also have changed the arguments). */ + if (d->__last_nonopt > d->optind) + d->__last_nonopt = d->optind; + if (d->__first_nonopt > d->optind) + d->__first_nonopt = d->optind; + + if (d->__ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (d->__first_nonopt != d->__last_nonopt + && d->__last_nonopt != d->optind) + exchange ((char **) argv, d); + else if (d->__last_nonopt != d->optind) + d->__first_nonopt = d->optind; + + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ + + while (d->optind < argc && NONOPTION_P) + d->optind++; + d->__last_nonopt = d->optind; + } + + /* The special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + + if (d->optind != argc && !strcmp (argv[d->optind], "--")) + { + d->optind++; + + if (d->__first_nonopt != d->__last_nonopt + && d->__last_nonopt != d->optind) + exchange ((char **) argv, d); + else if (d->__first_nonopt == d->__last_nonopt) + d->__first_nonopt = d->optind; + d->__last_nonopt = argc; + + d->optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + + if (d->optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (d->__first_nonopt != d->__last_nonopt) + d->optind = d->__first_nonopt; + return -1; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + + if (NONOPTION_P) + { + if (d->__ordering == REQUIRE_ORDER) + return -1; + d->optarg = argv[d->optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Skip the initial punctuation. */ + + d->__nextchar = (argv[d->optind] + 1 + + (longopts != NULL && argv[d->optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* Check whether the ARGV-element is a long option. + + If long_only and the ARGV-element has the form "-f", where f is + a valid short option, don't consider it an abbreviated form of + a long option that starts with f. Otherwise there would be no + way to give the -f short option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an abbreviation of + the long option, just like "--fu", and not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + + if (longopts != NULL + && (argv[d->optind][1] == '-' + || (long_only && (argv[d->optind][2] + || !strchr (optstring, argv[d->optind][1]))))) + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = -1; + int option_index; + + for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, d->__nextchar, nameend - d->__nextchar)) + { + if ((unsigned int) (nameend - d->__nextchar) + == (unsigned int) strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else if (long_only + || pfound->has_arg != p->has_arg + || pfound->flag != p->flag + || pfound->val != p->val) + /* Second or later nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + + if (__asprintf (&buf, _("%s: option `%s' is ambiguous\n"), + argv[0], argv[d->optind]) >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#else + fprintf (stderr, _("%s: option `%s' is ambiguous\n"), + argv[0], argv[d->optind]); +#endif + } + d->__nextchar += strlen (d->__nextchar); + d->optind++; + d->optopt = 0; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + d->optind++; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + d->optarg = nameend + 1; + else + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + int n; +#endif + + if (argv[d->optind - 1][1] == '-') + { + /* --option */ +#if defined _LIBC && defined USE_IN_LIBIO + n = __asprintf (&buf, _("\ +%s: option `--%s' doesn't allow an argument\n"), + argv[0], pfound->name); +#else + fprintf (stderr, _("\ +%s: option `--%s' doesn't allow an argument\n"), + argv[0], pfound->name); +#endif + } + else + { + /* +option or -option */ +#if defined _LIBC && defined USE_IN_LIBIO + n = __asprintf (&buf, _("\ +%s: option `%c%s' doesn't allow an argument\n"), + argv[0], argv[d->optind - 1][0], + pfound->name); +#else + fprintf (stderr, _("\ +%s: option `%c%s' doesn't allow an argument\n"), + argv[0], argv[d->optind - 1][0], + pfound->name); +#endif + } + +#if defined _LIBC && defined USE_IN_LIBIO + if (n >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 + |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#endif + } + + d->__nextchar += strlen (d->__nextchar); + + d->optopt = pfound->val; + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (d->optind < argc) + d->optarg = argv[d->optind++]; + else + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + + if (__asprintf (&buf, _("\ +%s: option `%s' requires an argument\n"), + argv[0], argv[d->optind - 1]) >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 + |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#else + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[d->optind - 1]); +#endif + } + d->__nextchar += strlen (d->__nextchar); + d->optopt = pfound->val; + return optstring[0] == ':' ? ':' : '?'; + } + } + d->__nextchar += strlen (d->__nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + /* Can't find it as a long option. If this is not getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[d->optind][1] == '-' + || strchr (optstring, *d->__nextchar) == NULL) + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + int n; +#endif + + if (argv[d->optind][1] == '-') + { + /* --option */ +#if defined _LIBC && defined USE_IN_LIBIO + n = __asprintf (&buf, _("%s: unrecognized option `--%s'\n"), + argv[0], d->__nextchar); +#else + fprintf (stderr, _("%s: unrecognized option `--%s'\n"), + argv[0], d->__nextchar); +#endif + } + else + { + /* +option or -option */ +#if defined _LIBC && defined USE_IN_LIBIO + n = __asprintf (&buf, _("%s: unrecognized option `%c%s'\n"), + argv[0], argv[d->optind][0], d->__nextchar); +#else + fprintf (stderr, _("%s: unrecognized option `%c%s'\n"), + argv[0], argv[d->optind][0], d->__nextchar); +#endif + } + +#if defined _LIBC && defined USE_IN_LIBIO + if (n >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#endif + } + d->__nextchar = (char *) ""; + d->optind++; + d->optopt = 0; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + + { + char c = *d->__nextchar++; + char *temp = strchr (optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*d->__nextchar == '\0') + ++d->optind; + + if (temp == NULL || c == ':') + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + int n; +#endif + + if (d->__posixly_correct) + { + /* 1003.2 specifies the format of this message. */ +#if defined _LIBC && defined USE_IN_LIBIO + n = __asprintf (&buf, _("%s: illegal option -- %c\n"), + argv[0], c); +#else + fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], c); +#endif + } + else + { +#if defined _LIBC && defined USE_IN_LIBIO + n = __asprintf (&buf, _("%s: invalid option -- %c\n"), + argv[0], c); +#else + fprintf (stderr, _("%s: invalid option -- %c\n"), argv[0], c); +#endif + } + +#if defined _LIBC && defined USE_IN_LIBIO + if (n >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#endif + } + d->optopt = c; + return '?'; + } + /* Convenience. Treat POSIX -W foo same as long option --foo */ + if (temp[0] == 'W' && temp[1] == ';') + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + + /* This is an option that requires an argument. */ + if (*d->__nextchar != '\0') + { + d->optarg = d->__nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + d->optind++; + } + else if (d->optind == argc) + { + if (print_errors) + { + /* 1003.2 specifies the format of this message. */ +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + + if (__asprintf (&buf, + _("%s: option requires an argument -- %c\n"), + argv[0], c) >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#else + fprintf (stderr, _("%s: option requires an argument -- %c\n"), + argv[0], c); +#endif + } + d->optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } + else + /* We already incremented `d->optind' once; + increment it again when taking next ARGV-elt as argument. */ + d->optarg = argv[d->optind++]; + + /* optarg is now the argument, see if it's in the + table of longopts. */ + + for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != '='; + nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, d->__nextchar, nameend - d->__nextchar)) + { + if ((unsigned int) (nameend - d->__nextchar) == strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + if (ambig && !exact) + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + + if (__asprintf (&buf, _("%s: option `-W %s' is ambiguous\n"), + argv[0], argv[d->optind]) >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#else + fprintf (stderr, _("%s: option `-W %s' is ambiguous\n"), + argv[0], argv[d->optind]); +#endif + } + d->__nextchar += strlen (d->__nextchar); + d->optind++; + return '?'; + } + if (pfound != NULL) + { + option_index = indfound; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + d->optarg = nameend + 1; + else + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + + if (__asprintf (&buf, _("\ +%s: option `-W %s' doesn't allow an argument\n"), + argv[0], pfound->name) >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 + |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#else + fprintf (stderr, _("\ +%s: option `-W %s' doesn't allow an argument\n"), + argv[0], pfound->name); +#endif + } + + d->__nextchar += strlen (d->__nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (d->optind < argc) + d->optarg = argv[d->optind++]; + else + { + if (print_errors) + { +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + + if (__asprintf (&buf, _("\ +%s: option `%s' requires an argument\n"), + argv[0], argv[d->optind - 1]) >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 + |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#else + fprintf (stderr, + _("%s: option `%s' requires an argument\n"), + argv[0], argv[d->optind - 1]); +#endif + } + d->__nextchar += strlen (d->__nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + d->__nextchar += strlen (d->__nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + d->__nextchar = NULL; + return 'W'; /* Let the application handle it. */ + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*d->__nextchar != '\0') + { + d->optarg = d->__nextchar; + d->optind++; + } + else + d->optarg = NULL; + d->__nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*d->__nextchar != '\0') + { + d->optarg = d->__nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + d->optind++; + } + else if (d->optind == argc) + { + if (print_errors) + { + /* 1003.2 specifies the format of this message. */ +#if defined _LIBC && defined USE_IN_LIBIO + char *buf; + + if (__asprintf (&buf, _("\ +%s: option requires an argument -- %c\n"), + argv[0], c) >= 0) + { + _IO_flockfile (stderr); + + int old_flags2 = ((_IO_FILE *) stderr)->_flags2; + ((_IO_FILE *) stderr)->_flags2 |= _IO_FLAGS2_NOTCANCEL; + + __fxprintf (NULL, "%s", buf); + + ((_IO_FILE *) stderr)->_flags2 = old_flags2; + _IO_funlockfile (stderr); + + free (buf); + } +#else + fprintf (stderr, + _("%s: option requires an argument -- %c\n"), + argv[0], c); +#endif + } + d->optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + d->optarg = argv[d->optind++]; + d->__nextchar = NULL; + } + } + return c; + } +} + +int +_getopt_internal (int argc, char **argv, const char *optstring, + const struct option *longopts, int *longind, + int long_only, int posixly_correct) +{ + int result; + + getopt_data.optind = optind; + getopt_data.opterr = opterr; + + result = _getopt_internal_r (argc, argv, optstring, longopts, longind, + long_only, posixly_correct, &getopt_data); + + optind = getopt_data.optind; + optarg = getopt_data.optarg; + optopt = getopt_data.optopt; + + return result; +} + +/* glibc gets a LSB-compliant getopt. + Standalone applications get a POSIX-compliant getopt. */ +#if _LIBC +enum { POSIXLY_CORRECT = 0 }; +#else +enum { POSIXLY_CORRECT = 1 }; +#endif + +int +getopt (int argc, char *const *argv, const char *optstring) +{ + return _getopt_internal (argc, (char **) argv, optstring, NULL, NULL, 0, + POSIXLY_CORRECT); +} + + +#ifdef TEST + +/* Compile with -DTEST to make an executable for use in testing + the above definition of `getopt'. */ + +int +main (int argc, char **argv) +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + + c = getopt (argc, argv, "abc:d:0123456789"); + if (c == -1) + break; + + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/Tools/unix/cpmtools/getopt1.c b/Tools/unix/cpmtools/getopt1.c new file mode 100644 index 00000000..7fa0c4e5 --- /dev/null +++ b/Tools/unix/cpmtools/getopt1.c @@ -0,0 +1,171 @@ +/* getopt_long and getopt_long_only entry points for GNU getopt. + Copyright (C) 1987,88,89,90,91,92,93,94,96,97,98,2004,2006 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifdef _LIBC +# include +#else +# include "config.h" +# include "getopt_.h" +#endif +#include "getopt_int.h" + +#include + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +#include +#endif + +#ifndef NULL +#define NULL 0 +#endif + +int +getopt_long (int argc, char *__getopt_argv_const *argv, const char *options, + const struct option *long_options, int *opt_index) +{ + return _getopt_internal (argc, (char **) argv, options, long_options, + opt_index, 0, 0); +} + +int +_getopt_long_r (int argc, char **argv, const char *options, + const struct option *long_options, int *opt_index, + struct _getopt_data *d) +{ + return _getopt_internal_r (argc, argv, options, long_options, opt_index, + 0, 0, d); +} + +/* Like getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int +getopt_long_only (int argc, char *__getopt_argv_const *argv, + const char *options, + const struct option *long_options, int *opt_index) +{ + return _getopt_internal (argc, (char **) argv, options, long_options, + opt_index, 1, 0); +} + +int +_getopt_long_only_r (int argc, char **argv, const char *options, + const struct option *long_options, int *opt_index, + struct _getopt_data *d) +{ + return _getopt_internal_r (argc, argv, options, long_options, opt_index, + 1, 0, d); +} + + +#ifdef TEST + +#include + +int +main (int argc, char **argv) +{ + int c; + int digit_optind = 0; + + while (1) + { + int this_option_optind = optind ? optind : 1; + int option_index = 0; + static struct option long_options[] = + { + {"add", 1, 0, 0}, + {"append", 0, 0, 0}, + {"delete", 1, 0, 0}, + {"verbose", 0, 0, 0}, + {"create", 0, 0, 0}, + {"file", 1, 0, 0}, + {0, 0, 0, 0} + }; + + c = getopt_long (argc, argv, "abc:d:0123456789", + long_options, &option_index); + if (c == -1) + break; + + switch (c) + { + case 0: + printf ("option %s", long_options[option_index].name); + if (optarg) + printf (" with arg %s", optarg); + printf ("\n"); + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_optind != 0 && digit_optind != this_option_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_optind = this_option_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", optarg); + break; + + case 'd': + printf ("option d with value `%s'\n", optarg); + break; + + case '?': + break; + + default: + printf ("?? getopt returned character code 0%o ??\n", c); + } + } + + if (optind < argc) + { + printf ("non-option ARGV-elements: "); + while (optind < argc) + printf ("%s ", argv[optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/Tools/unix/cpmtools/getopt_.h b/Tools/unix/cpmtools/getopt_.h new file mode 100644 index 00000000..615ef9a3 --- /dev/null +++ b/Tools/unix/cpmtools/getopt_.h @@ -0,0 +1,226 @@ +/* Declarations for getopt. + Copyright (C) 1989-1994,1996-1999,2001,2003,2004,2005,2006,2007 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef _GETOPT_H + +#ifndef __need_getopt +# define _GETOPT_H 1 +#endif + +/* Standalone applications should #define __GETOPT_PREFIX to an + identifier that prefixes the external functions and variables + defined in this header. When this happens, include the + headers that might declare getopt so that they will not cause + confusion if included after this file. Then systematically rename + identifiers so that they do not collide with the system functions + and variables. Renaming avoids problems with some compilers and + linkers. */ +#if defined __GETOPT_PREFIX && !defined __need_getopt +# include +# include +# include +# undef __need_getopt +# undef getopt +# undef getopt_long +# undef getopt_long_only +# undef optarg +# undef opterr +# undef optind +# undef optopt +# define __GETOPT_CONCAT(x, y) x ## y +# define __GETOPT_XCONCAT(x, y) __GETOPT_CONCAT (x, y) +# define __GETOPT_ID(y) __GETOPT_XCONCAT (__GETOPT_PREFIX, y) +# define getopt __GETOPT_ID (getopt) +# define getopt_long __GETOPT_ID (getopt_long) +# define getopt_long_only __GETOPT_ID (getopt_long_only) +# define optarg __GETOPT_ID (optarg) +# define opterr __GETOPT_ID (opterr) +# define optind __GETOPT_ID (optind) +# define optopt __GETOPT_ID (optopt) +#endif + +/* Standalone applications get correct prototypes for getopt_long and + getopt_long_only; they declare "char **argv". libc uses prototypes + with "char *const *argv" that are incorrect because getopt_long and + getopt_long_only can permute argv; this is required for backward + compatibility (e.g., for LSB 2.0.1). + + This used to be `#if defined __GETOPT_PREFIX && !defined __need_getopt', + but it caused redefinition warnings if both unistd.h and getopt.h were + included, since unistd.h includes getopt.h having previously defined + __need_getopt. + + The only place where __getopt_argv_const is used is in definitions + of getopt_long and getopt_long_only below, but these are visible + only if __need_getopt is not defined, so it is quite safe to rewrite + the conditional as follows: +*/ +#if !defined __need_getopt +# if defined __GETOPT_PREFIX +# define __getopt_argv_const /* empty */ +# else +# define __getopt_argv_const const +# endif +#endif + +/* If __GNU_LIBRARY__ is not already defined, either we are being used + standalone, or this is the first header included in the source file. + If we are being used with glibc, we need to include , but + that does not exist if we are standalone. So: if __GNU_LIBRARY__ is + not defined, include , which will pull in for us + if it's from glibc. (Why ctype.h? It's guaranteed to exist and it + doesn't flood the namespace with stuff the way some other headers do.) */ +#if !defined __GNU_LIBRARY__ +# include +#endif + +#ifndef __THROW +# ifndef __GNUC_PREREQ +# define __GNUC_PREREQ(maj, min) (0) +# endif +# if defined __cplusplus && __GNUC_PREREQ (2,8) +# define __THROW throw () +# else +# define __THROW +# endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns -1, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Set to an option character which was unrecognized. */ + +extern int optopt; + +#ifndef __need_getopt +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ + const char *name; + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct option'. */ + +# define no_argument 0 +# define required_argument 1 +# define optional_argument 2 +#endif /* need getopt */ + + +/* Get definitions and prototypes for functions to process the + arguments in ARGV (ARGC of them, minus the program name) for + options given in OPTS. + + Return the option character from OPTS just read. Return -1 when + there are no more options. For unrecognized options, or options + missing arguments, `optopt' is set to the option letter, and '?' is + returned. + + The OPTS string is a list of characters which are recognized option + letters, optionally followed by colons, specifying that that letter + takes an argument, to be placed in `optarg'. + + If a letter in OPTS is followed by two colons, its argument is + optional. This behavior is specific to the GNU `getopt'. + + The argument `--' causes premature termination of argument + scanning, explicitly telling `getopt' that there are no more + options. + + If OPTS begins with `-', then non-option arguments are treated as + arguments to the option '\1'. This behavior is specific to the GNU + `getopt'. If OPTS begins with `+', or POSIXLY_CORRECT is set in + the environment, then do not permute arguments. */ + +extern int getopt (int ___argc, char *const *___argv, const char *__shortopts) + __THROW; + +#ifndef __need_getopt +extern int getopt_long (int ___argc, char *__getopt_argv_const *___argv, + const char *__shortopts, + const struct option *__longopts, int *__longind) + __THROW; +extern int getopt_long_only (int ___argc, char *__getopt_argv_const *___argv, + const char *__shortopts, + const struct option *__longopts, int *__longind) + __THROW; + +#endif + +#ifdef __cplusplus +} +#endif + +/* Make sure we later can get all the definitions and declarations. */ +#undef __need_getopt + +#endif /* getopt.h */ diff --git a/Tools/unix/cpmtools/getopt_int.h b/Tools/unix/cpmtools/getopt_int.h new file mode 100644 index 00000000..401579fd --- /dev/null +++ b/Tools/unix/cpmtools/getopt_int.h @@ -0,0 +1,131 @@ +/* Internal declarations for getopt. + Copyright (C) 1989-1994,1996-1999,2001,2003,2004 + Free Software Foundation, Inc. + This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +#ifndef _GETOPT_INT_H +#define _GETOPT_INT_H 1 + +extern int _getopt_internal (int ___argc, char **___argv, + const char *__shortopts, + const struct option *__longopts, int *__longind, + int __long_only, int __posixly_correct); + + +/* Reentrant versions which can handle parsing multiple argument + vectors at the same time. */ + +/* Data type for reentrant functions. */ +struct _getopt_data +{ + /* These have exactly the same meaning as the corresponding global + variables, except that they are used for the reentrant + versions of getopt. */ + int optind; + int opterr; + int optopt; + char *optarg; + + /* Internal members. */ + + /* True if the internal members have been initialized. */ + int __initialized; + + /* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + char *__nextchar; + + /* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters, or by calling getopt. + + PERMUTE is the default. We permute the contents of ARGV as we + scan, so that eventually all the non-options are at the end. + This allows options to be given in any order, even with programs + that were not written to expect this. + + RETURN_IN_ORDER is an option available to programs that were + written to expect options and other ARGV-elements in any order + and that care about the ordering of the two. We describe each + non-option ARGV-element as if it were the argument of an option + with character code 1. Using `-' as the first character of the + list of option characters selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return -1 with `optind' != ARGC. */ + + enum + { + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER + } __ordering; + + /* If the POSIXLY_CORRECT environment variable is set + or getopt was called. */ + int __posixly_correct; + + + /* Handle permutation of arguments. */ + + /* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first + of them; `last_nonopt' is the index after the last of them. */ + + int __first_nonopt; + int __last_nonopt; + +#if defined _LIBC && defined USE_NONOPTION_FLAGS + int __nonoption_flags_max_len; + int __nonoption_flags_len; +# endif +}; + +/* The initializer is necessary to set OPTIND and OPTERR to their + default values and to clear the initialization flag. */ +#define _GETOPT_DATA_INITIALIZER { 1, 1 } + +extern int _getopt_internal_r (int ___argc, char **___argv, + const char *__shortopts, + const struct option *__longopts, int *__longind, + int __long_only, int __posixly_correct, + struct _getopt_data *__data); + +extern int _getopt_long_r (int ___argc, char **___argv, + const char *__shortopts, + const struct option *__longopts, int *__longind, + struct _getopt_data *__data); + +extern int _getopt_long_only_r (int ___argc, char **___argv, + const char *__shortopts, + const struct option *__longopts, + int *__longind, + struct _getopt_data *__data); + +#endif /* getopt_int.h */ diff --git a/Tools/unix/cpmtools/mkfs.cpm.1 b/Tools/unix/cpmtools/mkfs.cpm.1 new file mode 100644 index 00000000..b31fb7ce --- /dev/null +++ b/Tools/unix/cpmtools/mkfs.cpm.1 @@ -0,0 +1,70 @@ +.TH MKFS.CPM 1 "October 25, 2014" "CP/M tools" "User commands" +.SH NAME \"{{{roff}}}\"{{{ +mkfs.cpm \- make a CP/M file system +.\"}}} +.SH SYNOPSIS \"{{{ +.ad l +.B mkfs.cpm +.RB [ \-f +.IR format ] +.RB [ \-b +.IR boot ] +.RB [ \-L +.IR label ] +.RB [ \-t ] +.I image +.ad b +.\"}}} +.SH DESCRIPTION \"{{{ +\fBmkfs.cpm\fP makes a CP/M file system on an image file or device. +.\"}}} +.SH OPTIONS \"{{{ +.IP "\fB\-f\fP \fIformat\fP" +Use the given CP/M disk \fIformat\fP instead of the default format. +.IP "\fB\-b\fP \fIbootblock\fP" +Write the contents of the file \fIbootblock\fP to the system tracks +instead of filling them with 0xe5. This option can be used up to four +times. The file contents (typically boot block, CCP, BDOS and BIOS) +are written to sequential sectors, padding with 0xe5 if needed. +.IP "\fB\-L\fP \fIlabel\fP" +Label the file system. This is only supported by CP/M Plus. +.IP "\fB\-t\fP" +Create time stamps. +.\"}}} +.SH "RETURN VALUE" \"{{{ +Upon successful completion, exit code 0 is returned. +.\"}}} +.SH ERRORS \"{{{ +Any errors are indicated by exit code 1. +.\"}}} +.SH ENVIRONMENT \"{{{ +CPMTOOLSFMT Default format +.\"}}} +.SH FILES \"{{{ +${prefix}/share/diskdefs CP/M disk format definitions +.\"}}} +.SH AUTHORS \"{{{ +This program is copyright 1997\(en2012 Michael Haardt +. The Windows port is copyright 2000, 2001, 2011 John Elliott +. +.PP +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. +.PP +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +.PP +You should have received a copy of the GNU General Public License along +with this program. If not, write to the Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +.\"}}} +.SH "SEE ALSO" \"{{{ +.IR fsck.cpm (1), +.IR cpmls (1), +.IR mkfs (1), +.IR cpm (5) +.\"}}} diff --git a/Tools/unix/cpmtools/mkfs.cpm.c b/Tools/unix/cpmtools/mkfs.cpm.c new file mode 100644 index 00000000..2c37bdfd --- /dev/null +++ b/Tools/unix/cpmtools/mkfs.cpm.c @@ -0,0 +1,234 @@ +/* #includes */ /*{{{C}}}*//*{{{*/ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "getopt_.h" +#include "cpmfs.h" + +#ifdef USE_DMALLOC +#include +#endif +/*}}}*/ +/* #defines */ /*{{{*/ +#ifndef O_BINARY +#define O_BINARY 0 +#endif +/*}}}*/ + +/* mkfs -- make file system */ /*{{{*/ +static int mkfs(struct cpmSuperBlock *drive, const char *name, const char *format, const char *label, char *bootTracks, int timeStamps) +{ + /* variables */ /*{{{*/ + unsigned int i; + char buf[128]; + char firstbuf[128]; + int fd; + unsigned int bytes; + unsigned int trkbytes; + /*}}}*/ + + /* open image file */ /*{{{*/ + if ((fd = open(name, O_BINARY|O_CREAT|O_WRONLY, 0666)) < 0) + { + boo=strerror(errno); + return -1; + } + /*}}}*/ + /* write system tracks */ /*{{{*/ + /* this initialises only whole tracks, so it skew is not an issue */ + trkbytes=drive->secLength*drive->sectrk; + for (i=0; iboottrk; i+=drive->secLength) if (write(fd, bootTracks+i, drive->secLength)!=(ssize_t)drive->secLength) + { + boo=strerror(errno); + close(fd); + return -1; + } + /*}}}*/ + /* write directory */ /*{{{*/ + memset(buf,0xe5,128); + bytes=drive->maxdir*32; + if (bytes%trkbytes) bytes=((bytes+trkbytes)/trkbytes)*trkbytes; + if (timeStamps && (drive->type==CPMFS_P2DOS || drive->type==CPMFS_DR3)) buf[3*32]=0x21; + memcpy(firstbuf,buf,128); + if (drive->type==CPMFS_DR3) + { + time_t now; + struct tm *t; + int min,hour,days; + + firstbuf[0]=0x20; + for (i=0; i<11 && *label; ++i,++label) firstbuf[1+i]=toupper(*label&0x7f); + while (i<11) firstbuf[1+i++]=' '; + firstbuf[12]=timeStamps ? 0x11 : 0x01; /* label set and first time stamp is creation date */ + memset(&firstbuf[13],0,1+2+8); + if (timeStamps) + { + int year; + + /* Stamp label. */ + time(&now); + t=localtime(&now); + min=((t->tm_min/10)<<4)|(t->tm_min%10); + hour=((t->tm_hour/10)<<4)|(t->tm_hour%10); + for (year=1978,days=0; year<1900+t->tm_year; ++year) + { + days+=365; + if (year%4==0 && (year%100!=0 || year%400==0)) ++days; + } + days += t->tm_yday + 1; + firstbuf[24]=firstbuf[28]=days&0xff; firstbuf[25]=firstbuf[29]=days>>8; + firstbuf[26]=firstbuf[30]=hour; + firstbuf[27]=firstbuf[31]=min; + } + } + for (i=0; itype==CPMFS_P2DOS || drive->type==CPMFS_DR3)) /*{{{*/ + { + int offset,j; + struct cpmInode ino, root; + static const char sig[] = "!!!TIME"; + unsigned int records; + struct dsDate *ds; + struct cpmSuperBlock super; + const char *err; + + if ((err=Device_open(&super.dev,name,O_RDWR,NULL))) + { + fprintf(stderr,"%s: can not open %s (%s)\n",cmd,name,err); + exit(1); + } + cpmReadSuper(&super,&root,format); + + records=root.sb->maxdir/8; + if (!(ds=malloc(records*128))) + { + cpmUmount(&super); + return -1; + } + memset(ds,0,records*128); + offset=15; + for (i=0; ids=ds; + root.sb->dirtyDs=1; + cpmUmount(&super); + } + /*}}}*/ + + return 0; +} +/*}}}*/ + +const char cmd[]="mkfs.cpm"; + +int main(int argc, char *argv[]) /*{{{*/ +{ + char *image; + const char *format; + int c,usage=0; + struct cpmSuperBlock drive; + struct cpmInode root; + const char *label="unlabeled"; + int timeStamps=0; + size_t bootTrackSize,used; + char *bootTracks; + const char *boot[4]={(const char*)0,(const char*)0,(const char*)0,(const char*)0}; + + if (!(format=getenv("CPMTOOLSFMT"))) format=FORMAT; + while ((c=getopt(argc,argv,"b:f:L:th?"))!=EOF) switch(c) + { + case 'b': + { + if (boot[0]==(const char*)0) boot[0]=optarg; + else if (boot[1]==(const char*)0) boot[1]=optarg; + else if (boot[2]==(const char*)0) boot[2]=optarg; + else if (boot[3]==(const char*)0) boot[3]=optarg; + else usage=1; + break; + } + case 'f': format=optarg; break; + case 'L': label=optarg; break; + case 't': timeStamps=1; break; + case 'h': + case '?': usage=1; break; + } + + if (optind!=(argc-1)) usage=1; + else image=argv[optind++]; + + if (usage) + { + fprintf(stderr,"Usage: %s [-f format] [-b boot] [-L label] [-t] image\n",cmd); + exit(1); + } + drive.dev.opened=0; + cpmReadSuper(&drive,&root,format); + bootTrackSize=drive.boottrk*drive.secLength*drive.sectrk; + if ((bootTracks=malloc(bootTrackSize))==(void*)0) + { + fprintf(stderr,"%s: can not allocate boot track buffer: %s\n",cmd,strerror(errno)); + exit(1); + } + memset(bootTracks,0xe5,bootTrackSize); + used=0; + for (c=0; c<4 && boot[c]; ++c) + { + int fd; + size_t size; + + if ((fd=open(boot[c],O_BINARY|O_RDONLY))==-1) + { + fprintf(stderr,"%s: can not open %s: %s\n",cmd,boot[c],strerror(errno)); + exit(1); + } + size=read(fd,bootTracks+used,bootTrackSize-used); +#if 0 + fprintf(stderr,"%d %04x %s\n",c,used+0x800,boot[c]); +#endif + if (size%drive.secLength) size=(size|(drive.secLength-1))+1; + used+=size; + close(fd); + } + if (mkfs(&drive,image,format,label,bootTracks,timeStamps)==-1) + { + fprintf(stderr,"%s: can not make new file system: %s\n",cmd,boo); + exit(1); + } + else exit(0); +} +/*}}}*/ diff --git a/Tools/unix/lzsa/BlockFormat_LZSA1.md b/Tools/unix/lzsa/BlockFormat_LZSA1.md new file mode 100644 index 00000000..5264354e --- /dev/null +++ b/Tools/unix/lzsa/BlockFormat_LZSA1.md @@ -0,0 +1,65 @@ +# Block data format (LZSA1) + +Blocks encoded as LZSA1 are composed from consecutive commands. Each command follows this format: + +* token: +* optional extra literal length +* literal values +* match offset low +* optional match offset high +* optional extra encoded match length + +**token** + +The token byte is broken down into three parts: + + 7 6 5 4 3 2 1 0 + O L L L M M M M + +* L: 3-bit literals length (0-6, or 7 if extended). If the number of literals for this command is 0 to 6, the length is encoded in the token and no extra bytes are required. Otherwise, a value of 7 is encoded and extra bytes follow as 'optional extra literal length' +* M: 4-bit encoded match length (0-14, or 15 if extended). Likewise, if the encoded match length for this command is 0 to 14, it is directly stored, otherwise 15 is stored and extra bytes follow as 'optional extra encoded match length'. Except for the last command in a block, a command always contains a match, so the encoded match length is the actual match length offset by the minimum, which is 3 bytes. For instance, an actual match length of 10 bytes to be copied, is encoded as 7. +* O: set for a 2-bytes match offset, clear for a 1-byte match offset + +**optional extra literal length** + +If the literals length is 7 or more, the 'L' bits in the token form the value 7, and an extra byte follows here, with three possible types of value: + +* 0-248: the value is added to the 7 stored in the token, to compose the final literals length. For instance a length of 206 will be stored as 7 in the token + a single byte with the value of 199, as 7 + 199 = 206. +* 250: a second byte follows. The final literals value is 256 + the second byte. For instance, a literals length of 499 is encoded as 7 in the token, a byte with the value of 250, and a final byte with the value of 243, as 256 + 243 = 499. +* 249: a second and third byte follow, forming a little-endian 16-bit value. The final literals value is that 16-bit value. For instance, a literals length of 1024 is stored as 7 in the token, then byte values of 249, 0 and 4, as (4 * 256) = 1024. + +The extension byte values are chosen so that all three cases can be detected on 8-bit CPUs with a simple addition and overflow check. + +**literal values** + +Literal bytes, whose number is specified by the literals length, follow here. There can be zero literals in a command. + +Important note: for blocks that are part of a stream, the last command in a block ends here, as it always contains literals only. For raw blocks, the last command does contain the match offset and match length, see the note below for EOD detection. + +**match offset low** + +The low 8 bits of the match offset follows. + +**optional match offset high** + +If the 'O' bit (bit 7) is set in the token, the high 8 bits of the match offset follow, otherwise they are understood to be all set to 1. For instance, a short offset of 0x70 is interpreted as 0xff70. + +**important note regarding match offsets: stored as negative values** + +Note that the match offset is negative: it is added to the current decompressed location and not substracted, in order to locate the back-reference to copy. + +**optional extra encoded match length** + +If the encoded match length is 15 or more, the 'M' bits in the token form the value 15, and an extra byte follows here, with three possible types of value. + +* 0-237: the value is added to the 15 stored in the token. The final value is 3 + 15 + this byte. +* 239: a second byte follows. The final match length is 256 + the second byte. +* 238: a second and third byte follow, forming a little-endian 16-bit value. The final encoded match length is that 16-bit value. + +Again, the extension byte values are chosen so that all cases can be detected with a simple addition and overflow check on 8-bit CPUs. + +# End Of Data detection for raw blocks + +When the LZSA1 block is part of a stream (see StreamFormat.md), as previously mentioned, the block ends after the literal values of the last command, without a match offset or match length. + +However, in a raw LZSA1 block, the last command does include a 1-byte match offset (set to zero) and a match length. The match length is encoded as a long zero: the 'M' bits in the token form the value 15, then an extra match length byte is present, with the value 238 ("two match length bytes follow"). Finally, a two-byte zero match length follows, indicating the end of the block. EOD is the only time a zero match length (which normally would indicate a copy of 3 bytes) is encoded as a large 2-byte match value. This allows the EOD test to exist in a rarely used code branch. diff --git a/Tools/unix/lzsa/BlockFormat_LZSA2.md b/Tools/unix/lzsa/BlockFormat_LZSA2.md new file mode 100644 index 00000000..15ec3781 --- /dev/null +++ b/Tools/unix/lzsa/BlockFormat_LZSA2.md @@ -0,0 +1,89 @@ +# Block data format (LZSA2) + +Blocks encoded as LZSA2 are composed from consecutive commands. Each command follows this format: + +* token: +* optional extra literal length +* literal values +* match offset +* optional extra encoded match length + +**token** + +The token byte is broken down into three parts: + + 7 6 5 4 3 2 1 0 + X Y Z L L M M M + +* L: 2-bit literals length (0-2, or 3 if extended). If the number of literals for this command is 0 to 2, the length is encoded in the token and no extra bytes are required. Otherwise, a value of 3 is encoded and extra nibbles or bytes follow as 'optional extra literal length' +* M: 3-bit encoded match length (0-6, or 7 if extended). Likewise, if the encoded match length for this command is 0 to 6, it is directly stored, otherwise 7 is stored and extra nibbles or bytes follow as 'optional extra encoded match length'. Except for the last command in a block, a command always contains a match, so the encoded match length is the actual match length offset by the minimum, which is 2 bytes. For instance, an actual match length of 5 bytes to be copied, is encoded as 3. +* XYZ: 3-bit value that indicates how to decode the match offset + +**optional extra literal length** + +If the literals length is 3 or more, the 'L' bits in the token form the value 3, and an extra nibble is read: + +* 0-14: the value is added to the 3 stored in the token, to compose the final literals length. +* 15: an extra byte follows + +If an extra byte follows, it can have two possible types of value: + +* 0-237: 18 is added to the value (3 from the token + 15 from the nibble), to compose the final literals length. For instance a length of 206 will be stored as 3 in the token + a nibble with the value of 15 + a single byte with the value of 188. +* 239: a second and third byte follow, forming a little-endian 16-bit value. The final literals value is that 16-bit value. For instance, a literals length of 1027 is stored as 3 in the token, a nibble with the value of 15, then byte values of 239, 3 and 4, as 3 + (4 * 256) = 1027. + +**literal values** + +Literal bytes, whose number is specified by the literals length, follow here. There can be zero literals in a command. + +Important note: for blocks that are part of a stream, the last command in a block ends here, as it always contains literals only. For raw blocks, the last command does contain the match offset and match length, see the note below for EOD detection. + +**match offset** + +The match offset is decoded according to the XYZ bits in the token + + XYZ + 00Z 5-bit offset: read a nibble for offset bits 1-4 and use the inverted bit Z of the token as bit 0 of the offset. set bits 5-15 of the offset to 1. + 01Z 9-bit offset: read a byte for offset bits 0-7 and use the inverted bit Z for bit 8 of the offset. set bits 9-15 of the offset to 1. + 10Z 13-bit offset: read a nibble for offset bits 9-12 and use the inverted bit Z for bit 8 of the offset, then read a byte for offset bits 0-7. set bits 13-15 of the offset to 1. + 110 16-bit offset: read a byte for offset bits 8-15, then another byte for offset bits 0-7. + 111 repeat offset: reuse the offset value of the previous match command. + +The bit ordering and inversion helps optimize the decoder for size and speed on 8-bit CPUs. + +**important note regarding match offsets: stored as negative values** + +Note that the match offset is negative: it is added to the current decompressed location and not substracted, in order to locate the back-reference to copy. For this reason, as already indicated, unexpressed offset bits are set to 1 instead of 0. + +**optional extra encoded match length** + +If the encoded match length is 7 or more, the 'M' bits in the token form the value 7, and an extra nibble is read: + +* 0-14: the value is added to the 3 stored in the token, and then the minmatch of 2 is added, to compose the final match length. +* 15: an extra byte follows + +If an extra byte follows here, it can have two possible types of value: + +* 0-231: 24 is added to the value (7 from the token + 15 from the nibble + minmatch of 2), to compose the final match length. For instance a length of 150 will be stored as 7 in the token + a nibble with the value of 15 + a single byte with the value of 126. +* 233: a second and third byte follow, forming a little-endian 16-bit value. The final encoded match length is that 16-bit value. + +# End Of Data detection for raw blocks + +When the LZSA2 block is part of a stream (see StreamFormat.md), as previously mentioned, the block ends after the literal values of the last command, without a match offset or match length. + +However, in a raw LZSA2 block, the last command does include a 9-bit match offset (set to zero, to be ignored) and a EOD marker as the match length. The EOD match length marker is encoded as such: the 'M' bits in the token form the value 7, then a nibble with the value of 15 is present, then a single extra match length byte with the value of 232, indicating the end of the block. This allows the EOD test to exist in a rarely used code branch. + +The EOD condition can be easily checked as part of the tri-state condition when handling long matches. When 24 is added to the match byte value: +- If the byte doesn't overflow, the final match length is ready +- If the byte overflows and equals zero, the EOD marker has been hit +- Otherwise, if the overflows and doesn't equal zero, a 16-bit match length must be read. + +This tri-state test translates to only an addition and two branches on 8-bit CPUs. + +The equivalent EOD condition in literal lengths (which would be byte 238, that would overflow to exactly 0 when adding 18) is never emitted, so for size-optimized decompressors, the same code can be used to read both types of lengths. + +# Reading nibbles + +When the specification indicates that a nibble (4 bit value) must be read: + +* If there are no nibbles ready, read a byte immediately. Return the high 4 bits (bits 4-7) as the nibble and store the low 4 bits for later. Flag that a nibble is ready for next time. +* If a nibble is ready, return the previously stored low 4 bits (bits 0-3) and flag that no nibble is ready for next time. diff --git a/Tools/unix/lzsa/LICENSE b/Tools/unix/lzsa/LICENSE new file mode 100644 index 00000000..29b28c12 --- /dev/null +++ b/Tools/unix/lzsa/LICENSE @@ -0,0 +1,3 @@ +The LZSA code is available under the Zlib license, except for src/matchfinder.c which is placed under the Creative Commons CC0 license. + +Please consult LICENSE.zlib.md and LICENSE.CC0.md for more information. diff --git a/Tools/unix/lzsa/LICENSE.cc0.md b/Tools/unix/lzsa/LICENSE.cc0.md new file mode 100644 index 00000000..139c68e0 --- /dev/null +++ b/Tools/unix/lzsa/LICENSE.cc0.md @@ -0,0 +1,43 @@ +## creative commons + +# CC0 1.0 Universal + +CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. + +### Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. + +1. __Copyright and Related Rights.__ A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. + +2. __Waiver.__ To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. + +3. __Public License Fallback.__ Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. + +4. __Limitations and Disclaimers.__ + + a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. diff --git a/Tools/unix/lzsa/LICENSE.zlib.md b/Tools/unix/lzsa/LICENSE.zlib.md new file mode 100644 index 00000000..e1296a1f --- /dev/null +++ b/Tools/unix/lzsa/LICENSE.zlib.md @@ -0,0 +1,19 @@ +Copyright (c) 2019 Emmanuel Marty + +This software is provided 'as-is', without any express or implied warranty. In +no event will the authors be held liable for any damages arising from the use of +this software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim + that you wrote the original software. If you use this software in a product, + an acknowledgment in the product documentation would be appreciated but is + not required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + +3. This notice may not be removed or altered from any source distribution. diff --git a/Tools/unix/lzsa/Makefile b/Tools/unix/lzsa/Makefile new file mode 100644 index 00000000..0e0e7b28 --- /dev/null +++ b/Tools/unix/lzsa/Makefile @@ -0,0 +1,46 @@ +CC=gcc +CFLAGS=-O3 -fomit-frame-pointer -Isrc/libdivsufsort/include -Isrc +OBJDIR=obj +LDFLAGS= +STRIP=strip + +$(OBJDIR)/%.o: src/../%.c + @mkdir -p '$(@D)' + $(CC) $(CFLAGS) -c $< -o $@ + +APP := lzsa + +OBJS += $(OBJDIR)/src/lzsa.o +OBJS += $(OBJDIR)/src/dictionary.o +OBJS += $(OBJDIR)/src/expand_block_v1.o +OBJS += $(OBJDIR)/src/expand_block_v2.o +OBJS += $(OBJDIR)/src/expand_context.o +OBJS += $(OBJDIR)/src/expand_inmem.o +OBJS += $(OBJDIR)/src/expand_streaming.o +OBJS += $(OBJDIR)/src/frame.o +OBJS += $(OBJDIR)/src/matchfinder.o +OBJS += $(OBJDIR)/src/shrink_block_v1.o +OBJS += $(OBJDIR)/src/shrink_block_v2.o +OBJS += $(OBJDIR)/src/shrink_context.o +OBJS += $(OBJDIR)/src/shrink_inmem.o +OBJS += $(OBJDIR)/src/shrink_streaming.o +OBJS += $(OBJDIR)/src/stream.o +OBJS += $(OBJDIR)/src/libdivsufsort/lib/divsufsort.o +OBJS += $(OBJDIR)/src/libdivsufsort/lib/divsufsort_utils.o +OBJS += $(OBJDIR)/src/libdivsufsort/lib/sssort.o +OBJS += $(OBJDIR)/src/libdivsufsort/lib/trsort.o + +UNAME := $(shell uname) + +all: $(APP) + cp $(APP) ../../$(UNAME) + +$(APP): $(OBJS) + @mkdir -p ../../bin/posix + $(CC) $^ $(LDFLAGS) -o $(APP) + +clean: + @rm -rf $(APP) $(OBJDIR) + +clobber: + rm -f ../../$(UNAME)/$(APP) diff --git a/Tools/unix/lzsa/README.md b/Tools/unix/lzsa/README.md new file mode 100644 index 00000000..6430ee5c --- /dev/null +++ b/Tools/unix/lzsa/README.md @@ -0,0 +1,84 @@ +LZSA is a collection of byte-aligned compression formats that are specifically engineered for very fast decompression on 8-bit systems. It can compress files of any size by using blocks of a maximum size of 64 Kb with block-interdependent compression and up to 64 Kb of back-references for matches. + +![Pareto frontier](pareto_graph.png) +*ZX Spectrum + +Check out [The Hollow](https://www.pouet.net/prod.php?which=81909) by Darklite and Offense, winner of the Solskogen 2019 wild compo, that uses LZSA on Z80. + +[Gabba](https://www.pouet.net/prod.php?which=83539) by Stardust ranked 2nd in the ZX Spectrum demo compo at CAFe demoparty 2019 and also used LZSA on Z80. + +The LZSA compression tool uses an aggressive optimal packing strategy to try to find the sequence of commands that gives the smallest packed file that decompresses to the original while maintaining the maximum possible decompression speed. + +The compression formats give the user choices that range from decompressing faster than LZ4 on 8-bit systems with better compression, to compressing as well as ZX7 with much better decompression speed. LZSA1 is designed to replace LZ4 and LZSA2 to replace ZX7, in 8-bit scenarios. + +Compression ratio comparison between LZSA and other optimal packers, for a workload composed of ZX Spectrum and C64 files: + + Bytes Ratio Decompression speed vs. LZ4 + LZSA2 676681 52,49% <------ 75% + MegaLZ 4.89 679041 52,68% Not measured + ZX7 687133 53,30% 47,73% + LZ5 1.4.1 727107 56,40% 75% + LZSA1 735785 57,08% <------ 90% + Lizard -29 776122 60,21% Not measured + LZ4_HC -19 -B4 -BD 781049 60,59% 100% + Uncompressed 1289127 100% N/A + +Performance over well-known compression corpus files: + + Uncompressed LZ4_HC -19 -B4 -BD LZSA1 LZSA2 + Canterbury 2810784 935827 (33,29%) 850792 (30,27%) 770877 (27,43%) + Silesia 211938580 77299725 (36,47%) 73706340 (34,78%) 68928564 (32,52%) + Calgary 3251493 1248780 (38,40%) 1192123 (36,67%) 1110290 (34,15%) + Large 11159482 3771025 (33,79%) 3648393 (32,69%) 3519480 (31,54%) + enwik9 1000000000 371841591 (37,18%) 355360043 (35,54%) 334900611 (33,49%) + +As an example of LZSA1's simplicity, a size-optimized decompressor on Z80 has been implemented in 67 bytes. + +The compressor is approximately 2X slower than LZ4_HC but compresses better while maintaining similar decompression speeds and decompressor simplicity. + +The main differences between LZSA1 and the LZ4 compression format are: + +* The use of short (8-bit) match offsets where possible. The match-finder and optimizer cooperate to try and use the shortest match offsets possible. +* Shorter encoding of lengths. As blocks are maximum 64 Kb in size, lengths can only be up to 64 Kb. +* As a result of the smaller commands due to the possibly shorter match offsets, a minimum match size of 3 bytes instead of 4. The use of small matches is driven by the optimizer, and used where they provide gains. + +As for LZSA2: +* 5-bit, 9-bit, 13-bit and 16-bit match offsets, using nibble encoding +* Rep-matches +* Shorter encoding of lengths, also using nibbles +* A minmatch of 2 bytes +* No (slow) bit-packing. LZSA2 uses byte alignment in the hot path, and nibbles. + +Inspirations: + +* [LZ4](https://github.com/lz4/lz4) by Yann Collet. +* [LZ5/Lizard](https://github.com/inikep/lizard) by Przemyslaw Skibinski and Yann Collet. +* The suffix array intervals in [Wimlib](https://wimlib.net/git/?p=wimlib;a=tree) by Eric Biggers. +* ZX7 by Einar Saukas +* [apc](https://github.com/svendahl/cap) by Sven-Åke Dahl +* [Charles Bloom](http://cbloomrants.blogspot.com/)'s compression blog + +License: + +* The LZSA code is available under the Zlib license. +* The match finder (matchfinder.c) is available under the CC0 license due to using portions of code from Eric Bigger's Wimlib in the suffix array-based matchfinder. + +8-bit assembly code: + +* Z80 decompressors (size- and speed-optimized) written by [introspec](https://github.com/specke) +* 6502 and 8088 size-optimized improvements by [Peter Ferrie](https://github.com/peterferrie) +* 8088 speed-optimized decompressor by [Jim Leonard](https://github.com/mobygamer) + +External links: + +* [i8080 decompressors](https://gitlab.com/ivagor/lzsa8080/tree/master) by Ivan Gorodetsky +* [PDP-11 decompressors](https://gitlab.com/ivagor/lzsa8080/tree/master/PDP11) also by Ivan Gorodetsky +* LZSA's page on [Pouet](https://www.pouet.net/prod.php?which=81573) + +# Compressed format + +Decompression code is provided for common 8-bit CPUs such as Z80 and 6502. However, if you would like to write your own, or understand the encoding, LZSA compresses data to a format that is fast and simple to decompress on 8-bit CPUs. It is encoded in either a stream of blocks, or as a single raw block, depending on command-line settings. The encoding is deliberately designed to avoid complicated operations on 8-bits (such as 16-bit math). + +* [Stream format](https://github.com/emmanuel-marty/lzsa/blob/master/StreamFormat.md) +* [Block encoding for LZSA1](https://github.com/emmanuel-marty/lzsa/blob/master/BlockFormat_LZSA1.md) +* [Block encoding for LZSA2](https://github.com/emmanuel-marty/lzsa/blob/master/BlockFormat_LZSA2.md) diff --git a/Tools/unix/lzsa/StreamFormat.md b/Tools/unix/lzsa/StreamFormat.md new file mode 100644 index 00000000..3f37f868 --- /dev/null +++ b/Tools/unix/lzsa/StreamFormat.md @@ -0,0 +1,39 @@ +# Stream format + +The stream format is composed of: + +* a header +* one or more frames +* a footer + +# Header format + +The 3-bytes LZSA header contains a signature and a traits byte: + + 0 1 2 + 0x7b 0x9e 7 6 5 4 3 2 1 + V V V Z Z Z Z + <--- signature ---> <- traits -> + +Trait bits: + +* V: 3 bit code that indicates which block data encoding is used. 0 is LZSA1 and 2 is LZSA2. +* Z: these bits in the traits are set to 0 for LZSA1 and LZSA2. + +# Frame format + +Each frame contains a 3-bytes length followed by block data that expands to up to 64 Kb of decompressed data. The block data is encoded either as LZSA1 or LZSA2 depending on the V bits of the traits byte in the header. + + 0 1 2 + DSZ0 DSZ1 U|DSZ2 + +* DSZ0 (length byte 0) contains bits 0-7 of the block data size +* DSZ1 (length byte 1) contains bits 8-15 of the block data size +* DSZ2 (bit 0 of length byte 2) contains bit 16 of the block data size +* U (bit 7 of length byte 2) is set if the block data is uncompressed, and clear if the block data is compressed. +* Bits 1..6 of length byte 2 are currently undefined and must be set to 0. + +# Footer format + +The stream ends with the EOD frame: the 3 length bytes are set to 0x00, 0x00, 0x00, and no block data follows. + diff --git a/Tools/unix/lzsa/VS2017/lzsa.sln b/Tools/unix/lzsa/VS2017/lzsa.sln new file mode 100644 index 00000000..ffd10e37 --- /dev/null +++ b/Tools/unix/lzsa/VS2017/lzsa.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.489 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lzsa", "lzsa.vcxproj", "{3F30FEE8-63C5-4D39-A175-EDD7EA93E9B8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3F30FEE8-63C5-4D39-A175-EDD7EA93E9B8}.Debug|x64.ActiveCfg = Debug|x64 + {3F30FEE8-63C5-4D39-A175-EDD7EA93E9B8}.Debug|x64.Build.0 = Debug|x64 + {3F30FEE8-63C5-4D39-A175-EDD7EA93E9B8}.Debug|x86.ActiveCfg = Debug|Win32 + {3F30FEE8-63C5-4D39-A175-EDD7EA93E9B8}.Debug|x86.Build.0 = Debug|Win32 + {3F30FEE8-63C5-4D39-A175-EDD7EA93E9B8}.Release|x64.ActiveCfg = Release|x64 + {3F30FEE8-63C5-4D39-A175-EDD7EA93E9B8}.Release|x64.Build.0 = Release|x64 + {3F30FEE8-63C5-4D39-A175-EDD7EA93E9B8}.Release|x86.ActiveCfg = Release|Win32 + {3F30FEE8-63C5-4D39-A175-EDD7EA93E9B8}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A1E1655C-AA9F-41F0-80C9-18DD0B859D7C} + EndGlobalSection +EndGlobal diff --git a/Tools/unix/lzsa/VS2017/lzsa.vcxproj b/Tools/unix/lzsa/VS2017/lzsa.vcxproj new file mode 100644 index 00000000..f9275dcd --- /dev/null +++ b/Tools/unix/lzsa/VS2017/lzsa.vcxproj @@ -0,0 +1,225 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {3F30FEE8-63C5-4D39-A175-EDD7EA93E9B8} + Win32Proj + lzsa + 8.1 + + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + Application + true + v141 + Unicode + + + Application + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(ProjectDir)bin\ + $(ProjectName)_debug + + + true + $(ProjectDir)bin\ + $(ProjectName)_debug + + + false + $(ProjectDir)bin\ + + + false + $(ProjectDir)bin\ + + + + NotUsing + Level3 + Disabled + true + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + MultiThreadedDebug + ..\src\libdivsufsort\include;..\;%(AdditionalIncludeDirectories) + + + Console + true + $(ProjectDir)bin\$(TargetName)$(TargetExt) + + + + + NotUsing + Level3 + Disabled + true + _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + MultiThreadedDebug + ..\src\libdivsufsort\include;..\;%(AdditionalIncludeDirectories) + + + Console + true + $(ProjectDir)bin\$(TargetName)$(TargetExt) + + + + + NotUsing + Level3 + MaxSpeed + true + true + true + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + MultiThreaded + Speed + ..\src\libdivsufsort\include;..\;%(AdditionalIncludeDirectories) + true + + + Console + true + true + true + $(ProjectDir)bin\$(TargetName)$(TargetExt) + + + + + NotUsing + Level3 + MaxSpeed + true + true + true + _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + MultiThreaded + Speed + ..\src\libdivsufsort\include;..\;%(AdditionalIncludeDirectories) + true + + + Console + true + true + true + $(ProjectDir)bin\$(TargetName)$(TargetExt) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tools/unix/lzsa/VS2017/lzsa.vcxproj.filters b/Tools/unix/lzsa/VS2017/lzsa.vcxproj.filters new file mode 100644 index 00000000..415b4dd8 --- /dev/null +++ b/Tools/unix/lzsa/VS2017/lzsa.vcxproj.filters @@ -0,0 +1,147 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {5ec09c0d-19f7-4a6f-b524-f405fb99e48c} + + + {a922f475-1322-496d-8a6d-7f1c6b92423d} + + + {bd05c6e8-af92-4ab8-8916-0424cd8d186b} + + + + + Fichiers d%27en-tête + + + Fichiers sources + + + Fichiers sources\libdivsufsort\include + + + Fichiers sources\libdivsufsort\include + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources\libdivsufsort\include + + + + + Fichiers sources\libdivsufsort\lib + + + Fichiers sources\libdivsufsort\lib + + + Fichiers sources\libdivsufsort\lib + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources + + + Fichiers sources\libdivsufsort\lib + + + \ No newline at end of file diff --git a/Tools/unix/lzsa/VS2017/lzsa.vcxproj.user b/Tools/unix/lzsa/VS2017/lzsa.vcxproj.user new file mode 100644 index 00000000..58ecafc9 --- /dev/null +++ b/Tools/unix/lzsa/VS2017/lzsa.vcxproj.user @@ -0,0 +1,27 @@ + + + + $(TargetPath) + -f2 -c -v corpus/zxspectrum/graphics/bfox-dont_go_away_(2010).mg1 bfox.lzs + WindowsLocalDebugger + $(ProjectDir)..\ + + + $(TargetPath) + -f2 -c -v corpus/zxspectrum/graphics/bfox-dont_go_away_(2010).mg1 bfox.lzs + WindowsLocalDebugger + $(ProjectDir)..\ + + + $(TargetPath) + -f2 -c -v corpus/zxspectrum/graphics/bfox-dont_go_away_(2010).mg1 bfox.lzs + WindowsLocalDebugger + $(ProjectDir)..\ + + + $(TargetPath) + -f2 -c -v corpus/zxspectrum/graphics/bfox-dont_go_away_(2010).mg1 bfox.lzs + WindowsLocalDebugger + $(ProjectDir)..\ + + \ No newline at end of file diff --git a/Tools/unix/lzsa/Xcode/lzsa.xcodeproj/project.pbxproj b/Tools/unix/lzsa/Xcode/lzsa.xcodeproj/project.pbxproj new file mode 100644 index 00000000..608a7d6c --- /dev/null +++ b/Tools/unix/lzsa/Xcode/lzsa.xcodeproj/project.pbxproj @@ -0,0 +1,429 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 0CADC63122AAD8EB003E9821 /* shrink_inmem.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC5EE22AAD8EA003E9821 /* shrink_inmem.c */; }; + 0CADC63222AAD8EB003E9821 /* frame.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC5F322AAD8EB003E9821 /* frame.c */; }; + 0CADC63322AAD8EB003E9821 /* matchfinder.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC5F422AAD8EB003E9821 /* matchfinder.c */; }; + 0CADC63422AAD8EB003E9821 /* shrink_block_v1.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC5FA22AAD8EB003E9821 /* shrink_block_v1.c */; }; + 0CADC63A22AAD8EB003E9821 /* trsort.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC61622AAD8EB003E9821 /* trsort.c */; }; + 0CADC63B22AAD8EB003E9821 /* divsufsort.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC61722AAD8EB003E9821 /* divsufsort.c */; }; + 0CADC63D22AAD8EB003E9821 /* sssort.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC61922AAD8EB003E9821 /* sssort.c */; }; + 0CADC63E22AAD8EB003E9821 /* expand_block_v1.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC62122AAD8EB003E9821 /* expand_block_v1.c */; }; + 0CADC63F22AAD8EB003E9821 /* lzsa.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC62222AAD8EB003E9821 /* lzsa.c */; }; + 0CADC64022AAD8EB003E9821 /* shrink_streaming.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC62322AAD8EB003E9821 /* shrink_streaming.c */; }; + 0CADC64122AAD8EB003E9821 /* expand_inmem.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC62522AAD8EB003E9821 /* expand_inmem.c */; }; + 0CADC64222AAD8EB003E9821 /* stream.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC62922AAD8EB003E9821 /* stream.c */; }; + 0CADC64322AAD8EB003E9821 /* expand_block_v2.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC62A22AAD8EB003E9821 /* expand_block_v2.c */; }; + 0CADC64422AAD8EB003E9821 /* shrink_context.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC62B22AAD8EB003E9821 /* shrink_context.c */; }; + 0CADC64522AAD8EB003E9821 /* expand_streaming.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC62D22AAD8EB003E9821 /* expand_streaming.c */; }; + 0CADC64622AAD8EB003E9821 /* dictionary.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC62E22AAD8EB003E9821 /* dictionary.c */; }; + 0CADC64722AAD8EB003E9821 /* expand_context.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC62F22AAD8EB003E9821 /* expand_context.c */; }; + 0CADC64822AAD8EB003E9821 /* shrink_block_v2.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC63022AAD8EB003E9821 /* shrink_block_v2.c */; }; + 0CADC64A22AB8DAD003E9821 /* divsufsort_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = 0CADC64922AB8DAD003E9821 /* divsufsort_utils.c */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 0CADC57622A65EA4003E9821 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0CADC57822A65EA5003E9821 /* lzsa */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = lzsa; sourceTree = BUILT_PRODUCTS_DIR; }; + 0CADC5ED22AAD8EA003E9821 /* expand_streaming.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = expand_streaming.h; path = ../../src/expand_streaming.h; sourceTree = ""; }; + 0CADC5EE22AAD8EA003E9821 /* shrink_inmem.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = shrink_inmem.c; path = ../../src/shrink_inmem.c; sourceTree = ""; }; + 0CADC5EF22AAD8EB003E9821 /* stream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = stream.h; path = ../../src/stream.h; sourceTree = ""; }; + 0CADC5F022AAD8EB003E9821 /* expand_block_v1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = expand_block_v1.h; path = ../../src/expand_block_v1.h; sourceTree = ""; }; + 0CADC5F122AAD8EB003E9821 /* shrink_block_v1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = shrink_block_v1.h; path = ../../src/shrink_block_v1.h; sourceTree = ""; }; + 0CADC5F222AAD8EB003E9821 /* lib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lib.h; path = ../../src/lib.h; sourceTree = ""; }; + 0CADC5F322AAD8EB003E9821 /* frame.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = frame.c; path = ../../src/frame.c; sourceTree = ""; }; + 0CADC5F422AAD8EB003E9821 /* matchfinder.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = matchfinder.c; path = ../../src/matchfinder.c; sourceTree = ""; }; + 0CADC5F522AAD8EB003E9821 /* matchfinder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = matchfinder.h; path = ../../src/matchfinder.h; sourceTree = ""; }; + 0CADC5F622AAD8EB003E9821 /* dictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dictionary.h; path = ../../src/dictionary.h; sourceTree = ""; }; + 0CADC5F722AAD8EB003E9821 /* shrink_context.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = shrink_context.h; path = ../../src/shrink_context.h; sourceTree = ""; }; + 0CADC5F822AAD8EB003E9821 /* shrink_inmem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = shrink_inmem.h; path = ../../src/shrink_inmem.h; sourceTree = ""; }; + 0CADC5F922AAD8EB003E9821 /* expand_block_v2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = expand_block_v2.h; path = ../../src/expand_block_v2.h; sourceTree = ""; }; + 0CADC5FA22AAD8EB003E9821 /* shrink_block_v1.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = shrink_block_v1.c; path = ../../src/shrink_block_v1.c; sourceTree = ""; }; + 0CADC5FB22AAD8EB003E9821 /* expand_context.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = expand_context.h; path = ../../src/expand_context.h; sourceTree = ""; }; + 0CADC60922AAD8EB003E9821 /* divsufsort_private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = divsufsort_private.h; sourceTree = ""; }; + 0CADC60A22AAD8EB003E9821 /* divsufsort.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = divsufsort.h; sourceTree = ""; }; + 0CADC61622AAD8EB003E9821 /* trsort.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = trsort.c; sourceTree = ""; }; + 0CADC61722AAD8EB003E9821 /* divsufsort.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = divsufsort.c; sourceTree = ""; }; + 0CADC61922AAD8EB003E9821 /* sssort.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sssort.c; sourceTree = ""; }; + 0CADC62122AAD8EB003E9821 /* expand_block_v1.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = expand_block_v1.c; path = ../../src/expand_block_v1.c; sourceTree = ""; }; + 0CADC62222AAD8EB003E9821 /* lzsa.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = lzsa.c; path = ../../src/lzsa.c; sourceTree = ""; }; + 0CADC62322AAD8EB003E9821 /* shrink_streaming.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = shrink_streaming.c; path = ../../src/shrink_streaming.c; sourceTree = ""; }; + 0CADC62422AAD8EB003E9821 /* format.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = format.h; path = ../../src/format.h; sourceTree = ""; }; + 0CADC62522AAD8EB003E9821 /* expand_inmem.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = expand_inmem.c; path = ../../src/expand_inmem.c; sourceTree = ""; }; + 0CADC62622AAD8EB003E9821 /* shrink_block_v2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = shrink_block_v2.h; path = ../../src/shrink_block_v2.h; sourceTree = ""; }; + 0CADC62722AAD8EB003E9821 /* expand_inmem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = expand_inmem.h; path = ../../src/expand_inmem.h; sourceTree = ""; }; + 0CADC62822AAD8EB003E9821 /* shrink_streaming.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = shrink_streaming.h; path = ../../src/shrink_streaming.h; sourceTree = ""; }; + 0CADC62922AAD8EB003E9821 /* stream.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = stream.c; path = ../../src/stream.c; sourceTree = ""; }; + 0CADC62A22AAD8EB003E9821 /* expand_block_v2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = expand_block_v2.c; path = ../../src/expand_block_v2.c; sourceTree = ""; }; + 0CADC62B22AAD8EB003E9821 /* shrink_context.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = shrink_context.c; path = ../../src/shrink_context.c; sourceTree = ""; }; + 0CADC62C22AAD8EB003E9821 /* frame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = frame.h; path = ../../src/frame.h; sourceTree = ""; }; + 0CADC62D22AAD8EB003E9821 /* expand_streaming.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = expand_streaming.c; path = ../../src/expand_streaming.c; sourceTree = ""; }; + 0CADC62E22AAD8EB003E9821 /* dictionary.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dictionary.c; path = ../../src/dictionary.c; sourceTree = ""; }; + 0CADC62F22AAD8EB003E9821 /* expand_context.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = expand_context.c; path = ../../src/expand_context.c; sourceTree = ""; }; + 0CADC63022AAD8EB003E9821 /* shrink_block_v2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = shrink_block_v2.c; path = ../../src/shrink_block_v2.c; sourceTree = ""; }; + 0CADC64922AB8DAD003E9821 /* divsufsort_utils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = divsufsort_utils.c; sourceTree = ""; }; + 0CADC64B22AB8DC3003E9821 /* divsufsort_config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = divsufsort_config.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 0CADC57522A65EA4003E9821 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 0CADC56F22A65EA4003E9821 = { + isa = PBXGroup; + children = ( + 0CADC57A22A65EA5003E9821 /* lzsa */, + 0CADC57922A65EA5003E9821 /* Products */, + ); + sourceTree = ""; + }; + 0CADC57922A65EA5003E9821 /* Products */ = { + isa = PBXGroup; + children = ( + 0CADC57822A65EA5003E9821 /* lzsa */, + ); + name = Products; + sourceTree = ""; + }; + 0CADC57A22A65EA5003E9821 /* lzsa */ = { + isa = PBXGroup; + children = ( + 0CADC62E22AAD8EB003E9821 /* dictionary.c */, + 0CADC5F622AAD8EB003E9821 /* dictionary.h */, + 0CADC62122AAD8EB003E9821 /* expand_block_v1.c */, + 0CADC5F022AAD8EB003E9821 /* expand_block_v1.h */, + 0CADC62A22AAD8EB003E9821 /* expand_block_v2.c */, + 0CADC5F922AAD8EB003E9821 /* expand_block_v2.h */, + 0CADC62F22AAD8EB003E9821 /* expand_context.c */, + 0CADC5FB22AAD8EB003E9821 /* expand_context.h */, + 0CADC62522AAD8EB003E9821 /* expand_inmem.c */, + 0CADC62722AAD8EB003E9821 /* expand_inmem.h */, + 0CADC62D22AAD8EB003E9821 /* expand_streaming.c */, + 0CADC5ED22AAD8EA003E9821 /* expand_streaming.h */, + 0CADC62422AAD8EB003E9821 /* format.h */, + 0CADC5F322AAD8EB003E9821 /* frame.c */, + 0CADC62C22AAD8EB003E9821 /* frame.h */, + 0CADC5F222AAD8EB003E9821 /* lib.h */, + 0CADC5FC22AAD8EB003E9821 /* libdivsufsort */, + 0CADC62222AAD8EB003E9821 /* lzsa.c */, + 0CADC5F422AAD8EB003E9821 /* matchfinder.c */, + 0CADC5F522AAD8EB003E9821 /* matchfinder.h */, + 0CADC5FA22AAD8EB003E9821 /* shrink_block_v1.c */, + 0CADC5F122AAD8EB003E9821 /* shrink_block_v1.h */, + 0CADC63022AAD8EB003E9821 /* shrink_block_v2.c */, + 0CADC62622AAD8EB003E9821 /* shrink_block_v2.h */, + 0CADC62B22AAD8EB003E9821 /* shrink_context.c */, + 0CADC5F722AAD8EB003E9821 /* shrink_context.h */, + 0CADC5EE22AAD8EA003E9821 /* shrink_inmem.c */, + 0CADC5F822AAD8EB003E9821 /* shrink_inmem.h */, + 0CADC62322AAD8EB003E9821 /* shrink_streaming.c */, + 0CADC62822AAD8EB003E9821 /* shrink_streaming.h */, + 0CADC62922AAD8EB003E9821 /* stream.c */, + 0CADC5EF22AAD8EB003E9821 /* stream.h */, + ); + path = lzsa; + sourceTree = ""; + }; + 0CADC5FC22AAD8EB003E9821 /* libdivsufsort */ = { + isa = PBXGroup; + children = ( + 0CADC60322AAD8EB003E9821 /* include */, + 0CADC61422AAD8EB003E9821 /* lib */, + ); + name = libdivsufsort; + path = ../../src/libdivsufsort; + sourceTree = ""; + }; + 0CADC60322AAD8EB003E9821 /* include */ = { + isa = PBXGroup; + children = ( + 0CADC64B22AB8DC3003E9821 /* divsufsort_config.h */, + 0CADC60922AAD8EB003E9821 /* divsufsort_private.h */, + 0CADC60A22AAD8EB003E9821 /* divsufsort.h */, + ); + path = include; + sourceTree = ""; + }; + 0CADC61422AAD8EB003E9821 /* lib */ = { + isa = PBXGroup; + children = ( + 0CADC64922AB8DAD003E9821 /* divsufsort_utils.c */, + 0CADC61622AAD8EB003E9821 /* trsort.c */, + 0CADC61722AAD8EB003E9821 /* divsufsort.c */, + 0CADC61922AAD8EB003E9821 /* sssort.c */, + ); + path = lib; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 0CADC57722A65EA4003E9821 /* lzsa */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0CADC57F22A65EA5003E9821 /* Build configuration list for PBXNativeTarget "lzsa" */; + buildPhases = ( + 0CADC57422A65EA4003E9821 /* Sources */, + 0CADC57522A65EA4003E9821 /* Frameworks */, + 0CADC57622A65EA4003E9821 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = lzsa; + productName = lzsa; + productReference = 0CADC57822A65EA5003E9821 /* lzsa */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0CADC57022A65EA4003E9821 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = Emmanuel; + TargetAttributes = { + 0CADC57722A65EA4003E9821 = { + CreatedOnToolsVersion = 10.2.1; + }; + }; + }; + buildConfigurationList = 0CADC57322A65EA4003E9821 /* Build configuration list for PBXProject "lzsa" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 0CADC56F22A65EA4003E9821; + productRefGroup = 0CADC57922A65EA5003E9821 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 0CADC57722A65EA4003E9821 /* lzsa */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 0CADC57422A65EA4003E9821 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0CADC64822AAD8EB003E9821 /* shrink_block_v2.c in Sources */, + 0CADC63D22AAD8EB003E9821 /* sssort.c in Sources */, + 0CADC64322AAD8EB003E9821 /* expand_block_v2.c in Sources */, + 0CADC63F22AAD8EB003E9821 /* lzsa.c in Sources */, + 0CADC64422AAD8EB003E9821 /* shrink_context.c in Sources */, + 0CADC64522AAD8EB003E9821 /* expand_streaming.c in Sources */, + 0CADC63E22AAD8EB003E9821 /* expand_block_v1.c in Sources */, + 0CADC63122AAD8EB003E9821 /* shrink_inmem.c in Sources */, + 0CADC63B22AAD8EB003E9821 /* divsufsort.c in Sources */, + 0CADC64622AAD8EB003E9821 /* dictionary.c in Sources */, + 0CADC63422AAD8EB003E9821 /* shrink_block_v1.c in Sources */, + 0CADC64A22AB8DAD003E9821 /* divsufsort_utils.c in Sources */, + 0CADC64222AAD8EB003E9821 /* stream.c in Sources */, + 0CADC64022AAD8EB003E9821 /* shrink_streaming.c in Sources */, + 0CADC63A22AAD8EB003E9821 /* trsort.c in Sources */, + 0CADC64122AAD8EB003E9821 /* expand_inmem.c in Sources */, + 0CADC63322AAD8EB003E9821 /* matchfinder.c in Sources */, + 0CADC64722AAD8EB003E9821 /* expand_context.c in Sources */, + 0CADC63222AAD8EB003E9821 /* frame.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 0CADC57D22A65EA5003E9821 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + ../src/libdivsufsort/include, + ../src/xxhash, + ../src, + ); + LLVM_LTO = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = ""; + SDKROOT = macosx; + }; + name = Debug; + }; + 0CADC57E22A65EA5003E9821 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + ../src/libdivsufsort/include, + ../src/xxhash, + ../src, + ); + LLVM_LTO = YES; + MACOSX_DEPLOYMENT_TARGET = 10.8; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_CFLAGS = ""; + SDKROOT = macosx; + }; + name = Release; + }; + 0CADC58022A65EA5003E9821 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 0CADC58122A65EA5003E9821 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 0CADC57322A65EA4003E9821 /* Build configuration list for PBXProject "lzsa" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0CADC57D22A65EA5003E9821 /* Debug */, + 0CADC57E22A65EA5003E9821 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 0CADC57F22A65EA5003E9821 /* Build configuration list for PBXNativeTarget "lzsa" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0CADC58022A65EA5003E9821 /* Debug */, + 0CADC58122A65EA5003E9821 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0CADC57022A65EA4003E9821 /* Project object */; +} diff --git a/Tools/unix/lzsa/asm/6502/decompress_fast_v1.asm b/Tools/unix/lzsa/asm/6502/decompress_fast_v1.asm new file mode 100644 index 00000000..b36cc176 --- /dev/null +++ b/Tools/unix/lzsa/asm/6502/decompress_fast_v1.asm @@ -0,0 +1,305 @@ +; ----------------------------------------------------------------------------- +; Decompress raw LZSA1 block. Create one with lzsa -r +; +; in: +; * LZSA_SRC_LO and LZSA_SRC_HI contain the compressed raw block address +; * LZSA_DST_LO and LZSA_DST_HI contain the destination buffer address +; +; out: +; * LZSA_DST_LO and LZSA_DST_HI contain the last decompressed byte address, +1 +; +; ----------------------------------------------------------------------------- +; Backward decompression is also supported, use lzsa -r -b +; To use it, also define BACKWARD_DECOMPRESS=1 before including this code! +; +; in: +; * LZSA_SRC_LO/LZSA_SRC_HI must contain the address of the last byte of compressed data +; * LZSA_DST_LO/LZSA_DST_HI must contain the address of the last byte of the destination buffer +; +; out: +; * LZSA_DST_LO/LZSA_DST_HI contain the last decompressed byte address, -1 +; +; ----------------------------------------------------------------------------- +; +; Copyright (C) 2019 Emmanuel Marty, Peter Ferrie +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. +; ----------------------------------------------------------------------------- + +DECOMPRESS_LZSA1_FAST + LDY #$00 + +DECODE_TOKEN + JSR GETSRC ; read token byte: O|LLL|MMMM + PHA ; preserve token on stack + + AND #$70 ; isolate literals count + BEQ NO_LITERALS ; skip if no literals to copy + CMP #$70 ; LITERALS_RUN_LEN? + BNE PREPARE_COPY_LITERALS ; if not, count is directly embedded in token + + JSR GETSRC ; get extra byte of variable literals count + ; the carry is always set by the CMP above + ; GETSRC doesn't change it + SBC #$F9 ; (LITERALS_RUN_LEN) + BCC PREPARE_COPY_LITERALS_DIRECT + BEQ LARGE_VARLEN_LITERALS ; if adding up to zero, go grab 16-bit count + + JSR GETSRC ; get single extended byte of variable literals count + INY ; add 256 to literals count + BCS PREPARE_COPY_LITERALS_DIRECT ; (*like JMP PREPARE_COPY_LITERALS_DIRECT but shorter) + +LARGE_VARLEN_LITERALS ; handle 16 bits literals count + ; literals count = directly these 16 bits + JSR GETLARGESRC ; grab low 8 bits in X, high 8 bits in A + TAY ; put high 8 bits in Y + TXA + JMP PREPARE_COPY_LARGE_LITERALS + +PREPARE_COPY_LITERALS + TAX + LDA SHIFT_TABLE-1,X ; shift literals length into place + ; -1 because position 00 is reserved +PREPARE_COPY_LITERALS_DIRECT + TAX + +PREPARE_COPY_LARGE_LITERALS + BEQ COPY_LITERALS + INY + +COPY_LITERALS + JSR GETPUT ; copy one byte of literals + DEX + BNE COPY_LITERALS + DEY + BNE COPY_LITERALS + +NO_LITERALS + PLA ; retrieve token from stack + PHA ; preserve token again + BMI GET_LONG_OFFSET ; $80: 16 bit offset + + JSR GETSRC ; get 8 bit offset from stream in A + TAX ; save for later + LDA #$FF ; high 8 bits + BNE GOT_OFFSET ; go prepare match + ; (*like JMP GOT_OFFSET but shorter) + +SHORT_VARLEN_MATCHLEN + JSR GETSRC ; get single extended byte of variable match len + INY ; add 256 to match length + +PREPARE_COPY_MATCH + TAX +PREPARE_COPY_MATCH_Y + TXA + BEQ COPY_MATCH_LOOP + INY + +COPY_MATCH_LOOP + LDA $AAAA ; get one byte of backreference + JSR PUTDST ; copy to destination + +!ifdef BACKWARD_DECOMPRESS { + + ; Backward decompression -- put backreference bytes backward + + LDA COPY_MATCH_LOOP+1 + BEQ GETMATCH_ADJ_HI +GETMATCH_DONE + DEC COPY_MATCH_LOOP+1 + +} else { + + ; Forward decompression -- put backreference bytes forward + + INC COPY_MATCH_LOOP+1 + BEQ GETMATCH_ADJ_HI +GETMATCH_DONE + +} + + DEX + BNE COPY_MATCH_LOOP + DEY + BNE COPY_MATCH_LOOP + BEQ DECODE_TOKEN ; (*like JMP DECODE_TOKEN but shorter) + +!ifdef BACKWARD_DECOMPRESS { + +GETMATCH_ADJ_HI + DEC COPY_MATCH_LOOP+2 + JMP GETMATCH_DONE + +} else { + +GETMATCH_ADJ_HI + INC COPY_MATCH_LOOP+2 + JMP GETMATCH_DONE + +} + +GET_LONG_OFFSET ; handle 16 bit offset: + JSR GETLARGESRC ; grab low 8 bits in X, high 8 bits in A + +GOT_OFFSET + +!ifdef BACKWARD_DECOMPRESS { + + ; Backward decompression - substract match offset + + STA OFFSHI ; store high 8 bits of offset + STX OFFSLO + + SEC ; substract dest - match offset + LDA PUTDST+1 +OFFSLO = *+1 + SBC #$AA ; low 8 bits + STA COPY_MATCH_LOOP+1 ; store back reference address + LDA PUTDST+2 +OFFSHI = *+1 + SBC #$AA ; high 8 bits + STA COPY_MATCH_LOOP+2 ; store high 8 bits of address + SEC + +} else { + + ; Forward decompression - add match offset + + STA OFFSHI ; store high 8 bits of offset + TXA + + CLC ; add dest + match offset + ADC PUTDST+1 ; low 8 bits + STA COPY_MATCH_LOOP+1 ; store back reference address +OFFSHI = *+1 + LDA #$AA ; high 8 bits + + ADC PUTDST+2 + STA COPY_MATCH_LOOP+2 ; store high 8 bits of address + +} + + PLA ; retrieve token from stack again + AND #$0F ; isolate match len (MMMM) + ADC #$02 ; plus carry which is always set by the high ADC + CMP #$12 ; MATCH_RUN_LEN? + BCC PREPARE_COPY_MATCH ; if not, count is directly embedded in token + + JSR GETSRC ; get extra byte of variable match length + ; the carry is always set by the CMP above + ; GETSRC doesn't change it + SBC #$EE ; add MATCH_RUN_LEN and MIN_MATCH_SIZE to match length + BCC PREPARE_COPY_MATCH + BNE SHORT_VARLEN_MATCHLEN + + ; Handle 16 bits match length + JSR GETLARGESRC ; grab low 8 bits in X, high 8 bits in A + TAY ; put high 8 bits in Y + ; large match length with zero high byte? + BNE PREPARE_COPY_MATCH_Y ; if not, continue + +DECOMPRESSION_DONE + RTS + +SHIFT_TABLE + !BYTE $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 + !BYTE $01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01,$01 + !BYTE $02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02,$02 + !BYTE $03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03,$03 + !BYTE $04,$04,$04,$04,$04,$04,$04,$04,$04,$04,$04,$04,$04,$04,$04,$04 + !BYTE $05,$05,$05,$05,$05,$05,$05,$05,$05,$05,$05,$05,$05,$05,$05,$05 + !BYTE $06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06,$06 + !BYTE $07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07,$07 + +!ifdef BACKWARD_DECOMPRESS { + + ; Backward decompression -- get and put bytes backward + +GETPUT + JSR GETSRC +PUTDST +LZSA_DST_LO = *+1 +LZSA_DST_HI = *+2 + STA $AAAA + LDA PUTDST+1 + BEQ PUTDST_ADJ_HI + DEC PUTDST+1 + RTS + +PUTDST_ADJ_HI + DEC PUTDST+2 + DEC PUTDST+1 + RTS + +GETLARGESRC + JSR GETSRC ; grab low 8 bits + TAX ; move to X + ; fall through grab high 8 bits + +GETSRC +LZSA_SRC_LO = *+1 +LZSA_SRC_HI = *+2 + LDA $AAAA + PHA + LDA GETSRC+1 + BEQ GETSRC_ADJ_HI + DEC GETSRC+1 + PLA + RTS + +GETSRC_ADJ_HI + DEC GETSRC+2 + DEC GETSRC+1 + PLA + RTS + +} else { + + ; Forward decompression -- get and put bytes forward + +GETPUT + JSR GETSRC +PUTDST +LZSA_DST_LO = *+1 +LZSA_DST_HI = *+2 + STA $AAAA + INC PUTDST+1 + BEQ PUTDST_ADJ_HI + RTS + +PUTDST_ADJ_HI + INC PUTDST+2 + RTS + +GETLARGESRC + JSR GETSRC ; grab low 8 bits + TAX ; move to X + ; fall through grab high 8 bits + +GETSRC +LZSA_SRC_LO = *+1 +LZSA_SRC_HI = *+2 + LDA $AAAA + INC GETSRC+1 + BEQ GETSRC_ADJ_HI + RTS + +GETSRC_ADJ_HI + INC GETSRC+2 + RTS +} diff --git a/Tools/unix/lzsa/asm/6502/decompress_fast_v2.asm b/Tools/unix/lzsa/asm/6502/decompress_fast_v2.asm new file mode 100644 index 00000000..681d42d9 --- /dev/null +++ b/Tools/unix/lzsa/asm/6502/decompress_fast_v2.asm @@ -0,0 +1,363 @@ +; ----------------------------------------------------------------------------- +; Decompress raw LZSA2 block. +; Create one with lzsa -r -f2 +; +; in: +; * LZSA_SRC_LO and LZSA_SRC_HI contain the compressed raw block address +; * LZSA_DST_LO and LZSA_DST_HI contain the destination buffer address +; +; out: +; * LZSA_DST_LO and LZSA_DST_HI contain the last decompressed byte address, +1 +; +; ----------------------------------------------------------------------------- +; Backward decompression is also supported, use lzsa -r -b -f2 +; To use it, also define BACKWARD_DECOMPRESS=1 before including this code! +; +; in: +; * LZSA_SRC_LO/LZSA_SRC_HI must contain the address of the last byte of compressed data +; * LZSA_DST_LO/LZSA_DST_HI must contain the address of the last byte of the destination buffer +; +; out: +; * LZSA_DST_LO/LZSA_DST_HI contain the last decompressed byte address, -1 +; +; ----------------------------------------------------------------------------- +; +; Copyright (C) 2019 Emmanuel Marty, Peter Ferrie +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. +; ----------------------------------------------------------------------------- + +NIBCOUNT = $FC ; zero-page location for temp offset + +DECOMPRESS_LZSA2_FAST + LDY #$00 + STY NIBCOUNT + +DECODE_TOKEN + JSR GETSRC ; read token byte: XYZ|LL|MMM + PHA ; preserve token on stack + + AND #$18 ; isolate literals count (LL) + BEQ NO_LITERALS ; skip if no literals to copy + CMP #$18 ; LITERALS_RUN_LEN_V2? + BCC PREPARE_COPY_LITERALS ; if less, count is directly embedded in token + + JSR GETNIBBLE ; get extra literals length nibble + ; add nibble to len from token + ADC #$02 ; (LITERALS_RUN_LEN_V2) minus carry + CMP #$12 ; LITERALS_RUN_LEN_V2 + 15 ? + BCC PREPARE_COPY_LITERALS_DIRECT ; if less, literals count is complete + + JSR GETSRC ; get extra byte of variable literals count + ; the carry is always set by the CMP above + ; GETSRC doesn't change it + SBC #$EE ; overflow? + JMP PREPARE_COPY_LITERALS_DIRECT + +PREPARE_COPY_LITERALS_LARGE + ; handle 16 bits literals count + ; literals count = directly these 16 bits + JSR GETLARGESRC ; grab low 8 bits in X, high 8 bits in A + TAY ; put high 8 bits in Y + BCS PREPARE_COPY_LITERALS_HIGH ; (*same as JMP PREPARE_COPY_LITERALS_HIGH but shorter) + +PREPARE_COPY_LITERALS + LSR ; shift literals count into place + LSR + LSR + +PREPARE_COPY_LITERALS_DIRECT + TAX + BCS PREPARE_COPY_LITERALS_LARGE ; if so, literals count is large + +PREPARE_COPY_LITERALS_HIGH + TXA + BEQ COPY_LITERALS + INY + +COPY_LITERALS + JSR GETPUT ; copy one byte of literals + DEX + BNE COPY_LITERALS + DEY + BNE COPY_LITERALS + +NO_LITERALS + PLA ; retrieve token from stack + PHA ; preserve token again + ASL + BCS REPMATCH_OR_LARGE_OFFSET ; 1YZ: rep-match or 13/16 bit offset + + ASL ; 0YZ: 5 or 9 bit offset + BCS OFFSET_9_BIT + + ; 00Z: 5 bit offset + + LDX #$FF ; set offset bits 15-8 to 1 + + JSR GETCOMBINEDBITS ; rotate Z bit into bit 0, read nibble for bits 4-1 + ORA #$E0 ; set bits 7-5 to 1 + BNE GOT_OFFSET_LO ; go store low byte of match offset and prepare match + +OFFSET_9_BIT ; 01Z: 9 bit offset + ;;ASL ; shift Z (offset bit 8) in place + ROL + ROL + AND #$01 + EOR #$FF ; set offset bits 15-9 to 1 + BNE GOT_OFFSET_HI ; go store high byte, read low byte of match offset and prepare match + ; (*same as JMP GOT_OFFSET_HI but shorter) + +REPMATCH_OR_LARGE_OFFSET + ASL ; 13 bit offset? + BCS REPMATCH_OR_16_BIT ; handle rep-match or 16-bit offset if not + + ; 10Z: 13 bit offset + + JSR GETCOMBINEDBITS ; rotate Z bit into bit 8, read nibble for bits 12-9 + ADC #$DE ; set bits 15-13 to 1 and substract 2 (to substract 512) + BNE GOT_OFFSET_HI ; go store high byte, read low byte of match offset and prepare match + ; (*same as JMP GOT_OFFSET_HI but shorter) + +REPMATCH_OR_16_BIT ; rep-match or 16 bit offset + ;;ASL ; XYZ=111? + BMI REP_MATCH ; reuse previous offset if so (rep-match) + + ; 110: handle 16 bit offset + JSR GETSRC ; grab high 8 bits +GOT_OFFSET_HI + TAX + JSR GETSRC ; grab low 8 bits +GOT_OFFSET_LO + STA OFFSLO ; store low byte of match offset + STX OFFSHI ; store high byte of match offset + +REP_MATCH +!ifdef BACKWARD_DECOMPRESS { + + ; Backward decompression - substract match offset + + SEC ; add dest + match offset + LDA PUTDST+1 ; low 8 bits +OFFSLO = *+1 + SBC #$AA + STA COPY_MATCH_LOOP+1 ; store back reference address + LDA PUTDST+2 +OFFSHI = *+1 + SBC #$AA ; high 8 bits + STA COPY_MATCH_LOOP+2 ; store high 8 bits of address + SEC + +} else { + + ; Forward decompression - add match offset + + CLC ; add dest + match offset + LDA PUTDST+1 ; low 8 bits +OFFSLO = *+1 + ADC #$AA + STA COPY_MATCH_LOOP+1 ; store back reference address +OFFSHI = *+1 + LDA #$AA ; high 8 bits + ADC PUTDST+2 + STA COPY_MATCH_LOOP+2 ; store high 8 bits of address + +} + + PLA ; retrieve token from stack again + AND #$07 ; isolate match len (MMM) + ADC #$01 ; add MIN_MATCH_SIZE_V2 and carry + CMP #$09 ; MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2? + BCC PREPARE_COPY_MATCH ; if less, length is directly embedded in token + + JSR GETNIBBLE ; get extra match length nibble + ; add nibble to len from token + ADC #$08 ; (MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2) minus carry + CMP #$18 ; MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2 + 15? + BCC PREPARE_COPY_MATCH ; if less, match length is complete + + JSR GETSRC ; get extra byte of variable match length + ; the carry is always set by the CMP above + ; GETSRC doesn't change it + SBC #$E8 ; overflow? + +PREPARE_COPY_MATCH + TAX + BCC PREPARE_COPY_MATCH_Y ; if not, the match length is complete + BEQ DECOMPRESSION_DONE ; if EOD code, bail + + ; Handle 16 bits match length + JSR GETLARGESRC ; grab low 8 bits in X, high 8 bits in A + TAY ; put high 8 bits in Y + +PREPARE_COPY_MATCH_Y + TXA + BEQ COPY_MATCH_LOOP + INY + +COPY_MATCH_LOOP + LDA $AAAA ; get one byte of backreference + JSR PUTDST ; copy to destination + +!ifdef BACKWARD_DECOMPRESS { + + ; Backward decompression -- put backreference bytes backward + + LDA COPY_MATCH_LOOP+1 + BEQ GETMATCH_ADJ_HI +GETMATCH_DONE + DEC COPY_MATCH_LOOP+1 + +} else { + + ; Forward decompression -- put backreference bytes forward + + INC COPY_MATCH_LOOP+1 + BEQ GETMATCH_ADJ_HI +GETMATCH_DONE + +} + + DEX + BNE COPY_MATCH_LOOP + DEY + BNE COPY_MATCH_LOOP + JMP DECODE_TOKEN + +!ifdef BACKWARD_DECOMPRESS { + +GETMATCH_ADJ_HI + DEC COPY_MATCH_LOOP+2 + JMP GETMATCH_DONE + +} else { + +GETMATCH_ADJ_HI + INC COPY_MATCH_LOOP+2 + JMP GETMATCH_DONE + +} + +GETCOMBINEDBITS + EOR #$80 + ASL + PHP + + JSR GETNIBBLE ; get nibble into bits 0-3 (for offset bits 1-4) + PLP ; merge Z bit as the carry bit (for offset bit 0) +COMBINEDBITZ + ROL ; nibble -> bits 1-4; carry(!Z bit) -> bit 0 ; carry cleared +DECOMPRESSION_DONE + RTS + +GETNIBBLE +NIBBLES = *+1 + LDA #$AA + LSR NIBCOUNT + BCC NEED_NIBBLES + AND #$0F ; isolate low 4 bits of nibble + RTS + +NEED_NIBBLES + INC NIBCOUNT + JSR GETSRC ; get 2 nibbles + STA NIBBLES + LSR + LSR + LSR + LSR + SEC + RTS + +!ifdef BACKWARD_DECOMPRESS { + + ; Backward decompression -- get and put bytes backward + +GETPUT + JSR GETSRC +PUTDST +LZSA_DST_LO = *+1 +LZSA_DST_HI = *+2 + STA $AAAA + LDA PUTDST+1 + BEQ PUTDST_ADJ_HI + DEC PUTDST+1 + RTS + +PUTDST_ADJ_HI + DEC PUTDST+2 + DEC PUTDST+1 + RTS + +GETLARGESRC + JSR GETSRC ; grab low 8 bits + TAX ; move to X + ; fall through grab high 8 bits + +GETSRC +LZSA_SRC_LO = *+1 +LZSA_SRC_HI = *+2 + LDA $AAAA + PHA + LDA GETSRC+1 + BEQ GETSRC_ADJ_HI + DEC GETSRC+1 + PLA + RTS + +GETSRC_ADJ_HI + DEC GETSRC+2 + DEC GETSRC+1 + PLA + RTS + +} else { + + ; Forward decompression -- get and put bytes forward + +GETPUT + JSR GETSRC +PUTDST +LZSA_DST_LO = *+1 +LZSA_DST_HI = *+2 + STA $AAAA + INC PUTDST+1 + BEQ PUTDST_ADJ_HI + RTS + +PUTDST_ADJ_HI + INC PUTDST+2 + RTS + +GETLARGESRC + JSR GETSRC ; grab low 8 bits + TAX ; move to X + ; fall through grab high 8 bits + +GETSRC +LZSA_SRC_LO = *+1 +LZSA_SRC_HI = *+2 + LDA $AAAA + INC GETSRC+1 + BEQ GETSRC_ADJ_HI + RTS + +GETSRC_ADJ_HI + INC GETSRC+2 + RTS +} + diff --git a/Tools/unix/lzsa/asm/6502/decompress_faster_v1.asm b/Tools/unix/lzsa/asm/6502/decompress_faster_v1.asm new file mode 100644 index 00000000..93619070 --- /dev/null +++ b/Tools/unix/lzsa/asm/6502/decompress_faster_v1.asm @@ -0,0 +1,392 @@ +; *************************************************************************** +; *************************************************************************** +; +; lzsa1_6502.s +; +; NMOS 6502 decompressor for data stored in Emmanuel Marty's LZSA1 format. +; +; This code is written for the ACME assembler. +; +; Optional code is presented for one minor 6502 optimization that breaks +; compatibility with the current LZSA1 format standard. +; +; The code is 168 bytes for the small version, and 205 bytes for the normal. +; +; Copyright John Brandwood 2019. +; +; Distributed under the Boost Software License, Version 1.0. +; (See accompanying file LICENSE_1_0.txt or copy at +; http://www.boost.org/LICENSE_1_0.txt) +; +; *************************************************************************** +; *************************************************************************** + + + +; *************************************************************************** +; *************************************************************************** +; +; Decompression Options & Macros +; + + ; + ; Save 6 bytes of code and 21 cycles by swapping the order + ; of bytes in the 16-bit length encoding? + ; + ; N.B. Setting this breaks compatibility with LZSA v1.2 + ; + +LZSA_SWAP_LEN16 = 0 + + ; + ; Choose size over space (within sane limits)? + ; + +LZSA_SMALL_SIZE = 0 + + ; + ; Remove code inlining to save space? + ; + ; This saves 15 bytes of code at the cost of 7% speed. + ; + + !if LZSA_SMALL_SIZE { +LZSA_NO_INLINE = 1 + } else { +LZSA_NO_INLINE = 0 + } + + ; + ; Use smaller code for copying literals? + ; + ; This saves 11 bytes of code at the cost of 15% speed. + ; + + !if LZSA_SMALL_SIZE { +LZSA_SHORT_CP = 1 + } else { +LZSA_SHORT_CP = 0 + } + + ; + ; Use smaller code for copying literals? + ; + ; This saves 11 bytes of code at the cost of 30% speed. + ; + + !if LZSA_SMALL_SIZE { +LZSA_SHORT_LZ = 1 + } else { +LZSA_SHORT_LZ = 0 + } + + ; + ; Assume that we're decompessing from a large multi-bank + ; compressed data file, and that the next bank may need to + ; paged in when a page-boundary is crossed. + ; + +LZSA_FROM_BANK = 0 + + ; + ; Macro to increment the source pointer to the next page. + ; + ; This should call a subroutine to determine if a bank + ; has been crossed, and a new bank should be paged in. + ; + + !if LZSA_FROM_BANK { + !macro LZSA_INC_PAGE { + jsr lzsa1_next_page + } + } else { + !macro LZSA_INC_PAGE { + inc +; +; in: +; * LZSA_SRC_LO and LZSA_SRC_HI contain the compressed raw block address +; * LZSA_DST_LO and LZSA_DST_HI contain the destination buffer address +; +; out: +; * LZSA_DST_LO and LZSA_DST_HI contain the last decompressed byte address, +1 +; +; ----------------------------------------------------------------------------- +; Backward decompression is also supported, use lzsa -r -b +; To use it, also define BACKWARD_DECOMPRESS=1 before including this code! +; +; in: +; * LZSA_SRC_LO/LZSA_SRC_HI must contain the address of the last byte of compressed data +; * LZSA_DST_LO/LZSA_DST_HI must contain the address of the last byte of the destination buffer +; +; out: +; * LZSA_DST_LO/LZSA_DST_HI contain the last decompressed byte address, -1 +; +; ----------------------------------------------------------------------------- +; +; Copyright (C) 2019 Emmanuel Marty +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. +; ----------------------------------------------------------------------------- + +DECOMPRESS_LZSA1 + LDY #$00 + +DECODE_TOKEN + JSR GETSRC ; read token byte: O|LLL|MMMM + PHA ; preserve token on stack + + AND #$70 ; isolate literals count + BEQ NO_LITERALS ; skip if no literals to copy + LSR ; shift literals count into place + LSR + LSR + LSR + CMP #$07 ; LITERALS_RUN_LEN? + BCC PREPARE_COPY_LITERALS ; if not, count is directly embedded in token + + JSR GETSRC ; get extra byte of variable literals count + ; the carry is always set by the CMP above + ; GETSRC doesn't change it + SBC #$F9 ; (LITERALS_RUN_LEN) + BCC PREPARE_COPY_LITERALS + BEQ LARGE_VARLEN_LITERALS ; if adding up to zero, go grab 16-bit count + + JSR GETSRC ; get single extended byte of variable literals count + INY ; add 256 to literals count + BCS PREPARE_COPY_LITERALS ; (*like JMP PREPARE_COPY_LITERALS but shorter) + +LARGE_VARLEN_LITERALS ; handle 16 bits literals count + ; literals count = directly these 16 bits + JSR GETLARGESRC ; grab low 8 bits in X, high 8 bits in A + TAY ; put high 8 bits in Y + TXA + +PREPARE_COPY_LITERALS + TAX + BEQ COPY_LITERALS + INY + +COPY_LITERALS + JSR GETPUT ; copy one byte of literals + DEX + BNE COPY_LITERALS + DEY + BNE COPY_LITERALS + +NO_LITERALS + PLA ; retrieve token from stack + PHA ; preserve token again + BMI GET_LONG_OFFSET ; $80: 16 bit offset + + JSR GETSRC ; get 8 bit offset from stream in A + TAX ; save for later + LDA #$FF ; high 8 bits + BNE GOT_OFFSET ; go prepare match + ; (*like JMP GOT_OFFSET but shorter) + +SHORT_VARLEN_MATCHLEN + JSR GETSRC ; get single extended byte of variable match len + INY ; add 256 to match length + +PREPARE_COPY_MATCH + TAX +PREPARE_COPY_MATCH_Y + TXA + BEQ COPY_MATCH_LOOP + INY + +COPY_MATCH_LOOP + LDA $AAAA ; get one byte of backreference + JSR PUTDST ; copy to destination + +!ifdef BACKWARD_DECOMPRESS { + + ; Backward decompression -- put backreference bytes backward + + LDA COPY_MATCH_LOOP+1 + BNE GETMATCH_DONE + DEC COPY_MATCH_LOOP+2 +GETMATCH_DONE + DEC COPY_MATCH_LOOP+1 + +} else { + + ; Forward decompression -- put backreference bytes forward + + INC COPY_MATCH_LOOP+1 + BNE GETMATCH_DONE + INC COPY_MATCH_LOOP+2 +GETMATCH_DONE + +} + + DEX + BNE COPY_MATCH_LOOP + DEY + BNE COPY_MATCH_LOOP + BEQ DECODE_TOKEN ; (*like JMP DECODE_TOKEN but shorter) + +GET_LONG_OFFSET ; handle 16 bit offset: + JSR GETLARGESRC ; grab low 8 bits in X, high 8 bits in A + +GOT_OFFSET + +!ifdef BACKWARD_DECOMPRESS { + + ; Backward decompression - substract match offset + + STA OFFSHI ; store high 8 bits of offset + STX OFFSLO + + SEC ; substract dest - match offset + LDA PUTDST+1 +OFFSLO = *+1 + SBC #$AA ; low 8 bits + STA COPY_MATCH_LOOP+1 ; store back reference address + LDA PUTDST+2 +OFFSHI = *+1 + SBC #$AA ; high 8 bits + STA COPY_MATCH_LOOP+2 ; store high 8 bits of address + SEC + +} else { + + ; Forward decompression - add match offset + + STA OFFSHI ; store high 8 bits of offset + TXA + + CLC ; add dest + match offset + ADC PUTDST+1 ; low 8 bits + STA COPY_MATCH_LOOP+1 ; store back reference address +OFFSHI = *+1 + LDA #$AA ; high 8 bits + + ADC PUTDST+2 + STA COPY_MATCH_LOOP+2 ; store high 8 bits of address + +} + + PLA ; retrieve token from stack again + AND #$0F ; isolate match len (MMMM) + ADC #$02 ; plus carry which is always set by the high ADC + CMP #$12 ; MATCH_RUN_LEN? + BCC PREPARE_COPY_MATCH ; if not, count is directly embedded in token + + JSR GETSRC ; get extra byte of variable match length + ; the carry is always set by the CMP above + ; GETSRC doesn't change it + SBC #$EE ; add MATCH_RUN_LEN and MIN_MATCH_SIZE to match length + BCC PREPARE_COPY_MATCH + BNE SHORT_VARLEN_MATCHLEN + + ; Handle 16 bits match length + JSR GETLARGESRC ; grab low 8 bits in X, high 8 bits in A + TAY ; put high 8 bits in Y + ; large match length with zero high byte? + BNE PREPARE_COPY_MATCH_Y ; if not, continue + +DECOMPRESSION_DONE + RTS + +!ifdef BACKWARD_DECOMPRESS { + + ; Backward decompression -- get and put bytes backward + +GETPUT + JSR GETSRC +PUTDST +LZSA_DST_LO = *+1 +LZSA_DST_HI = *+2 + STA $AAAA + LDA PUTDST+1 + BNE PUTDST_DONE + DEC PUTDST+2 +PUTDST_DONE + DEC PUTDST+1 + RTS + +GETLARGESRC + JSR GETSRC ; grab low 8 bits + TAX ; move to X + ; fall through grab high 8 bits + +GETSRC +LZSA_SRC_LO = *+1 +LZSA_SRC_HI = *+2 + LDA $AAAA + PHA + LDA GETSRC+1 + BNE GETSRC_DONE + DEC GETSRC+2 +GETSRC_DONE + DEC GETSRC+1 + PLA + RTS + +} else { + + ; Forward decompression -- get and put bytes forward + +GETPUT + JSR GETSRC +PUTDST +LZSA_DST_LO = *+1 +LZSA_DST_HI = *+2 + STA $AAAA + INC PUTDST+1 + BNE PUTDST_DONE + INC PUTDST+2 +PUTDST_DONE + RTS + +GETLARGESRC + JSR GETSRC ; grab low 8 bits + TAX ; move to X + ; fall through grab high 8 bits + +GETSRC +LZSA_SRC_LO = *+1 +LZSA_SRC_HI = *+2 + LDA $AAAA + INC GETSRC+1 + BNE GETSRC_DONE + INC GETSRC+2 +GETSRC_DONE + RTS + +} diff --git a/Tools/unix/lzsa/asm/6502/decompress_small_v2.asm b/Tools/unix/lzsa/asm/6502/decompress_small_v2.asm new file mode 100644 index 00000000..6daa1bdb --- /dev/null +++ b/Tools/unix/lzsa/asm/6502/decompress_small_v2.asm @@ -0,0 +1,336 @@ +; ----------------------------------------------------------------------------- +; Decompress raw LZSA2 block. +; Create one with lzsa -r -f2 +; +; in: +; * LZSA_SRC_LO and LZSA_SRC_HI contain the compressed raw block address +; * LZSA_DST_LO and LZSA_DST_HI contain the destination buffer address +; +; out: +; * LZSA_DST_LO and LZSA_DST_HI contain the last decompressed byte address, +1 +; +; ----------------------------------------------------------------------------- +; Backward decompression is also supported, use lzsa -r -b -f2 +; To use it, also define BACKWARD_DECOMPRESS=1 before including this code! +; +; in: +; * LZSA_SRC_LO/LZSA_SRC_HI must contain the address of the last byte of compressed data +; * LZSA_DST_LO/LZSA_DST_HI must contain the address of the last byte of the destination buffer +; +; out: +; * LZSA_DST_LO/LZSA_DST_HI contain the last decompressed byte address, -1 +; +; ----------------------------------------------------------------------------- +; +; Copyright (C) 2019 Emmanuel Marty +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. +; ----------------------------------------------------------------------------- + +NIBCOUNT = $FC ; zero-page location for temp offset + +DECOMPRESS_LZSA2 + LDY #$00 + STY NIBCOUNT + +DECODE_TOKEN + JSR GETSRC ; read token byte: XYZ|LL|MMM + PHA ; preserve token on stack + + AND #$18 ; isolate literals count (LL) + BEQ NO_LITERALS ; skip if no literals to copy + LSR ; shift literals count into place + LSR + LSR + CMP #$03 ; LITERALS_RUN_LEN_V2? + BCC PREPARE_COPY_LITERALS ; if less, count is directly embedded in token + + JSR GETNIBBLE ; get extra literals length nibble + ; add nibble to len from token + ADC #$02 ; (LITERALS_RUN_LEN_V2) minus carry + CMP #$12 ; LITERALS_RUN_LEN_V2 + 15 ? + BCC PREPARE_COPY_LITERALS ; if less, literals count is complete + + JSR GETSRC ; get extra byte of variable literals count + ; the carry is always set by the CMP above + ; GETSRC doesn't change it + SBC #$EE ; overflow? + +PREPARE_COPY_LITERALS + TAX + BCC PREPARE_COPY_LITERALS_HIGH ; if not, literals count is complete + + ; handle 16 bits literals count + ; literals count = directly these 16 bits + JSR GETLARGESRC ; grab low 8 bits in X, high 8 bits in A + TAY ; put high 8 bits in Y + +PREPARE_COPY_LITERALS_HIGH + TXA + BEQ COPY_LITERALS + INY + +COPY_LITERALS + JSR GETPUT ; copy one byte of literals + DEX + BNE COPY_LITERALS + DEY + BNE COPY_LITERALS + +NO_LITERALS + PLA ; retrieve token from stack + PHA ; preserve token again + ASL + BCS REPMATCH_OR_LARGE_OFFSET ; 1YZ: rep-match or 13/16 bit offset + + ASL ; 0YZ: 5 or 9 bit offset + BCS OFFSET_9_BIT + + ; 00Z: 5 bit offset + + LDX #$FF ; set offset bits 15-8 to 1 + + JSR GETCOMBINEDBITS ; rotate Z bit into bit 0, read nibble for bits 4-1 + ORA #$E0 ; set bits 7-5 to 1 + BNE GOT_OFFSET_LO ; go store low byte of match offset and prepare match + +OFFSET_9_BIT ; 01Z: 9 bit offset + ;;ASL ; shift Z (offset bit 8) in place + ROL + ROL + AND #$01 + EOR #$FF ; set offset bits 15-9 to 1 + BNE GOT_OFFSET_HI ; go store high byte, read low byte of match offset and prepare match + ; (*same as JMP GOT_OFFSET_HI but shorter) + +REPMATCH_OR_LARGE_OFFSET + ASL ; 13 bit offset? + BCS REPMATCH_OR_16_BIT ; handle rep-match or 16-bit offset if not + + ; 10Z: 13 bit offset + + JSR GETCOMBINEDBITS ; rotate Z bit into bit 8, read nibble for bits 12-9 + ADC #$DE ; set bits 15-13 to 1 and substract 2 (to substract 512) + BNE GOT_OFFSET_HI ; go store high byte, read low byte of match offset and prepare match + ; (*same as JMP GOT_OFFSET_HI but shorter) + +REPMATCH_OR_16_BIT ; rep-match or 16 bit offset + ;;ASL ; XYZ=111? + BMI REP_MATCH ; reuse previous offset if so (rep-match) + + ; 110: handle 16 bit offset + JSR GETSRC ; grab high 8 bits +GOT_OFFSET_HI + TAX + JSR GETSRC ; grab low 8 bits +GOT_OFFSET_LO + STA OFFSLO ; store low byte of match offset + STX OFFSHI ; store high byte of match offset + +REP_MATCH +!ifdef BACKWARD_DECOMPRESS { + + ; Backward decompression - substract match offset + + SEC ; add dest + match offset + LDA PUTDST+1 ; low 8 bits +OFFSLO = *+1 + SBC #$AA + STA COPY_MATCH_LOOP+1 ; store back reference address + LDA PUTDST+2 +OFFSHI = *+1 + SBC #$AA ; high 8 bits + STA COPY_MATCH_LOOP+2 ; store high 8 bits of address + SEC + +} else { + + ; Forward decompression - add match offset + + CLC ; add dest + match offset + LDA PUTDST+1 ; low 8 bits +OFFSLO = *+1 + ADC #$AA + STA COPY_MATCH_LOOP+1 ; store back reference address +OFFSHI = *+1 + LDA #$AA ; high 8 bits + ADC PUTDST+2 + STA COPY_MATCH_LOOP+2 ; store high 8 bits of address + +} + + PLA ; retrieve token from stack again + AND #$07 ; isolate match len (MMM) + ADC #$01 ; add MIN_MATCH_SIZE_V2 and carry + CMP #$09 ; MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2? + BCC PREPARE_COPY_MATCH ; if less, length is directly embedded in token + + JSR GETNIBBLE ; get extra match length nibble + ; add nibble to len from token + ADC #$08 ; (MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2) minus carry + CMP #$18 ; MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2 + 15? + BCC PREPARE_COPY_MATCH ; if less, match length is complete + + JSR GETSRC ; get extra byte of variable match length + ; the carry is always set by the CMP above + ; GETSRC doesn't change it + SBC #$E8 ; overflow? + +PREPARE_COPY_MATCH + TAX + BCC PREPARE_COPY_MATCH_Y ; if not, the match length is complete + BEQ DECOMPRESSION_DONE ; if EOD code, bail + + ; Handle 16 bits match length + JSR GETLARGESRC ; grab low 8 bits in X, high 8 bits in A + TAY ; put high 8 bits in Y + +PREPARE_COPY_MATCH_Y + TXA + BEQ COPY_MATCH_LOOP + INY + +COPY_MATCH_LOOP + LDA $AAAA ; get one byte of backreference + JSR PUTDST ; copy to destination + +!ifdef BACKWARD_DECOMPRESS { + + ; Backward decompression -- put backreference bytes backward + + LDA COPY_MATCH_LOOP+1 + BNE GETMATCH_DONE + DEC COPY_MATCH_LOOP+2 +GETMATCH_DONE + DEC COPY_MATCH_LOOP+1 + +} else { + + ; Forward decompression -- put backreference bytes forward + + INC COPY_MATCH_LOOP+1 + BNE GETMATCH_DONE + INC COPY_MATCH_LOOP+2 +GETMATCH_DONE + +} + + DEX + BNE COPY_MATCH_LOOP + DEY + BNE COPY_MATCH_LOOP + JMP DECODE_TOKEN + +GETCOMBINEDBITS + EOR #$80 + ASL + PHP + + JSR GETNIBBLE ; get nibble into bits 0-3 (for offset bits 1-4) + PLP ; merge Z bit as the carry bit (for offset bit 0) +COMBINEDBITZ + ROL ; nibble -> bits 1-4; carry(!Z bit) -> bit 0 ; carry cleared +DECOMPRESSION_DONE + RTS + +GETNIBBLE +NIBBLES = *+1 + LDA #$AA + LSR NIBCOUNT + BCS HAS_NIBBLES + + INC NIBCOUNT + JSR GETSRC ; get 2 nibbles + STA NIBBLES + LSR + LSR + LSR + LSR + SEC + +HAS_NIBBLES + AND #$0F ; isolate low 4 bits of nibble + RTS + +!ifdef BACKWARD_DECOMPRESS { + + ; Backward decompression -- get and put bytes backward + +GETPUT + JSR GETSRC +PUTDST +LZSA_DST_LO = *+1 +LZSA_DST_HI = *+2 + STA $AAAA + LDA PUTDST+1 + BNE PUTDST_DONE + DEC PUTDST+2 +PUTDST_DONE + DEC PUTDST+1 + RTS + +GETLARGESRC + JSR GETSRC ; grab low 8 bits + TAX ; move to X + ; fall through grab high 8 bits + +GETSRC +LZSA_SRC_LO = *+1 +LZSA_SRC_HI = *+2 + LDA $AAAA + PHA + LDA GETSRC+1 + BNE GETSRC_DONE + DEC GETSRC+2 +GETSRC_DONE + DEC GETSRC+1 + PLA + RTS + +} else { + + ; Forward decompression -- get and put bytes forward + +GETPUT + JSR GETSRC +PUTDST +LZSA_DST_LO = *+1 +LZSA_DST_HI = *+2 + STA $AAAA + INC PUTDST+1 + BNE PUTDST_DONE + INC PUTDST+2 +PUTDST_DONE + RTS + +GETLARGESRC + JSR GETSRC ; grab low 8 bits + TAX ; move to X + ; fall through grab high 8 bits + +GETSRC +LZSA_SRC_LO = *+1 +LZSA_SRC_HI = *+2 + LDA $AAAA + INC GETSRC+1 + BNE GETSRC_DONE + INC GETSRC+2 +GETSRC_DONE + RTS + +} + diff --git a/Tools/unix/lzsa/asm/8088/LZSA1FTA.ASM b/Tools/unix/lzsa/asm/8088/LZSA1FTA.ASM new file mode 100644 index 00000000..b342a230 --- /dev/null +++ b/Tools/unix/lzsa/asm/8088/LZSA1FTA.ASM @@ -0,0 +1,250 @@ +; lzsa1fta.asm time-efficient decompressor implementation for 8088 +; Turbo Assembler IDEAL mode dialect; can also be assembled with NASM. +; +; Usual DOS assembler SMALL model assumptions apply. This code: +; - Assumes it was invoked via NEAR call (change RET to RETF for FAR calls) +; - Is interrupt-safe +; - Is not re-entrant (do not decompress while already running decompression) +; - Trashes all data and segment registers +; +; Copyright (C) 2019 Jim Leonard, Emmanuel Marty +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. + + IDEAL + P8086 + +SEGMENT CODE para public + +ASSUME cs:CODE, ds:CODE + +PUBLIC lzsa1_decompress_speed + +; --------------------------------------------------------------------------- +; Decompress raw LZSA1 block +; inputs: +; * ds:si: raw LZSA1 block +; * es:di: output buffer +; output: +; * ax: decompressed size +; --------------------------------------------------------------------------- + +; Must declare this in the code segment: +SHR4table: + DB 00h,00h,00h,00h,00h,00h,00h,00h,00h,00h,00h,00h,00h,00h,00h,00h + DB 01h,01h,01h,01h,01h,01h,01h,01h,01h,01h,01h,01h,01h,01h,01h,01h + DB 02h,02h,02h,02h,02h,02h,02h,02h,02h,02h,02h,02h,02h,02h,02h,02h + DB 03h,03h,03h,03h,03h,03h,03h,03h,03h,03h,03h,03h,03h,03h,03h,03h + DB 04h,04h,04h,04h,04h,04h,04h,04h,04h,04h,04h,04h,04h,04h,04h,04h + DB 05h,05h,05h,05h,05h,05h,05h,05h,05h,05h,05h,05h,05h,05h,05h,05h + DB 06h,06h,06h,06h,06h,06h,06h,06h,06h,06h,06h,06h,06h,06h,06h,06h + DB 07h,07h,07h,07h,07h,07h,07h,07h,07h,07h,07h,07h,07h,07h,07h,07h + +PROC lzsa1_decompress_speed NEAR + +lzsa1_start: + push di ;remember decompression offset + cld ;ensure string ops move forward + mov bx,offset SHR4table + xor cx,cx + +@@decode_token: + xchg cx,ax ;clear ah (cx = 0 from match copy's rep movsb) + lodsb ;read token byte: O|LLL|MMMM + mov dx,ax ;copy our token to dl for later MMMM handling + + and al,070H ;isolate literals length in token (LLL) + jz @@check_offset_size ;if LLL=0, we have no literals; goto match + cmp al,070H ;LITERALS_RUN_LEN? + jne @@got_literals ;no, we have full count from token; go copy + + lodsb ;grab extra length byte + add al,07H ;add LITERALS_RUN_LEN + jnc @@got_literals_exact ;if no overflow, we have full count + je @@big_literals + +@@mid_literals: + lodsb ;grab single extra length byte + inc ah ;add 256 + xchg cx,ax ;with longer counts, we can save some time + shr cx,1 ;by doing a word copy instead of a byte copy. + rep movsw ;We don't need to account for overlap because + adc cx,0 ;source for literals isn't the output buffer. + rep movsb + jmp @@check_offset_size + +@@big_literals: + lodsw ;grab 16-bit extra length + xchg cx,ax ;with longer counts, we can save some time + shr cx,1 ;by doing a word copy instead of a byte copy. + rep movsw + adc cx,0 + rep movsb + jmp @@check_offset_size + +@@got_literals: + segcs xlat ;shift literals length into place +@@got_literals_exact: + xchg cx,ax + rep movsb ;copy cx literals from ds:si to es:di + +@@check_offset_size: + test dl,dl ;check match offset size in token (O bit) + js @@get_long_offset ;load absolute 16-bit match offset + + mov ah,0ffh ;set up high byte + lodsb ;load low byte + +@@get_match_length: + xchg dx,ax ;dx: match offset ax: original token + and al,0FH ;isolate match length in token (MMMM) + cmp al,0FH ;MATCH_RUN_LEN? + jne @@got_matchlen_short ;no, we have the full match length from the token, go copy + + lodsb ;grab extra length byte + add al,012H ;add MIN_MATCH_SIZE + MATCH_RUN_LEN + jnc @@do_long_copy ;if no overflow, we have the entire length + jne @@mid_matchlen + + lodsw ;grab 16-bit length + xchg cx,ax ;get ready to do a long copy + jcxz @@done_decompressing ;wait, is it the EOD marker? Exit if so + jmp @@copy_len_preset ;otherwise, do the copy + +@@get_long_offset: + lodsw ;Get 2-byte match offset + jmp @@get_match_length + +@@got_matchlen_short: + add al,3 ;add MIN_MATCH_SIZE + xchg cx,ax ;copy match length into cx + mov bp,ds ;save ds + mov ax,es + mov ds,ax ;ds=es + xchg ax,si ;save si + mov si,di ;ds:si now points at back reference in output data + add si,dx + rep movsb ;copy match + xchg si,ax ;restore si + mov ds,bp ;restore ds + jmp @@decode_token ;go decode another token + +@@done_decompressing: + pop ax ;retrieve the original decompression offset + xchg di,ax ;compute decompressed size + sub ax,di + ret ;done decompressing, exit to caller + +;With a confirmed longer match length, we have an opportunity to optimize for +;the case where a single byte is repeated long enough that we can benefit +;from rep movsw to perform the run (instead of rep movsb). +@@mid_matchlen: + lodsb ;grab single extra length byte + inc ah ;add 256 +@@do_long_copy: + xchg cx,ax ;copy match length into cx +@@copy_len_preset: + push ds ;save ds + mov bp,es + mov ds,bp ;ds=es + mov bp,si ;save si + mov si,di ;ds:si now points at back reference in output data + add si,dx + cmp dx,-2 ;do we have a byte/word run to optimize? + jae @@do_run ;perform a run +;You may be tempted to change "jae" to "jge" because DX is a signed number. +;Don't! The total window is 64k, so if you treat this as a signed comparison, +;you will get incorrect results for offsets over 32K. + +;If we're here, we have a long copy and it isn't byte-overlapping (if it +;overlapped, we'd be in @@do_run_1) So, let's copy faster with REP MOVSW. +;This won't affect 8088 that much, but it speeds up 8086 and higher. + shr cx,1 + rep movsw + adc cx,0 + rep movsb + mov si,bp ;restore si + pop ds + jmp @@decode_token ;go decode another token + +@@do_run: + je @@do_run_2 ;fall through to byte (common) if not word run + +@@do_run_1: + lodsb ;load first byte of run into al + mov ah,al + shr cx,1 + rep stosw ;perform word run + adc cx,0 + rep stosb ;finish word run + mov si,bp ;restore si + pop ds + jmp @@decode_token ;go decode another token + +@@do_run_2: + lodsw ;load first word of run + shr cx,1 + rep stosw ;perform word run + adc cx,0 ;despite 2-byte offset, compressor might + rep stosb ;output odd length. better safe than sorry. + mov si,bp ;restore si + pop ds + jmp @@decode_token ;go decode another token + +ENDP lzsa1_decompress_speed + +ENDS CODE + +END + +;Speed optimization history (decompression times in microseconds @ 4.77 MHz): +; original E. Marty code shuttle 123208 alice 65660 robotron 407338 *** +; table for shr al,4 shuttle 120964 alice 63230 robotron 394733 +++ +; push/pop to mov/mov shuttle 118176 alice 61835 robotron 386762 +++ +; movsw for literalcpys shuttle 124102 alice 64908 robotron 400220 --- rb +; stosw for byte runs shuttle 118897 alice 65040 robotron 403518 --- rb +; better stosw for runs shuttle 117712 alice 65040 robotron 403343 +-- +; disable RLE by default shuttle 116924 alice 60783 robotron 381226 +++ +; optimize got_matchlen shuttle 115294 alice 59588 robotron 374330 +++ +; fall through to getML shuttle 113258 alice 59572 robotron 372004 +++ +; fall through to midLI shuttle 113258 alice 59572 robotron 375060 ..- rb +; fall through midMaLen shuttle 113247 alice 59572 robotron 372004 +.+ +; movsw for litlen > 255 shuttle 113247 alice 59572 robotron 371612 ..+ +; rep stosw for long runs shuttle 113247 alice 59572 robotron 371612 ... +; rep movsw for long cpys shuttle 113247 alice 59572 robotron 371035 ..+ +; xchg/dec ah -> mov ah,val shuttle 112575 alice 59272 robotron 369198 +++ +; force >12h len.to longcpy shuttle 101998 alice 59266 robotron 364459 +.+ +; more efficient run branch shuttle 102239 alice 59297 robotron 364716 --- rb +; even more eff. run branch shuttle 101998 alice 59266 robotron 364459 *** +; BUGFIX - bad sign compare shuttle 101955 alice 59225 robotron 364117 +++ +; reverse 16-bit len compar shuttle 102000 alice 59263 robotron 364460 --- rb +; jcxz for EOD detection no change to speed, but is 1 byte shorter +++ +; force movsw for literals shuttle 107183 alice 62555 robotron 379524 --- rb +; defer shr4 until necessry shuttle 102069 alice 60236 robotron 364096 --- +; skip literals if LLL=0 shuttle 98655 alice 57849 robotron 363358 --- +; fall through to mid_liter shuttle 98595 alice 57789 robotron 361998 +++ +; == jumptable experiments begin == +; jumptable for small copys shuttle 101594 alice 61078 robotron 386018 --- +; start:xchg instead of mov shuttle 100948 alice 60467 robotron 381112 +++ +; use table for LLL=0 check shuttle 106972 alice 63333 robotron 388304 --- rb +; jmptbl to fallthrough mov shuttle 102532 alice 60760 robotron 383070 --- +; cpy fallthrough check_ofs shuttle 98939 alice 58917 robotron 371019 +** +; single jumptable jump shuttle 97528 alice 57264 robotron 362194 ++* +; conditional check for L=7 shuttle 98610 alice 58521 robotron 368153 --- rb +; rip out the jumptable :-/ shuttle 97616 alice 57128 robotron 360697 +++ +; defer add MIN_MATCH_SIZE shuttle 97250 alice 57004 robotron 361191 ++? +; cache constants in regs shuttle 104681 alice 59939 robotron 380125 --- rb diff --git a/Tools/unix/lzsa/asm/8088/LZSA1JMP.ASM b/Tools/unix/lzsa/asm/8088/LZSA1JMP.ASM new file mode 100644 index 00000000..b96b4989 --- /dev/null +++ b/Tools/unix/lzsa/asm/8088/LZSA1JMP.ASM @@ -0,0 +1,523 @@ +; lzsa1fta.asm time-efficient decompressor implementation for 808x CPUs. +; Turbo Assembler IDEAL mode dialect. +; (Is supposed to also assemble with NASM's IDEAL mode support, but YMMV.) +; +; This code assembles to about 3K of lookup tables and unrolled code, +; but the tradeoff for that size is the absolute fastest decompressor +; of LZSA1 block data for 808x CPUs. +; If you need moderately fast code with less size, see LZSA1FTA.ASM. +; If you need the smallest decompression code, see decompress_small_v1.S. +; +; Usual DOS assembler SMALL model assumptions apply. This code: +; - Assumes it was invoked via NEAR call (change RET to RETF for FAR calls) +; - Is interrupt-safe +; - Is not re-entrant (do not decompress while already running decompression) +; - Trashes all data and segment registers +; +; Copyright (C) 2019 Jim Leonard, Emmanuel Marty +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. +; +; =========================================================================== +; +; The key area to concentrate on when optimizing LZSA1 decompression speed is +; reducing time spent handling the shortest matches. This is for two reasons: +; 1. shorter matches are more common +; 2. short matches are least efficient in terms of decomp speed per byte +; You can confirm #1 using the --stats mode of the compressor. +; +; Branches are costly on 8086. To ensure we branch as little as possible, a +; jumptable will be used to branch directly to as many direct decode paths as +; possible. This will burn up 512 bytes of RAM for a jumptable, and a few +; hundred bytes of duplicated program code (rather than JMP/CALL common code +; blocks, we inline them to avoid the branch overhead). +; +; =========================================================================== +; +; === LZSA1 block reference: +; +; Blocks encoded as LZSA1 are composed from consecutive commands. +; Each command follows this format: +; +; token: +; optional extra literal length +; literal values +; match offset low +; optional match offset high +; optional extra encoded match length +; +; +; === LZSA1 Token Reference: +; +; 7 6 5 4 3 2 1 0 +; O L L L M M M M +; +; L: 3-bit literals length (0-6, or 7 if extended). If the number of literals for +; this command is 0 to 6, the length is encoded in the token and no extra bytes +; are required. Otherwise, a value of 7 is encoded and extra bytes follow as +; 'optional extra literal length' +; +; M: 4-bit encoded match length (0-14, or 15 if extended). Likewise, if the +; encoded match length for this command is 0 to 14, it is directly stored, +; otherwise 15 is stored and extra bytes follow as 'optional extra encoded match +; length'. Except for the last command in a block, a command always contains a +; match, so the encoded match length is the actual match length, offset by the +; minimum which is 3 bytes. For instance, an actual match length of 10 bytes to +; be copied, is encoded as 7. +; +; O: set for a 2-bytes match offset, clear for a 1-byte match offset +; +; +; === Decoding extended literal length: +; +; If the literals length is 7 or more, then an extra byte follows here, with +; three possible values: +; +; 0-248: the value is added to the 7 stored in the token. +; 250: a second byte follows. The final literals value is 256 + the second byte. +; 249: a little-endian 16-bit value follows, forming the final literals value. +; +; +; === Decoding match offsets: +; +; match offset low: The low 8 bits of the match offset follows. +; +; optional match offset high: If the 'O' bit (bit 7) is set in the token, the +; high 8 bits of the match offset follow, otherwise they are understood to be all +; set to 1. For instance, a short offset of 0x70 is interpreted as 0xff70 +; +; +; === Decoding extra encoded match length: +; +; optional extra encoded match length: If the encoded match length is 15 or more, +; the 'M' bits in the token form the value 15, and an extra byte follows here, +; with three possible types of value. +; +; 0-237: the value is added to the 15 stored in the token. The final value is 3 + 15 + this byte. +; 239: a second byte follows. The final match length is 256 + the second byte. +; 238: a second and third byte follow, forming a little-endian 16-bit value. +; The final encoded match length is that 16-bit value. +; +; =========================================================================== + + IDEAL ; Use Turbo Assembler IDEAL syntax checking + P8086 ; Restrict code generation to the 808x and later + JUMPS ; Perform fixups for out-of-bound conditional jumps + ; This is required for the (L=07 & M=0Fh) decode paths as they + ; have the most code, but these are uncommon paths so the + ; tiny speed loss in just these paths is not a concern. + +SEGMENT CODE para public + +ASSUME cs:CODE, ds:CODE + +PUBLIC lzsa1_decompress_speed_jumptable + +; EQU helper statements (so we can construct a jump table without going crazy) + +minmatch EQU 3 +litrunlen EQU 7 + +leml1 EQU OFFSET lit_ext_mat_len_1b +leme1 EQU OFFSET lit_ext_mat_ext_1b +leml2 EQU OFFSET lit_ext_mat_len_2b +leme2 EQU OFFSET lit_ext_mat_ext_2b + +;short-circuit special cases for 0 through 6 literal copies: +l6ml1 EQU OFFSET lit_len_mat_len_1b +l6me1 EQU OFFSET lit_len_mat_ext_1b +l6ml2 EQU OFFSET lit_len_mat_len_2b +l6me2 EQU OFFSET lit_len_mat_ext_2b +l5ml1 EQU OFFSET lit_len_mat_len_1b + 1 +l5me1 EQU OFFSET lit_len_mat_ext_1b + 1 +l5ml2 EQU OFFSET lit_len_mat_len_2b + 1 +l5me2 EQU OFFSET lit_len_mat_ext_2b + 1 +l4ml1 EQU OFFSET lit_len_mat_len_1b + 2 +l4me1 EQU OFFSET lit_len_mat_ext_1b + 2 +l4ml2 EQU OFFSET lit_len_mat_len_2b + 2 +l4me2 EQU OFFSET lit_len_mat_ext_2b + 2 +l3ml1 EQU OFFSET lit_len_mat_len_1b + 3 +l3me1 EQU OFFSET lit_len_mat_ext_1b + 3 +l3ml2 EQU OFFSET lit_len_mat_len_2b + 3 +l3me2 EQU OFFSET lit_len_mat_ext_2b + 3 +l2ml1 EQU OFFSET lit_len_mat_len_1b + 4 +l2me1 EQU OFFSET lit_len_mat_ext_1b + 4 +l2ml2 EQU OFFSET lit_len_mat_len_2b + 4 +l2me2 EQU OFFSET lit_len_mat_ext_2b + 4 +l1ml1 EQU OFFSET lit_len_mat_len_1b + 5 +l1me1 EQU OFFSET lit_len_mat_ext_1b + 5 +l1ml2 EQU OFFSET lit_len_mat_len_2b + 5 +l1me2 EQU OFFSET lit_len_mat_ext_2b + 5 +l0ml1 EQU OFFSET lit_len_mat_len_1b + 6 ; MMMM handling comes after LLL code +l0me1 EQU OFFSET lit_len_mat_ext_1b + 6 ; MMMM handling comes after LLL code +l0ml2 EQU OFFSET lit_len_mat_len_2b + 6 ; MMMM handling comes after LLL code +l0me2 EQU OFFSET lit_len_mat_ext_2b + 6 ; MMMM handling comes after LLL code + +; === Hand-written (!) jumptable actually begins here. +; Located before the program code results in an extra JMP and 3 wasted bytes, +; but it makes the code easier to follow in this location. +; Relocate the jump table after the ENDP directive to save 3 bytes. +; +; 7 6 5 4 3 2 1 0 +; O L L L M M M M +; +; 0 1 2 3 4 5 6 7 8 9 a b c d e f +jtbl DW l0ml1,l0ml1,l0ml1,l0ml1,l0ml1,l0ml1,l0ml1,l0ml1,l0ml1,l0ml1,l0ml1,l0ml1,l0ml1,l0ml1,l0ml1,l0me1 ;0 + DW l1ml1,l1ml1,l1ml1,l1ml1,l1ml1,l1ml1,l1ml1,l1ml1,l1ml1,l1ml1,l1ml1,l1ml1,l1ml1,l1ml1,l1ml1,l1me1 ;1 + DW l2ml1,l2ml1,l2ml1,l2ml1,l2ml1,l2ml1,l2ml1,l2ml1,l2ml1,l2ml1,l2ml1,l2ml1,l2ml1,l2ml1,l2ml1,l2me1 ;2 + DW l3ml1,l3ml1,l3ml1,l3ml1,l3ml1,l3ml1,l3ml1,l3ml1,l3ml1,l3ml1,l3ml1,l3ml1,l3ml1,l3ml1,l3ml1,l3me1 ;3 + DW l4ml1,l4ml1,l4ml1,l4ml1,l4ml1,l4ml1,l4ml1,l4ml1,l4ml1,l4ml1,l4ml1,l4ml1,l4ml1,l4ml1,l4ml1,l4me1 ;4 + DW l5ml1,l5ml1,l5ml1,l5ml1,l5ml1,l5ml1,l5ml1,l5ml1,l5ml1,l5ml1,l5ml1,l5ml1,l5ml1,l5ml1,l5ml1,l5me1 ;5 + DW l6ml1,l6ml1,l6ml1,l6ml1,l6ml1,l6ml1,l6ml1,l6ml1,l6ml1,l6ml1,l6ml1,l6ml1,l6ml1,l6ml1,l6ml1,l6me1 ;6 + DW leml1,leml1,leml1,leml1,leml1,leml1,leml1,leml1,leml1,leml1,leml1,leml1,leml1,leml1,leml1,leme1 ;7 + DW l0ml2,l0ml2,l0ml2,l0ml2,l0ml2,l0ml2,l0ml2,l0ml2,l0ml2,l0ml2,l0ml2,l0ml2,l0ml2,l0ml2,l0ml2,l0me2 ;8 + DW l1ml2,l1ml2,l1ml2,l1ml2,l1ml2,l1ml2,l1ml2,l1ml2,l1ml2,l1ml2,l1ml2,l1ml2,l1ml2,l1ml2,l1ml2,l1me2 ;9 + DW l2ml2,l2ml2,l2ml2,l2ml2,l2ml2,l2ml2,l2ml2,l2ml2,l2ml2,l2ml2,l2ml2,l2ml2,l2ml2,l2ml2,l2ml2,l2me2 ;a + DW l3ml2,l3ml2,l3ml2,l3ml2,l3ml2,l3ml2,l3ml2,l3ml2,l3ml2,l3ml2,l3ml2,l3ml2,l3ml2,l3ml2,l3ml2,l3me2 ;b + DW l4ml2,l4ml2,l4ml2,l4ml2,l4ml2,l4ml2,l4ml2,l4ml2,l4ml2,l4ml2,l4ml2,l4ml2,l4ml2,l4ml2,l4ml2,l4me2 ;c + DW l5ml2,l5ml2,l5ml2,l5ml2,l5ml2,l5ml2,l5ml2,l5ml2,l5ml2,l5ml2,l5ml2,l5ml2,l5ml2,l5ml2,l5ml2,l5me2 ;d + DW l6ml2,l6ml2,l6ml2,l6ml2,l6ml2,l6ml2,l6ml2,l6ml2,l6ml2,l6ml2,l6ml2,l6ml2,l6ml2,l6ml2,l6ml2,l6me2 ;e + DW leml2,leml2,leml2,leml2,leml2,leml2,leml2,leml2,leml2,leml2,leml2,leml2,leml2,leml2,leml2,leme2 ;f + +PROC lzsa1_decompress_speed_jumptable NEAR +; --------------------------------------------------------------------------- +; Decompress raw LZSA1 block +; inputs: +; * ds:si: raw LZSA1 block +; * es:di: output buffer +; output: +; * ax: decompressed size +; --------------------------------------------------------------------------- + +MACRO get_byte_match_offset + mov ah,0ffh ;O=0, so set up offset's high byte + lodsb ;load low byte; ax=match offset + xchg bp,ax ;bp=match offset ax=00 + original token +ENDM + +MACRO get_word_match_offset + lodsw ;ax=match offset + xchg bp,ax ;bp=match offset ax=00 + original token +ENDM + +MACRO do_match_copy_long +LOCAL do_run, do_run_w +; Copies a long match as optimally as possible. +; requirements: cx=length, bp=negative offset, ds:si=compdata, es:di=output +; trashes: ax, bx +; must leave cx=0 at exit + mov bx,ds ;save ds + mov ax,es + mov ds,ax ;ds=es + xchg ax,si ;save si + lea si,[bp+di] ;si = output buffer + negative match offset + cmp bp,-2 ;do we have a byte/word run to optimize? + jae do_run ;perform a run if so, otherwise fall through +;You may be tempted to change "jae" to "jge" because DX is a signed number. +;Don't! The total window is 64k, so if you treat this as a signed comparison, +;you will get incorrect results for offsets over 32K. + +;If we're here, we have a long copy and it isn't byte-overlapping (if it +;overlapped, we'd be in @@do_run) So, let's copy faster with REP MOVSW. +;This affects 8088 only slightly, but is a bigger win on 8086 and higher. + shr cx,1 + rep movsw + adc cl,0 + rep movsb + xchg si,ax ;restore si + mov ds,bx ;restore ds + jmp decode_token + +do_run: + je do_run_w ;if applicable, handle word-sized value faster + xchg dx,ax ;save si into dx, as ax is getting trashed + lodsb ;load first byte of run into al + mov ah,al + shr cx,1 + rep stosw ;perform word run + adc cl,0 + rep stosb ;finish word run + mov si,dx ;restore si + mov ds,bx ;restore ds + jmp decode_token + +do_run_w: + xchg dx,ax ;save si into dx, as ax is getting trashed + lodsw ;load first word of run + shr cx,1 + rep stosw ;perform word run + adc cl,0 ;despite 2-byte offset, compressor might + rep stosb ;output odd length. better safe than sorry. + mov si,dx ;restore si + mov ds,bx ;restore ds + jmp decode_token +ENDM + +MACRO do_match_copy +; Copies a shorter match with as little overhead as possible. +; requirements: cx=length, bp=negative offset, ds:si=compdata, es:di=output +; trashes: ax, bx +; must leave cx=0 at exit + mov bx,ds ;save ds + mov ax,es + mov ds,ax ;ds=es + xchg ax,si ;save si + lea si,[bp+di] ;si = output buffer + negative match offset + rep movsb + xchg si,ax ;restore si + mov ds,bx ;restore ds + jmp decode_token +ENDM + +MACRO do_literal_copy +; Copies a literal sequence using words. +; Meant for longer lengths; for 128 bytes or less, use REP MOVSB. +; requirements: cx=length, ds:si=compdata, es:di=output +; must leave cx=0 at exit + shr cx,1 + rep movsw + adc cl,0 + rep movsb +ENDM + +MACRO copy_small_match_len + and al,0FH ;isolate length in token (MMMM) + add al,minmatch ;ax=match length + xchg cx,ax ;cx=match length + do_match_copy ;copy match with cx=length, bp=offset +ENDM + +MACRO copy_large_match_len +LOCAL val239, val238, EOD +; Handle MMMM=Fh +; Assumptions: ah=0 from get_????_match_offset's xchg + lodsb ;grab extra match length byte + add al,0Fh+minmatch ;add MATCH_RUN_LEN + MIN_MATCH_SIZE + jz val238 ;if zf & cf, 238: get 16-bit match length + jc val239 ;if cf, 239: get extra match length byte + xchg cx,ax ;otherwise, we have our match length + do_match_copy_long ;copy match with cx=length, bp=offset +val239: + lodsb ;ah=0; grab single extra length byte + inc ah ;ax=256+length byte + xchg cx,ax + do_match_copy_long ;copy match with cx=length, bp=offset +val238: + lodsw ;grab 16-bit length + xchg cx,ax + jcxz EOD ;is it the EOD marker? Exit if so + do_match_copy_long ;copy match with cx=length, bp=offset +EOD: + jmp done_decompressing +ENDM + + +lzsa1_start: + push di ;remember decompression offset + cld ;ensure string ops move forward + xor cx,cx + +decode_token: + xchg cx,ax ;clear ah (cx = 0 from match copy's REP) + lodsb ;read token byte: O|LLL|MMMM + mov bp,ax ;preserve 0+token in bp for later MMMM handling + mov bx,ax ;prep for table lookup + shl bx,1 ;adjust for offset word size + jmp [cs:jtbl+bx] ;jump directly to relevant decode path + +; There are eight basic decode paths for an LZSA1 token. Each of these +; paths perform only the necessary actions to decode the token and then +; fetch the next token. This results in a lot of code duplication, but +; it is the only way to get down to two branches per token (jump to unique +; decode path, then jump back to next token) for the most common cases. + +; Path #1: LLL=0-6, MMMM=0-Eh, O=0 (1-byte match offset) +; Handle LLL=0-6 by jumping directly into # of bytes to copy (6 down to 1) +lit_len_mat_len_1b: + movsb + movsb + movsb + movsb + movsb + movsb + get_byte_match_offset + copy_small_match_len + + +; Path #2: LLL=0-6, MMMM=Fh, O=0 (1-byte match offset) +lit_len_mat_ext_1b: + movsb + movsb + movsb + movsb + movsb + movsb + get_byte_match_offset + copy_large_match_len + + +; Path #3: LLL=7, MMMM=0-Eh, O=0 (1-byte match offset) +lit_ext_mat_len_1b: +; on entry: ax=0 + token, bp=ax + lodsb ;grab extra literal length byte + add al,litrunlen ;add 7h literal run length + jz @@val249_3 ;if zf & cf, 249: get 16-bit literal length + jc @@val250_3 ;if cf, 250: get extra literal length byte + xchg cx,ax ;otherwise, we have our literal length + do_literal_copy ;this might be better as rep movsw !!! benchmark + get_byte_match_offset + copy_small_match_len +@@val250_3: + lodsb ;ah=0; grab single extra length byte + inc ah ;ax=256+length byte + xchg cx,ax + do_literal_copy + get_byte_match_offset + copy_small_match_len +@@val249_3: + lodsw ;grab 16-bit length + xchg cx,ax + do_literal_copy + get_byte_match_offset + copy_small_match_len + + +; Path #4: LLL=7, MMMM=Fh, O=0 (1-byte match offset) +lit_ext_mat_ext_1b: +; on entry: ax=0 + token, bp=ax + lodsb ;grab extra literal length byte + add al,litrunlen ;add 7h literal run length + jz @@val249_4 ;if zf & cf, 249: get 16-bit literal length + jc @@val250_4 ;if cf, 250: get extra literal length byte + xchg cx,ax ;otherwise, we have our literal length + do_literal_copy ;this might be better as rep movsw !!! benchmark + get_byte_match_offset + copy_large_match_len +@@val250_4: + lodsb ;ah=0; grab single extra length byte + inc ah ;ax=256+length byte + xchg cx,ax + do_literal_copy + get_byte_match_offset + copy_large_match_len +@@val249_4: + lodsw ;grab 16-bit length + xchg cx,ax + do_literal_copy + get_byte_match_offset + copy_large_match_len + + +; Path #5: LLL=0-6, MMMM=0-Eh, O=1 (2-byte match offset) +; Handle LLL=0-6 by jumping directly into # of bytes to copy (6 down to 1) +lit_len_mat_len_2b: + movsb + movsb + movsb + movsb + movsb + movsb + get_word_match_offset + copy_small_match_len + + +; Path #6: LLL=0-6, MMMM=Fh, O=1 (2-byte match offset) +lit_len_mat_ext_2b: + movsb + movsb + movsb + movsb + movsb + movsb + get_word_match_offset + copy_large_match_len + + +; Path #7: LLL=7, MMMM=0-Eh, O=1 (2-byte match offset) +lit_ext_mat_len_2b: +; on entry: ax=0 + token, bp=ax + lodsb ;grab extra literal length byte + add al,litrunlen ;add 7h literal run length + jz @@val249_7 ;if zf & cf, 249: get 16-bit literal length + jc @@val250_7 ;if cf, 250: get extra literal length byte + xchg cx,ax ;otherwise, we have our literal length + do_literal_copy ;this might be better as rep movsw !!! benchmark + get_word_match_offset + copy_small_match_len +@@val250_7: + lodsb ;ah=0; grab single extra length byte + inc ah ;ax=256+length byte + xchg cx,ax + do_literal_copy + get_word_match_offset + copy_small_match_len +@@val249_7: + lodsw ;grab 16-bit length + xchg cx,ax + do_literal_copy + get_word_match_offset + copy_small_match_len + + +; Path #8: LLL=7, MMMM=Fh, O=1 (2-byte match offset) +lit_ext_mat_ext_2b: +; on entry: ax=0 + token, bp=ax + lodsb ;grab extra literal length byte + add al,litrunlen ;add 7h literal run length + jz @@val249_8 ;if zf & cf, 249: get 16-bit literal length + jc @@val250_8 ;if cf, 250: get extra literal length byte + xchg cx,ax ;otherwise, we have our literal length + do_literal_copy ;this might be better as rep movsw !!! benchmark + get_word_match_offset + copy_large_match_len +@@val250_8: + lodsb ;ah=0; grab single extra length byte + inc ah ;ax=256+length byte + xchg cx,ax + do_literal_copy + get_word_match_offset + copy_large_match_len +@@val249_8: + lodsw ;grab 16-bit length + xchg cx,ax + do_literal_copy + get_word_match_offset + copy_large_match_len + + +done_decompressing: +;return # of decompressed bytes in ax + pop ax ;retrieve the original decompression offset + sub di,ax ;adjust for original offset + xchg di,ax ;return adjusted value in ax + ret ;done decompressing, exit to caller + +ENDP lzsa1_decompress_speed_jumptable + +ENDS CODE + +END + +;Speed optimization history (decompression times in microseconds @ 4.77 MHz): +; defer add MIN_MATCH_SIZE shuttle 97207 alice 57200 robotron 362884 ++* +; jumptable rewrite, no RLE shuttle 97744 alice 46905 robotron 309032 -++ +; adc cx,0 -> adc cl,0 shuttle 97744 alice 46893 robotron 309032 .+.! +; jumptable rewrite w/RLE shuttle 88776 alice 50433 robotron 319222 +-- +; short match copies movsb shuttle 97298 alice 49769 robotron 326282 ---rb +; long match copy #1 16-bit shuttle 92490 alice 46905 robotron 308722 +*+ +; long match copy #2 extraB shuttle 92464 alice 46905 robotron 308371 +.+ +; long match copy #3 0f->ed shuttle 86765 alice 46864 robotron 303895 +++! diff --git a/Tools/unix/lzsa/asm/8088/LZSA2FTA.ASM b/Tools/unix/lzsa/asm/8088/LZSA2FTA.ASM new file mode 100644 index 00000000..bf38e18c --- /dev/null +++ b/Tools/unix/lzsa/asm/8088/LZSA2FTA.ASM @@ -0,0 +1,302 @@ +; lzsa2fta.asm - LZSA v2 time-efficient decompressor implementation for 8088 +; Turbo Assembler IDEAL mode dialect; can also be assembled with NASM. +; +; Usual DOS assembler SMALL model assumptions apply. This code: +; - Assumes it was invoked via NEAR call (change RET to RETF for FAR calls) +; - Is interrupt-safe +; - Is not re-entrant (do not decompress while already running decompression) +; - Trashes all data and segment registers +; +; Copyright (C) 2019 Jim Leonard, Emmanuel Marty +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. + + IDEAL + P8086 + MODEL SMALL + + CODESEG + +;While LZSA2 is technically capable of generating a match offset of -2, +;this sequence never actually showed up in my LZSA2 test corpus, likely due +;to compressor optimizations and the LZSA2 format itself. If you know your +;test data will contain a match offset of -2, you can enable code to write +;out the sequence very quickly at the cost of 18 bytes of code. +HANDLE_WORD_RUN EQU 0 + +PUBLIC lzsa2_decompress_speed + +; --------------------------------------------------------------------------- +; Decompress raw LZSA2 block +; inputs: +; * ds:si: raw LZSA2 block +; * es:di: output buffer +; output: +; * ax: decompressed size +; --------------------------------------------------------------------------- + +PROC lzsa2_decompress_speed NEAR + +MACRO get_nybble +LOCAL has_nybble + neg bh ;nybble ready? + jns has_nybble + xchg bx,ax + lodsb ;load two nybbles + xchg bx,ax +has_nybble: + mov cl,4 ;swap 4 high and low bits of nybble + ror bl,cl + mov cl,0FH + and cl,bl +ENDM + +lzsa2_speed_start: + push di ;remember decompression offset + cld ;make string operations go forward + xor cx,cx + mov bx,0100H ;bx used by get_nybble + +@@decode_token: + mov ax,cx ;clear ah - cx is zero (and must stay that way) + lodsb ;read token byte: XYZ|LL|MMMM + mov dx,ax ;keep copy of token in dl + + and al,018H ;isolate literals length in token (LL) + jz @@check_offset ;no literals? stop decoding, go to matches + +;At this point, al can be in three (unshifted) states: 1, 2, or 3. +;3 = not done yet. + cmp al,(2 shl 3) ;LITERALS_RUN_LEN_V2? (original: cmp al,03h) + jb @@lit1b ;LZSA2 output 1-byte more often, so test first + je @@lit2b + + mov cl,3 + shr al,cl ;shift literals length into place + get_nybble ;cl := get extra literals length nybble + add al,cl ;add len from token to nybble + cmp al,012H ;LITERALS_RUN_LEN_V2 + 15 ? + jne @@got_literals ;if not, we have the full literals count + lodsb ;grab extra length byte + add al,012H ;overflow? + jnc @@got_literals_big ;if not, we have a big full literals count + lodsw ;grab 16-bit extra length + +;For larger counts, it pays to set up a faster copy +@@got_literals_big: + xchg cx,ax + shr cx,1 + rep movsw + adc cx,0 + rep movsb + jmp @@check_offset + +@@got_literals: + xchg cx,ax + rep movsb ;copy cx literals from ds:si to es:di + jmp @@check_offset + +;LZSA2 likes to produce tiny literals of 1 or 2 bytes. Handle them here. +@@lit2b:movsb +@@lit1b:movsb + +@@check_offset: + test dl,dl ;check match offset mode in token (X bit) + js @@rep_match_or_large_offset + + cmp dl,040H ;check if this is a 5 or 9-bit offset (Y bit) + jnb @@offset_9_bit + + ;5 bit offset: + xchg cx,ax ;clear ah - cx is zero from prior rep movs + mov al,020H ;shift Z (offset bit 4) in place + and al,dl + shl al,1 + shl al,1 + get_nybble ;get nybble for offset bits 0-3 + or al,cl ;merge nybble + rol al,1 + xor al,0E1H ;set offset bits 7-5 to 1 + dec ah ;set offset bits 15-8 to 1 + jmp @@get_match_length + +@@rep_match_or_16_bit: + test dl,020H ;test bit Z (offset bit 8) + jne @@repeat_match ;rep-match + + ;16 bit offset: + lodsw ;Get 2-byte match offset + xchg ah,al + jmp @@get_match_length + +@@offset_9_bit: + ;9 bit offset: + xchg cx,ax ;clear ah - cx is zero from prior rep movs + lodsb ;get 8 bit offset from stream in A + dec ah ;set offset bits 15-8 to 1 + test dl,020H ;test bit Z (offset bit 8) + je @@get_match_length + dec ah ;clear bit 8 if Z bit is clear + jmp @@get_match_length + +@@rep_match_or_large_offset: + cmp dl,0c0H ;check if this is a 13-bit offset + ;or a 16-bit offset/rep match (Y bit) + jnb @@rep_match_or_16_bit + + ;13 bit offset: + mov ah,020H ;shift Z (offset bit 12) in place + and ah,dl + shl ah,1 + shl ah,1 + get_nybble ;get nybble for offset bits 8-11 + or ah,cl ;merge nybble + rol ah,1 + xor ah,0E1H ;set offset bits 15-13 to 1 + sub ah,2 ;substract 512 + lodsb ;load match offset bits 0-7 + +@@get_match_length: + mov bp,ax ;bp:=offset +@@repeat_match: + mov ax,dx ;ax: original token + and al,07H ;isolate match length in token (MMM) + add al,2 ;add MIN_MATCH_SIZE_V2 + + cmp al,09H ;MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2? + jne @@got_matchlen ;no, we have full match length from token + + get_nybble ;get extra literals length nybble + add al,cl ;add len from token to nybble + cmp al,018H ;MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2 + 15? + jne @@got_matchlen ;no, we have full match length from token + + lodsb ;grab extra length byte + add al,018H ;overflow? + jnc @@got_matchlen_big ;if not, we have entire (big) length + je @@done_decompressing ; detect EOD code + + lodsw ;grab 16-bit length + +;If we're here, we have a larger match copy and can optimize how we do that +@@got_matchlen_big: + xchg cx,ax ;copy match length into cx + mov dx,ds ;save ds + mov ax,es + mov ds,ax ;ds:=es + xchg si,ax ;dx:ax = old ds:si + mov si,di ;ds:si now points at back reference in output data + add si,bp +IF HANDLE_WORD_RUN + cmp bp,-2 ;do we have a byte/word run to optimize? + jae @@do_run ;perform a run +ELSE + cmp bp,-1 ;do we have a byte run to optimize? + je @@do_run_1 ;perform a byte run +ENDIF +;You may be tempted to change "jae" to "jge" because DX is a signed number. +;Don't! The total window is 64k, so if you treat this as a signed comparison, +;you will get incorrect results for offsets over 32K. +; +;If we're here, we have a long copy and it isn't byte-overlapping (if it +;overlapped, we'd be in @@do_run_1) So, let's copy faster with REP MOVSW. +;This won't affect 8088 that much, but it speeds up 8086 and higher. + shr cx,1 + rep movsw + adc cx,0 + rep movsb + xchg si,ax + mov ds,dx ;restore ds:si + jmp @@decode_token ;go decode another token + +;Smaller match copies handled here: +@@got_matchlen: + xchg cx,ax ;copy match length into cx + mov dx,ds ;save ds + mov ax,es + mov ds,ax ;ds:=es + xchg si,ax ;dx:ax = old ds:si + mov si,di ;ds:si = back reference in output data + add si,bp + rep movsb ;copy match + xchg si,ax + mov ds,dx ;restore ds:si + jmp @@decode_token ;go decode another token + +@@done_decompressing: + pop ax ;retrieve the original decompression offset + xchg di,ax ;compute decompressed size + sub ax,di + ret ;done + +IF HANDLE_WORD_RUN +@@do_run: + je @@do_run_2 ;fall through to byte (common) if not word run +ENDIF + +@@do_run_1: + push ax + lodsb ;load first byte of run into al + mov ah,al + shr cx,1 + rep stosw ;perform word run + adc cx,0 + rep stosb ;finish word run + pop si + mov ds,dx + jmp @@decode_token ;go decode another token + +IF HANDLE_WORD_RUN +@@do_run_2: + push ax + lodsw ;load first word of run + shr cx,1 + rep stosw ;perform word run + adc cx,0 ;despite 2-byte offset, compressor might + rep stosb ;output odd length. better safe than sorry. + pop si + mov ds,dx + jmp @@decode_token ;go decode another token +ENDIF + +ENDP lzsa2_decompress_speed + +ENDS + +END + +;Speed optimization history (decompression times in microseconds @ 4.77 MHz): +;Compression corpus:shuttle alice robotro rletest largetx linewar ...... .. +;Start of exercise 160828 113311 665900 238507 1053865 1004237 ****** +;add al,val -> al,cl 160813 113296 668721 237484 1053604 1003815 ++-+++ +;sub ah,2 -> dec dec 160907 113585 666744 237484 1056651 1005172 --+*-- rb +;mov ax,cx->xchgcxax 159741 112460 660594 237477 1046770 998323 ++++++ +;unroll get_nibble 152552 106327 621119 237345 982381 942373 ++++++ +;early exit if LL=0 147242 103842 615559 239318 946863 942932 +++-+- +;push/pop->mov/mov 145447 100832 604822 237288 927017 931366 ++++++ +;push/pop->mov/mov(2)143214 98817 592920 239298 908217 910955 +++-++ +;rep stos for -1, -2 143289 102812 617087 237164 942081 940688 ---+-- rb +;larger literal cpys 143214 98817 591940 238296 907237 909657 **++++ +;larger copys & runs 132440 98802 586551 178768 904129 896709 ++++++ :-) +;smaller lit. copies 131991 99131 583933 177760 901824 898308 +-+++- +;swap smal lit compa 131828 99022 585121 177757 901793 894054 ++-*++ +;compare before shif 130587 95970 569908 177753 889221 872461 +++*++ +;getmatchlength base 130587 95970 570634 177753 893536 871556 ...... === +; f->rep_match_or_16 xxxxxx xxxxx 569910 xxxxxx 889266 871435 ..+.++ +; f->rep_match_or_la 129966 94748 566169 xxxxxx 880870 867030 +++.++ +++ +; f->offset_9_bit 132126 95258 568869 xxxxxx 893169 870364 -++.-+ +;final fallthrough 129966 94748 566169 177753 880870 865023 ****** diff --git a/Tools/unix/lzsa/asm/8088/decompress_small_v1.S b/Tools/unix/lzsa/asm/8088/decompress_small_v1.S new file mode 100644 index 00000000..ddb9196f --- /dev/null +++ b/Tools/unix/lzsa/asm/8088/decompress_small_v1.S @@ -0,0 +1,120 @@ +; decompress_small.S - space-efficient decompressor implementation for 8088 +; +; Copyright (C) 2019 Emmanuel Marty +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. + + segment .text + bits 16 + +; --------------------------------------------------------------------------- +; Decompress raw LZSA1 block +; inputs: +; * ds:si: raw LZSA1 block +; * es:di: output buffer +; output: +; * ax: decompressed size +; --------------------------------------------------------------------------- + +lzsa1_decompress: + push di ; remember decompression offset + cld ; make string operations (lods, movs, stos..) move forward + + xor cx,cx + +.decode_token: + mov ax,cx ; clear ah - cx is zero from above or from after rep movsb in .copy_match + lodsb ; read token byte: O|LLL|MMMM + mov dx,ax ; keep token in dl + + and al,070H ; isolate literals length in token (LLL) + mov cl,4 + shr al,cl ; shift literals length into place + + cmp al,07H ; LITERALS_RUN_LEN? + jne .got_literals ; no, we have the full literals count from the token, go copy + + lodsb ; grab extra length byte + add al,07H ; add LITERALS_RUN_LEN + jnc .got_literals ; if no overflow, we have the full literals count, go copy + jne .mid_literals + + lodsw ; grab 16-bit extra length + db 81H ; mask inc ah/lodsb + ; (*like jmp short .got_literals but faster) + +.mid_literals: + inc ah ; add 256 + lodsb ; grab single extra length byte + +.got_literals: + xchg cx,ax + rep movsb ; copy cx literals from ds:si to es:di + + test dl,dl ; check match offset size in token (O bit) + js .get_long_offset + + dec cx + xchg cx,ax ; ah to 0xff - cx was zero from the rep movsb above + lodsb + db 3CH ; mask lodsw + ; (*like jmp short .get_match_length but faster) + +.get_long_offset: + lodsw ; Get 2-byte match offset + +.get_match_length: + xchg dx,ax ; dx: match offset ax: original token + and al,0FH ; isolate match length in token (MMMM) + add al,3 ; add MIN_MATCH_SIZE + + cmp al,012H ; MATCH_RUN_LEN? + jne .got_matchlen ; no, we have the full match length from the token, go copy + + lodsb ; grab extra length byte + add al,012H ; add MIN_MATCH_SIZE + MATCH_RUN_LEN + jnc .got_matchlen ; if no overflow, we have the entire length + jne .mid_matchlen + + lodsw ; grab 16-bit length + test ax,ax ; bail if we hit EOD + je short .done_decompressing + + db 81H ; mask inc ah/lodsb + ; (*like jmp short .got_literals but faster) +.mid_matchlen: + inc ah ; add 256 + lodsb ; grab single extra length byte + +.got_matchlen: + xchg cx,ax ; copy match length into cx + push ds ; save ds:si (current pointer to compressed data) + xchg si,ax + push es + pop ds + mov si,di ; ds:si now points at back reference in output data + add si,dx + rep movsb ; copy match + xchg si,ax ; restore ds:si + pop ds + jmp short .decode_token ; go decode another token + +.done_decompressing: + pop ax ; retrieve the original decompression offset + xchg ax,di ; compute decompressed size + sub ax,di + ret ; done diff --git a/Tools/unix/lzsa/asm/8088/decompress_small_v2.S b/Tools/unix/lzsa/asm/8088/decompress_small_v2.S new file mode 100644 index 00000000..16a95df5 --- /dev/null +++ b/Tools/unix/lzsa/asm/8088/decompress_small_v2.S @@ -0,0 +1,176 @@ +; decompress_small.S - space-efficient decompressor implementation for 8088 +; +; Copyright (C) 2019 Emmanuel Marty +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. + + segment .text + bits 16 + +; --------------------------------------------------------------------------- +; Decompress raw LZSA2 block +; inputs: +; * ds:si: raw LZSA2 block +; * es:di: output buffer +; output: +; * ax: decompressed size +; --------------------------------------------------------------------------- + +lzsa2_decompress: + push di ; remember decompression offset + cld ; make string operations (lods, movs, stos..) move forward + + xor cx,cx + mov bx,0100H + xor bp,bp + +.decode_token: + mov ax,cx ; clear ah - cx is zero from above or from after rep movsb in .copy_match + lodsb ; read token byte: XYZ|LL|MMMM + mov dx,ax ; keep token in dl + + and al,018H ; isolate literals length in token (LL) + mov cl,3 + shr al,cl ; shift literals length into place + + cmp al,03H ; LITERALS_RUN_LEN_V2? + jne .got_literals ; no, we have the full literals count from the token, go copy + + call .get_nibble ; get extra literals length nibble + add al,cl ; add len from token to nibble + cmp al,012H ; LITERALS_RUN_LEN_V2 + 15 ? + jne .got_literals ; if not, we have the full literals count, go copy + + lodsb ; grab extra length byte + add al,012H ; overflow? + jnc .got_literals ; if not, we have the full literals count, go copy + + lodsw ; grab 16-bit extra length + +.got_literals: + xchg cx,ax + rep movsb ; copy cx literals from ds:si to es:di + + test dl,0C0h ; check match offset mode in token (X bit) + js .rep_match_or_large_offset + + ;;cmp dl,040H ; check if this is a 5 or 9-bit offset (Y bit) + ; discovered via the test with bit 6 set + xchg cx,ax ; clear ah - cx is zero from the rep movsb above + jne .offset_9_bit + + ; 5 bit offset + cmp dl,020H ; test bit 5 + call .get_nibble_x + jmp short .dec_offset_top + +.offset_9_bit: ; 9 bit offset + lodsb ; get 8 bit offset from stream in A + dec ah ; set offset bits 15-8 to 1 + test dl,020H ; test bit Z (offset bit 8) + je .get_match_length +.dec_offset_top: + dec ah ; clear bit 8 if Z bit is clear + ; or set offset bits 15-8 to 1 + jmp short .get_match_length + +.rep_match_or_large_offset: + ;;cmp dl,0c0H ; check if this is a 13-bit offset or a 16-bit offset/rep match (Y bit) + jpe .rep_match_or_16_bit + + ; 13 bit offset + + cmp dl,0A0H ; test bit 5 (knowing that bit 7 is also set) + xchg ah,al + call .get_nibble_x + sub al,2 ; substract 512 + jmp short .get_match_length_1 + +.rep_match_or_16_bit: + test dl,020H ; test bit Z (offset bit 8) + jne .repeat_match ; rep-match + + ; 16 bit offset + lodsb ; Get 2-byte match offset + +.get_match_length_1: + xchg ah,al + lodsb ; load match offset bits 0-7 + +.get_match_length: + xchg bp,ax ; bp: offset +.repeat_match: + xchg ax,dx ; ax: original token + and al,07H ; isolate match length in token (MMM) + add al,2 ; add MIN_MATCH_SIZE_V2 + + cmp al,09H ; MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2? + jne .got_matchlen ; no, we have the full match length from the token, go copy + + call .get_nibble ; get extra literals length nibble + add al,cl ; add len from token to nibble + cmp al,018H ; MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2 + 15? + jne .got_matchlen ; no, we have the full match length from the token, go copy + + lodsb ; grab extra length byte + add al,018H ; overflow? + jnc .got_matchlen ; if not, we have the entire length + je short .done_decompressing ; detect EOD code + + lodsw ; grab 16-bit length + +.got_matchlen: + xchg cx,ax ; copy match length into cx + push ds ; save ds:si (current pointer to compressed data) + xchg si,ax + push es + pop ds + lea si,[bp+di] ; ds:si now points at back reference in output data + rep movsb ; copy match + xchg si,ax ; restore ds:si + pop ds + jmp .decode_token ; go decode another token + +.done_decompressing: + pop ax ; retrieve the original decompression offset + xchg di,ax ; compute decompressed size + sub ax,di + ret ; done + +.get_nibble_x: + cmc ; carry set if bit 4 was set + rcr al,1 + call .get_nibble ; get nibble for offset bits 0-3 + or al,cl ; merge nibble + rol al,1 + xor al,0E1H ; set offset bits 7-5 to 1 + ret + +.get_nibble: + neg bh ; nibble ready? + jns .has_nibble + + xchg bx,ax + lodsb ; load two nibbles + xchg bx,ax + +.has_nibble: + mov cl,4 ; swap 4 high and low bits of nibble + ror bl,cl + mov cl,0FH + and cl,bl + ret diff --git a/Tools/unix/lzsa/asm/8088/decompress_speed_v1.S b/Tools/unix/lzsa/asm/8088/decompress_speed_v1.S new file mode 100644 index 00000000..142bd15a --- /dev/null +++ b/Tools/unix/lzsa/asm/8088/decompress_speed_v1.S @@ -0,0 +1,236 @@ +; decompress_speed_v1.S - time-efficient decompressor implementation for 8088 +; NASM syntax. +; +; Usual DOS assembler SMALL model assumptions apply. This code: +; - Assumes it was invoked via NEAR call (change RET to RETF for FAR calls) +; - Is interrupt-safe +; - Is not re-entrant (do not decompress while already running decompression) +; - Trashes all data and segment registers +; +; Copyright (C) 2019 Jim Leonard, Emmanuel Marty +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. + + segment .text + bits 16 + +; --------------------------------------------------------------------------- +; Decompress raw LZSA1 block +; inputs: +; * ds:si: raw LZSA1 block +; * es:di: output buffer +; output: +; * ax: decompressed size +; --------------------------------------------------------------------------- + +; Must declare this in the code segment: +SHR4table: + DB 00h,00h,00h,00h,00h,00h,00h,00h,00h,00h,00h,00h,00h,00h,00h,00h + DB 01h,01h,01h,01h,01h,01h,01h,01h,01h,01h,01h,01h,01h,01h,01h,01h + DB 02h,02h,02h,02h,02h,02h,02h,02h,02h,02h,02h,02h,02h,02h,02h,02h + DB 03h,03h,03h,03h,03h,03h,03h,03h,03h,03h,03h,03h,03h,03h,03h,03h + DB 04h,04h,04h,04h,04h,04h,04h,04h,04h,04h,04h,04h,04h,04h,04h,04h + DB 05h,05h,05h,05h,05h,05h,05h,05h,05h,05h,05h,05h,05h,05h,05h,05h + DB 06h,06h,06h,06h,06h,06h,06h,06h,06h,06h,06h,06h,06h,06h,06h,06h + DB 07h,07h,07h,07h,07h,07h,07h,07h,07h,07h,07h,07h,07h,07h,07h,07h + +lzsa1_decompress_speed: + push di ;remember decompression offset + cld ;ensure string ops move forward + mov bx,SHR4table + xor cx,cx + +.decode_token: + xchg cx,ax ;clear ah (cx = 0 from match copy's rep movsb) + lodsb ;read token byte: O|LLL|MMMM + mov dx,ax ;copy our token to dl for later MMMM handling + + and al,070H ;isolate literals length in token (LLL) + jz .check_offset_size ;if LLL=0, we have no literals; goto match + cmp al,070H ;LITERALS_RUN_LEN? + jne .got_literals ;no, we have full count from token; go copy + + lodsb ;grab extra length byte + add al,07H ;add LITERALS_RUN_LEN + jnc .got_literals_exact ;if no overflow, we have full count + je .big_literals + +.mid_literals: + lodsb ;grab single extra length byte + inc ah ;add 256 + xchg cx,ax ;with longer counts, we can save some time + shr cx,1 ;by doing a word copy instead of a byte copy. + rep movsw ;We don't need to account for overlap because + adc cx,0 ;source for literals isn't the output buffer. + rep movsb + jmp .check_offset_size + +.big_literals: + lodsw ;grab 16-bit extra length + xchg cx,ax ;with longer counts, we can save some time + shr cx,1 ;by doing a word copy instead of a byte copy. + rep movsw + adc cx,0 + rep movsb + jmp .check_offset_size + +.got_literals: + cs xlat ;shift literals length into place +.got_literals_exact: + xchg cx,ax + rep movsb ;copy cx literals from ds:si to es:di + +.check_offset_size: + test dl,dl ;check match offset size in token (O bit) + js .get_long_offset ;load absolute 16-bit match offset + + mov ah,0ffh ;set up high byte + lodsb ;load low byte + +.get_match_length: + xchg dx,ax ;dx: match offset ax: original token + and al,0FH ;isolate match length in token (MMMM) + cmp al,0FH ;MATCH_RUN_LEN? + jne .got_matchlen_short ;no, we have the full match length from the token, go copy + + lodsb ;grab extra length byte + add al,012H ;add MIN_MATCH_SIZE + MATCH_RUN_LEN + jnc .do_long_copy ;if no overflow, we have the entire length + jne .mid_matchlen + + lodsw ;grab 16-bit length + xchg cx,ax ;get ready to do a long copy + jcxz .done_decompressing ;wait, is it the EOD marker? Exit if so + jmp .copy_len_preset ;otherwise, do the copy + +.get_long_offset: + lodsw ;Get 2-byte match offset + jmp .get_match_length + +.got_matchlen_short: + add al,3 ;add MIN_MATCH_SIZE + xchg cx,ax ;copy match length into cx + mov bp,ds ;save ds + mov ax,es + mov ds,ax ;ds=es + xchg ax,si ;save si + mov si,di ;ds:si now points at back reference in output data + add si,dx + rep movsb ;copy match + xchg si,ax ;restore si + mov ds,bp ;restore ds + jmp .decode_token ;go decode another token + +.done_decompressing: + pop ax ;retrieve the original decompression offset + xchg di,ax ;compute decompressed size + sub ax,di + ret ;done decompressing, exit to caller + +;With a confirmed longer match length, we have an opportunity to optimize for +;the case where a single byte is repeated long enough that we can benefit +;from rep movsw to perform the run (instead of rep movsb). +.mid_matchlen: + lodsb ;grab single extra length byte + inc ah ;add 256 +.do_long_copy: + xchg cx,ax ;copy match length into cx +.copy_len_preset: + push ds ;save ds + mov bp,es + mov ds,bp ;ds=es + mov bp,si ;save si + mov si,di ;ds:si now points at back reference in output data + add si,dx + cmp dx,-2 ;do we have a byte/word run to optimize? + jae .do_run ;perform a run +;You may be tempted to change "jae" to "jge" because DX is a signed number. +;Don't! The total window is 64k, so if you treat this as a signed comparison, +;you will get incorrect results for offsets over 32K. + +;If we're here, we have a long copy and it isn't byte-overlapping (if it +;overlapped, we'd be in .do_run_1) So, let's copy faster with REP MOVSW. +;This won't affect 8088 that much, but it speeds up 8086 and higher. + shr cx,1 + rep movsw + adc cx,0 + rep movsb + mov si,bp ;restore si + pop ds + jmp .decode_token ;go decode another token + +.do_run: + je .do_run_2 ;fall through to byte (common) if not word run + +.do_run_1: + lodsb ;load first byte of run into al + mov ah,al + shr cx,1 + rep stosw ;perform word run + adc cx,0 + rep stosb ;finish word run + mov si,bp ;restore si + pop ds + jmp .decode_token ;go decode another token + +.do_run_2: + lodsw ;load first word of run + shr cx,1 + rep stosw ;perform word run + adc cx,0 ;despite 2-byte offset, compressor might + rep stosb ;output odd length. better safe than sorry. + mov si,bp ;restore si + pop ds + jmp .decode_token ;go decode another token + +;Speed optimization history (decompression times in microseconds @ 4.77 MHz): +; original E. Marty code shuttle 123208 alice 65660 robotron 407338 *** +; table for shr al,4 shuttle 120964 alice 63230 robotron 394733 +++ +; push/pop to mov/mov shuttle 118176 alice 61835 robotron 386762 +++ +; movsw for literalcpys shuttle 124102 alice 64908 robotron 400220 --- rb +; stosw for byte runs shuttle 118897 alice 65040 robotron 403518 --- rb +; better stosw for runs shuttle 117712 alice 65040 robotron 403343 +-- +; disable RLE by default shuttle 116924 alice 60783 robotron 381226 +++ +; optimize got_matchlen shuttle 115294 alice 59588 robotron 374330 +++ +; fall through to getML shuttle 113258 alice 59572 robotron 372004 +++ +; fall through to midLI shuttle 113258 alice 59572 robotron 375060 ..- rb +; fall through midMaLen shuttle 113247 alice 59572 robotron 372004 +.+ +; movsw for litlen > 255 shuttle 113247 alice 59572 robotron 371612 ..+ +; rep stosw for long runs shuttle 113247 alice 59572 robotron 371612 ... +; rep movsw for long cpys shuttle 113247 alice 59572 robotron 371035 ..+ +; xchg/dec ah -> mov ah,val shuttle 112575 alice 59272 robotron 369198 +++ +; force >12h len.to longcpy shuttle 101998 alice 59266 robotron 364459 +.+ +; more efficient run branch shuttle 102239 alice 59297 robotron 364716 --- rb +; even more eff. run branch shuttle 101998 alice 59266 robotron 364459 *** +; BUGFIX - bad sign compare shuttle 101955 alice 59225 robotron 364117 +++ +; reverse 16-bit len compar shuttle 102000 alice 59263 robotron 364460 --- rb +; jcxz for EOD detection no change to speed, but is 1 byte shorter +++ +; force movsw for literals shuttle 107183 alice 62555 robotron 379524 --- rb +; defer shr4 until necessry shuttle 102069 alice 60236 robotron 364096 --- +; skip literals if LLL=0 shuttle 98655 alice 57849 robotron 363358 --- +; fall through to mid_liter shuttle 98595 alice 57789 robotron 361998 +++ +; == jumptable experiments begin == +; jumptable for small copys shuttle 101594 alice 61078 robotron 386018 --- +; start:xchg instead of mov shuttle 100948 alice 60467 robotron 381112 +++ +; use table for LLL=0 check shuttle 106972 alice 63333 robotron 388304 --- rb +; jmptbl to fallthrough mov shuttle 102532 alice 60760 robotron 383070 --- +; cpy fallthrough check_ofs shuttle 98939 alice 58917 robotron 371019 +** +; single jumptable jump shuttle 97528 alice 57264 robotron 362194 ++* +; conditional check for L=7 shuttle 98610 alice 58521 robotron 368153 --- rb +; rip out the jumptable :-/ shuttle 97616 alice 57128 robotron 360697 +++ +; defer add MIN_MATCH_SIZE shuttle 97250 alice 57004 robotron 361191 ++? +; cache constants in regs shuttle 104681 alice 59939 robotron 380125 --- rb diff --git a/Tools/unix/lzsa/asm/8088/decompress_speed_v2.S b/Tools/unix/lzsa/asm/8088/decompress_speed_v2.S new file mode 100644 index 00000000..973ae01b --- /dev/null +++ b/Tools/unix/lzsa/asm/8088/decompress_speed_v2.S @@ -0,0 +1,288 @@ +; decompress_speed_v2.S - LZSA v2 time-efficient decompressor implementation for 8088 +; NASM syntax. +; +; Usual DOS assembler SMALL model assumptions apply. This code: +; - Assumes it was invoked via NEAR call (change RET to RETF for FAR calls) +; - Is interrupt-safe +; - Is not re-entrant (do not decompress while already running decompression) +; - Trashes all data and segment registers +; +; Copyright (C) 2019 Jim Leonard, Emmanuel Marty +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. + + segment .text + bits 16 + +;While LZSA2 is technically capable of generating a match offset of -2, +;this sequence never actually showed up in my LZSA2 test corpus, likely due +;to compressor optimizations and the LZSA2 format itself. If you know your +;test data will contain a match offset of -2, you can enable code to write +;out the sequence very quickly at the cost of 18 bytes of code. +HANDLE_WORD_RUN EQU 0 + +; --------------------------------------------------------------------------- +; Decompress raw LZSA2 block +; inputs: +; * ds:si: raw LZSA2 block +; * es:di: output buffer +; output: +; * ax: decompressed size +; --------------------------------------------------------------------------- + +%macro get_nybble 0 + neg bh ;nybble ready? + jns %%has_nybble + xchg bx,ax + lodsb ;load two nybbles + xchg bx,ax +%%has_nybble: + mov cl,4 ;swap 4 high and low bits of nybble + ror bl,cl + mov cl,0FH + and cl,bl +%endmacro + +lzsa2_decompress_speed: + push di ;remember decompression offset + cld ;make string operations go forward + xor cx,cx + mov bx,0100H ;bx used by get_nybble + +.decode_token: + mov ax,cx ;clear ah - cx is zero (and must stay that way) + lodsb ;read token byte: XYZ|LL|MMMM + mov dx,ax ;keep copy of token in dl + + and al,018H ;isolate literals length in token (LL) + jz .check_offset ;no literals? stop decoding, go to matches + +;At this point, al can be in three (unshifted) states: 1, 2, or 3. +;3 = not done yet. + cmp al,(2 << 3) ;LITERALS_RUN_LEN_V2? (original: cmp al,03h) + jb .lit1b ;LZSA2 output 1-byte more often, so test first + je .lit2b + + mov cl,3 + shr al,cl ;shift literals length into place + get_nybble ;cl := get extra literals length nybble + add al,cl ;add len from token to nybble + cmp al,012H ;LITERALS_RUN_LEN_V2 + 15 ? + jne .got_literals ;if not, we have the full literals count + lodsb ;grab extra length byte + add al,012H ;overflow? + jnc .got_literals_big ;if not, we have a big full literals count + lodsw ;grab 16-bit extra length + +;For larger counts, it pays to set up a faster copy +.got_literals_big: + xchg cx,ax + shr cx,1 + rep movsw + adc cx,0 + rep movsb + jmp .check_offset + +.got_literals: + xchg cx,ax + rep movsb ;copy cx literals from ds:si to es:di + jmp .check_offset + +;LZSA2 likes to produce tiny literals of 1 or 2 bytes. Handle them here. +.lit2b:movsb +.lit1b:movsb + +.check_offset: + test dl,dl ;check match offset mode in token (X bit) + js .rep_match_or_large_offset + + cmp dl,040H ;check if this is a 5 or 9-bit offset (Y bit) + jnb .offset_9_bit + + ;5 bit offset: + xchg cx,ax ;clear ah - cx is zero from prior rep movs + mov al,020H ;shift Z (offset bit 4) in place + and al,dl + shl al,1 + shl al,1 + get_nybble ;get nybble for offset bits 0-3 + or al,cl ;merge nybble + rol al,1 + xor al,0E1H ;set offset bits 7-5 to 1 + dec ah ;set offset bits 15-8 to 1 + jmp .get_match_length + +.rep_match_or_16_bit: + test dl,020H ;test bit Z (offset bit 8) + jne .repeat_match ;rep-match + + ;16 bit offset: + lodsw ;Get 2-byte match offset + xchg ah,al + jmp .get_match_length + +.offset_9_bit: + ;9 bit offset: + xchg cx,ax ;clear ah - cx is zero from prior rep movs + lodsb ;get 8 bit offset from stream in A + dec ah ;set offset bits 15-8 to 1 + test dl,020H ;test bit Z (offset bit 8) + je .get_match_length + dec ah ;clear bit 8 if Z bit is clear + jmp .get_match_length + +.rep_match_or_large_offset: + cmp dl,0c0H ;check if this is a 13-bit offset + ;or a 16-bit offset/rep match (Y bit) + jnb .rep_match_or_16_bit + + ;13 bit offset: + mov ah,020H ;shift Z (offset bit 12) in place + and ah,dl + shl ah,1 + shl ah,1 + get_nybble ;get nybble for offset bits 8-11 + or ah,cl ;merge nybble + rol ah,1 + xor ah,0E1H ;set offset bits 15-13 to 1 + sub ah,2 ;substract 512 + lodsb ;load match offset bits 0-7 + +.get_match_length: + mov bp,ax ;bp:=offset +.repeat_match: + mov ax,dx ;ax: original token + and al,07H ;isolate match length in token (MMM) + add al,2 ;add MIN_MATCH_SIZE_V2 + + cmp al,09H ;MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2? + jne .got_matchlen ;no, we have full match length from token + + get_nybble ;get extra literals length nybble + add al,cl ;add len from token to nybble + cmp al,018H ;MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2 + 15? + jne .got_matchlen ;no, we have full match length from token + + lodsb ;grab extra length byte + add al,018H ;overflow? + jnc .got_matchlen_big ;if not, we have entire (big) length + je .done_decompressing ; detect EOD code + + lodsw ;grab 16-bit length + +;If we're here, we have a larger match copy and can optimize how we do that +.got_matchlen_big: + xchg cx,ax ;copy match length into cx + mov dx,ds ;save ds + mov ax,es + mov ds,ax ;ds:=es + xchg si,ax ;dx:ax = old ds:si + mov si,di ;ds:si now points at back reference in output data + add si,bp +%if HANDLE_WORD_RUN + cmp bp,-2 ;do we have a byte/word run to optimize? + jae .do_run ;perform a run +%else + cmp bp,-1 ;do we have a byte run to optimize? + je .do_run_1 ;perform a byte run +%endif +;You may be tempted to change "jae" to "jge" because DX is a signed number. +;Don't! The total window is 64k, so if you treat this as a signed comparison, +;you will get incorrect results for offsets over 32K. +; +;If we're here, we have a long copy and it isn't byte-overlapping (if it +;overlapped, we'd be in .do_run_1) So, let's copy faster with REP MOVSW. +;This won't affect 8088 that much, but it speeds up 8086 and higher. + shr cx,1 + rep movsw + adc cx,0 + rep movsb + xchg si,ax + mov ds,dx ;restore ds:si + jmp .decode_token ;go decode another token + +;Smaller match copies handled here: +.got_matchlen: + xchg cx,ax ;copy match length into cx + mov dx,ds ;save ds + mov ax,es + mov ds,ax ;ds:=es + xchg si,ax ;dx:ax = old ds:si + mov si,di ;ds:si = back reference in output data + add si,bp + rep movsb ;copy match + xchg si,ax + mov ds,dx ;restore ds:si + jmp .decode_token ;go decode another token + +.done_decompressing: + pop ax ;retrieve the original decompression offset + xchg di,ax ;compute decompressed size + sub ax,di + ret ;done + +%if HANDLE_WORD_RUN +.do_run: + je .do_run_2 ;fall through to byte (common) if not word run +%endif + +.do_run_1: + push ax + lodsb ;load first byte of run into al + mov ah,al + shr cx,1 + rep stosw ;perform word run + adc cx,0 + rep stosb ;finish word run + pop si + mov ds,dx + jmp .decode_token ;go decode another token + +%if HANDLE_WORD_RUN +.do_run_2: + push ax + lodsw ;load first word of run + shr cx,1 + rep stosw ;perform word run + adc cx,0 ;despite 2-byte offset, compressor might + rep stosb ;output odd length. better safe than sorry. + pop si + mov ds,dx + jmp .decode_token ;go decode another token +%endif + +;Speed optimization history (decompression times in microseconds @ 4.77 MHz): +;Compression corpus:shuttle alice robotro rletest largetx linewar ...... .. +;Start of exercise 160828 113311 665900 238507 1053865 1004237 ****** +;add al,val -> al,cl 160813 113296 668721 237484 1053604 1003815 ++-+++ +;sub ah,2 -> dec dec 160907 113585 666744 237484 1056651 1005172 --+*-- rb +;mov ax,cx->xchgcxax 159741 112460 660594 237477 1046770 998323 ++++++ +;unroll get_nibble 152552 106327 621119 237345 982381 942373 ++++++ +;early exit if LL=0 147242 103842 615559 239318 946863 942932 +++-+- +;push/pop->mov/mov 145447 100832 604822 237288 927017 931366 ++++++ +;push/pop->mov/mov(2)143214 98817 592920 239298 908217 910955 +++-++ +;rep stos for -1, -2 143289 102812 617087 237164 942081 940688 ---+-- rb +;larger literal cpys 143214 98817 591940 238296 907237 909657 **++++ +;larger copys & runs 132440 98802 586551 178768 904129 896709 ++++++ :-) +;smaller lit. copies 131991 99131 583933 177760 901824 898308 +-+++- +;swap smal lit compa 131828 99022 585121 177757 901793 894054 ++-*++ +;compare before shif 130587 95970 569908 177753 889221 872461 +++*++ +;getmatchlength base 130587 95970 570634 177753 893536 871556 ...... === +; f->rep_match_or_16 xxxxxx xxxxx 569910 xxxxxx 889266 871435 ..+.++ +; f->rep_match_or_la 129966 94748 566169 xxxxxx 880870 867030 +++.++ +++ +; f->offset_9_bit 132126 95258 568869 xxxxxx 893169 870364 -++.-+ +;final fallthrough 129966 94748 566169 177753 880870 865023 ****** diff --git a/Tools/unix/lzsa/asm/x86/decompress_small_v1.asm b/Tools/unix/lzsa/asm/x86/decompress_small_v1.asm new file mode 100644 index 00000000..41ce991c --- /dev/null +++ b/Tools/unix/lzsa/asm/x86/decompress_small_v1.asm @@ -0,0 +1,120 @@ +; decompress_small_v1.asm - space-efficient decompressor implementation for x86 +; +; Copyright (C) 2019 Emmanuel Marty +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. + + segment .text + bits 32 + +; --------------------------------------------------------------------------- +; Decompress raw LZSA1 block +; inputs: +; * esi: raw LZSA1 block +; * edi: output buffer +; output: +; * eax: decompressed size +; --------------------------------------------------------------------------- + + %ifndef BIN + global lzsa1_decompress + global _lzsa1_decompress + %endif + +lzsa1_decompress: +_lzsa1_decompress: + pushad + + ;mov edi, [esp+32+4] ; edi = outbuf + ;mov esi, [esp+32+8] ; esi = inbuf + + xor ecx, ecx +.decode_token: + mul ecx + lodsb ; read token byte: O|LLL|MMMM + mov dl, al ; keep token in dl + + and al, 070H ; isolate literals length in token (LLL) + shr al, 4 ; shift literals length into place + + cmp al, 07H ; LITERALS_RUN_LEN? + jne .got_literals ; no, we have the full literals count from the token, go copy + + lodsb ; grab extra length byte + add al, 07H ; add LITERALS_RUN_LEN + jnc .got_literals ; if no overflow, we have the full literals count, go copy + jne .mid_literals + + lodsw ; grab 16-bit extra length + jmp .got_literals + +.mid_literals: + lodsb ; grab single extra length byte + inc ah ; add 256 + +.got_literals: + xchg ecx, eax + rep movsb ; copy cx literals from ds:si to es:di + + test dl, dl ; check match offset size in token (O bit) + js .get_long_offset + + dec ecx + xchg eax, ecx ; clear ah - cx is zero from the rep movsb above + lodsb + jmp .get_match_length + +.get_long_offset: + lodsw ; Get 2-byte match offset + +.get_match_length: + xchg eax, edx ; edx: match offset eax: original token + and al, 0FH ; isolate match length in token (MMMM) + add al, 3 ; add MIN_MATCH_SIZE + + cmp al, 012H ; MATCH_RUN_LEN? + jne .got_matchlen ; no, we have the full match length from the token, go copy + + lodsb ; grab extra length byte + add al,012H ; add MIN_MATCH_SIZE + MATCH_RUN_LEN + jnc .got_matchlen ; if no overflow, we have the entire length + jne .mid_matchlen + + lodsw ; grab 16-bit length + test eax, eax ; bail if we hit EOD + je .done_decompressing + jmp .got_matchlen + +.mid_matchlen: + lodsb ; grab single extra length byte + inc ah ; add 256 + +.got_matchlen: + xchg ecx, eax ; copy match length into ecx + xchg esi, eax + mov esi, edi ; esi now points at back reference in output data + movsx edx, dx ; sign-extend dx to 32-bits. + add esi, edx + rep movsb ; copy match + xchg esi, eax ; restore esi + jmp .decode_token ; go decode another token + +.done_decompressing: + sub edi, [esp+32+4] + mov [esp+28], edi ; eax = decompressed size + popad + ret ; done diff --git a/Tools/unix/lzsa/asm/x86/decompress_small_v2.asm b/Tools/unix/lzsa/asm/x86/decompress_small_v2.asm new file mode 100644 index 00000000..fe185c1d --- /dev/null +++ b/Tools/unix/lzsa/asm/x86/decompress_small_v2.asm @@ -0,0 +1,181 @@ +; decompress_small_v2.asm - space-efficient decompressor implementation for x86 +; +; Copyright (C) 2019 Emmanuel Marty +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. + + segment .text + bits 32 + +; --------------------------------------------------------------------------- +; Decompress raw LZSA2 block +; inputs: +; * esi: raw LZSA2 block +; * edi: output buffer +; output: +; * eax: decompressed size +; --------------------------------------------------------------------------- + + %ifndef BIN + global lzsa2_decompress + global _lzsa2_decompress + %endif + +lzsa2_decompress: +_lzsa2_decompress: + pushad + + ;mov edi, [esp+32+4] ; edi = outbuf + ;mov esi, [esp+32+8] ; esi = inbuf + + xor ecx, ecx + xor ebx, ebx ; ebx = 0100H + inc bh + xor ebp, ebp + +.decode_token: + mul ecx + lodsb ; read token byte: XYZ|LL|MMMM + mov dl, al ; keep token in dl + + and al, 018H ; isolate literals length in token (LL) + shr al, 3 ; shift literals length into place + + cmp al, 03H ; LITERALS_RUN_LEN_V2? + jne .got_literals ; no, we have the full literals count from the token, go copy + + call .get_nibble ; get extra literals length nibble + add al, cl ; add len from token to nibble + cmp al, 012H ; LITERALS_RUN_LEN_V2 + 15 ? + jne .got_literals ; if not, we have the full literals count, go copy + + lodsb ; grab extra length byte + add al,012H ; overflow? + jnc .got_literals ; if not, we have the full literals count, go copy + + lodsw ; grab 16-bit extra length + +.got_literals: + xchg ecx, eax + rep movsb ; copy ecx literals from esi to edi + + test dl, 0C0h ; check match offset mode in token (X bit) + js .rep_match_or_large_offset + + ;;cmp dl,040H ; check if this is a 5 or 9-bit offset (Y bit) + ; discovered via the test with bit 6 set + xchg ecx, eax ; clear ah - cx is zero from the rep movsb above + jne .offset_9_bit + + ; 5 bit offset + cmp dl, 020H ; test bit 5 + call .get_nibble_x + jmp .dec_offset_top + +.offset_9_bit: ; 9 bit offset + lodsb ; get 8 bit offset from stream in A + dec ah ; set offset bits 15-8 to 1 + test dl, 020H ; test bit Z (offset bit 8) + je .get_match_length +.dec_offset_top: + dec ah ; clear bit 8 if Z bit is clear + ; or set offset bits 15-8 to 1 + jmp .get_match_length + +.rep_match_or_large_offset: + ;;cmp dl,0c0H ; check if this is a 13-bit offset or a 16-bit offset/rep match (Y bit) + jpe .rep_match_or_16_bit + + ; 13 bit offset + + cmp dl, 0A0H ; test bit 5 (knowing that bit 7 is also set) + xchg ah, al + call .get_nibble_x + sub al, 2 ; substract 512 + jmp .get_match_length_1 + +.rep_match_or_16_bit: + test dl, 020H ; test bit Z (offset bit 8) + jne .repeat_match ; rep-match + + ; 16 bit offset + lodsb ; Get 2-byte match offset + +.get_match_length_1: + xchg ah, al + lodsb ; load match offset bits 0-7 + +.get_match_length: + xchg ebp, eax ; ebp: offset +.repeat_match: + xchg eax, edx ; ax: original token + and al, 07H ; isolate match length in token (MMM) + add al, 2 ; add MIN_MATCH_SIZE_V2 + + cmp al, 09H ; MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2? + jne .got_matchlen ; no, we have the full match length from the token, go copy + + call .get_nibble ; get extra literals length nibble + add al, cl ; add len from token to nibble + cmp al, 018H ; MIN_MATCH_SIZE_V2 + MATCH_RUN_LEN_V2 + 15? + jne .got_matchlen ; no, we have the full match length from the token, go copy + + lodsb ; grab extra length byte + add al,018H ; overflow? + jnc .got_matchlen ; if not, we have the entire length + je .done_decompressing ; detect EOD code + + lodsw ; grab 16-bit length + +.got_matchlen: + xchg ecx, eax ; copy match length into ecx + xchg esi, eax + movsx ebp, bp ; sign-extend bp to 32-bits + lea esi,[ebp+edi] ; esi now points at back reference in output data + rep movsb ; copy match + xchg esi, eax ; restore esi + jmp .decode_token ; go decode another token + +.done_decompressing: + sub edi, [esp+32+4] + mov [esp+28], edi + popad + ret ; done + +.get_nibble_x: + cmc ; carry set if bit 4 was set + rcr al, 1 + call .get_nibble ; get nibble for offset bits 0-3 + or al, cl ; merge nibble + rol al, 1 + xor al, 0E1H ; set offset bits 7-5 to 1 + ret + +.get_nibble: + neg bh ; nibble ready? + jns .has_nibble + + xchg ebx, eax + lodsb ; load two nibbles + xchg ebx, eax + +.has_nibble: + mov cl, 4 ; swap 4 high and low bits of nibble + ror bl, cl + mov cl, 0FH + and cl, bl + ret diff --git a/Tools/unix/lzsa/asm/z80/unlzsa1_fast.asm b/Tools/unix/lzsa/asm/z80/unlzsa1_fast.asm new file mode 100644 index 00000000..79b5c613 --- /dev/null +++ b/Tools/unix/lzsa/asm/z80/unlzsa1_fast.asm @@ -0,0 +1,201 @@ +; +; Speed-optimized LZSA1 decompressor by spke & uniabis (109 bytes) +; +; ver.00 by spke for LZSA 0.5.4 (03-24/04/2019, 134 bytes); +; ver.01 by spke for LZSA 0.5.6 (25/04/2019, 110(-24) bytes, +0.2% speed); +; ver.02 by spke for LZSA 1.0.5 (24/07/2019, added support for backward decompression); +; ver.03 by uniabis (30/07/2019, 109(-1) bytes, +3.5% speed); +; ver.04 by spke (31/07/2019, small re-organization of macros); +; ver.05 by uniabis (22/08/2019, 107(-2) bytes, same speed); +; ver.06 by spke for LZSA 1.0.7 (27/08/2019, 111(+4) bytes, +2.1% speed); +; ver.07 by spke for LZSA 1.1.0 (25/09/2019, added full revision history); +; ver.08 by spke for LZSA 1.1.2 (22/10/2019, re-organized macros and added an option for unrolled copying of long matches); +; ver.09 by spke for LZSA 1.2.1 (02/01/2020, 109(-2) bytes, same speed) +; +; The data must be compressed using the command line compressor by Emmanuel Marty +; The compression is done as follows: +; +; lzsa.exe -f1 -r +; +; where option -r asks for the generation of raw (frame-less) data. +; +; The decompression is done in the standard way: +; +; ld hl,FirstByteOfCompressedData +; ld de,FirstByteOfMemoryForDecompressedData +; call DecompressLZSA1 +; +; Backward compression is also supported; you can compress files backward using: +; +; lzsa.exe -f1 -r -b +; +; and decompress the resulting files using: +; +; ld hl,LastByteOfCompressedData +; ld de,LastByteOfMemoryForDecompressedData +; call DecompressLZSA1 +; +; (do not forget to uncomment the BACKWARD_DECOMPRESS option in the decompressor). +; +; Of course, LZSA compression algorithms are (c) 2019 Emmanuel Marty, +; see https://github.com/emmanuel-marty/lzsa for more information +; +; Drop me an email if you have any comments/ideas/suggestions: zxintrospec@gmail.com +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. + +; DEFINE UNROLL_LONG_MATCHES ; uncomment for faster decompression of very compressible data (+57 bytes) +; DEFINE BACKWARD_DECOMPRESS + + IFNDEF BACKWARD_DECOMPRESS + + MACRO NEXT_HL + inc hl + ENDM + + MACRO ADD_OFFSET + ex de,hl : add hl,de + ENDM + + MACRO COPY1 + ldi + ENDM + + MACRO COPYBC + ldir + ENDM + + ELSE + + MACRO NEXT_HL + dec hl + ENDM + + MACRO ADD_OFFSET + ex de,hl : ld a,e : sub l : ld l,a + ld a,d : sbc h : ld h,a ; 4*4+3*4 = 28t / 7 bytes + ENDM + + MACRO COPY1 + ldd + ENDM + + MACRO COPYBC + lddr + ENDM + + ENDIF + +@DecompressLZSA1: + ld b,0 : jr ReadToken + +NoLiterals: xor (hl) : NEXT_HL : jp m,LongOffset + +ShortOffset: push de : ld e,(hl) : ld d,#FF + + ; short matches have length 0+3..14+3 + add 3 : cp 15+3 : jr nc,LongerMatch + + ; placed here this saves a JP per iteration +CopyMatch: ld c,a +.UseC NEXT_HL : ex (sp),hl ; BC = len, DE = offset, HL = dest, SP ->[dest,src] + ADD_OFFSET ; BC = len, DE = dest, HL = dest-offset, SP->[src] + COPY1 : COPY1 : COPYBC ; BC = 0, DE = dest +.popSrc pop hl ; HL = src + +ReadToken: ; first a byte token "O|LLL|MMMM" is read from the stream, + ; where LLL is the number of literals and MMMM is + ; a length of the match that follows after the literals + ld a,(hl) : and #70 : jr z,NoLiterals + + cp #70 : jr z,MoreLiterals ; LLL=7 means 7+ literals... + rrca : rrca : rrca : rrca : ld c,a ; LLL<7 means 0..6 literals... + + ld a,(hl) : NEXT_HL + COPYBC + + ; the top bit of token is set if the offset contains two bytes + and #8F : jp p,ShortOffset + +LongOffset: ; read second byte of the offset + push de : ld e,(hl) : NEXT_HL : ld d,(hl) + add -128+3 : cp 15+3 : jp c,CopyMatch + + IFNDEF UNROLL_LONG_MATCHES + + ; MMMM=15 indicates a multi-byte number of literals +LongerMatch: NEXT_HL : add (hl) : jr nc,CopyMatch + + ; the codes are designed to overflow; + ; the overflow value 1 means read 1 extra byte + ; and overflow value 0 means read 2 extra bytes +.code1 ld b,a : NEXT_HL : ld c,(hl) : jr nz,CopyMatch.UseC +.code0 NEXT_HL : ld b,(hl) + + ; the two-byte match length equal to zero + ; designates the end-of-data marker + ld a,b : or c : jr nz,CopyMatch.UseC + pop de : ret + + ELSE + + ; MMMM=15 indicates a multi-byte number of literals +LongerMatch: NEXT_HL : add (hl) : jr c,VeryLongMatch + + ld c,a +.UseC NEXT_HL : ex (sp),hl + ADD_OFFSET + COPY1 : COPY1 + + ; this is an unrolled equivalent of LDIR + xor a : sub c + and 16-1 : add a + ld (.jrOffset),a : jr nz,$+2 +.jrOffset EQU $-1 +.fastLDIR DUP 16 + COPY1 + EDUP + jp pe,.fastLDIR + jp CopyMatch.popSrc + +VeryLongMatch: ; the codes are designed to overflow; + ; the overflow value 1 means read 1 extra byte + ; and overflow value 0 means read 2 extra bytes +.code1 ld b,a : NEXT_HL : ld c,(hl) : jr nz,LongerMatch.UseC +.code0 NEXT_HL : ld b,(hl) + + ; the two-byte match length equal to zero + ; designates the end-of-data marker + ld a,b : or c : jr nz,LongerMatch.UseC + pop de : ret + + ENDIF + +MoreLiterals: ; there are three possible situations here + xor (hl) : NEXT_HL : exa + ld a,7 : add (hl) : jr c,ManyLiterals + +CopyLiterals: ld c,a +.UseC NEXT_HL : COPYBC + + exa : jp p,ShortOffset : jr LongOffset + +ManyLiterals: +.code1 ld b,a : NEXT_HL : ld c,(hl) : jr nz,CopyLiterals.UseC +.code0 NEXT_HL : ld b,(hl) : jr CopyLiterals.UseC + + diff --git a/Tools/unix/lzsa/asm/z80/unlzsa1_small.asm b/Tools/unix/lzsa/asm/z80/unlzsa1_small.asm new file mode 100644 index 00000000..8592f928 --- /dev/null +++ b/Tools/unix/lzsa/asm/z80/unlzsa1_small.asm @@ -0,0 +1,135 @@ +; +; Size-optimized LZSA1 decompressor by spke & uniabis (67 bytes) +; +; ver.00 by spke for LZSA 0.5.4 (23/04/2019, 69 bytes); +; ver.01 by spke for LZSA 1.0.5 (24/07/2019, added support for backward decompression); +; ver.02 by uniabis (30/07/2019, 68(-1) bytes, +3.2% speed); +; ver.03 by spke for LZSA 1.0.7 (31/07/2019, small re-organization of macros); +; ver.04 by spke (06/08/2019, 67(-1) bytes, -1.2% speed); +; ver.05 by spke for LZSA 1.1.0 (25/09/2019, added full revision history) +; +; The data must be compressed using the command line compressor by Emmanuel Marty +; The compression is done as follows: +; +; lzsa.exe -f1 -r +; +; where option -r asks for the generation of raw (frame-less) data. +; +; The decompression is done in the standard way: +; +; ld hl,FirstByteOfCompressedData +; ld de,FirstByteOfMemoryForDecompressedData +; call DecompressLZSA1 +; +; Backward compression is also supported; you can compress files backward using: +; +; lzsa.exe -f1 -r -b +; +; and decompress the resulting files using: +; +; ld hl,LastByteOfCompressedData +; ld de,LastByteOfMemoryForDecompressedData +; call DecompressLZSA1 +; +; (do not forget to uncomment the BACKWARD_DECOMPRESS option in the decompressor). +; +; Of course, LZSA compression algorithms are (c) 2019 Emmanuel Marty, +; see https://github.com/emmanuel-marty/lzsa for more information +; +; Drop me an email if you have any comments/ideas/suggestions: zxintrospec@gmail.com +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. + +; DEFINE BACKWARD_DECOMPRESS + + IFNDEF BACKWARD_DECOMPRESS + + MACRO NEXT_HL + inc hl + ENDM + + MACRO ADD_OFFSET + ex de,hl : add hl,de + ENDM + + MACRO BLOCKCOPY + ldir + ENDM + + ELSE + + MACRO NEXT_HL + dec hl + ENDM + + MACRO ADD_OFFSET + push hl : or a : sbc hl,de : pop de ; 11+4+15+10 = 40t / 5 bytes + ENDM + + MACRO BLOCKCOPY + lddr + ENDM + + ENDIF + +@DecompressLZSA1: + ld b,0 + + ; first a byte token "O|LLL|MMMM" is read from the stream, + ; where LLL is the number of literals and MMMM is + ; a length of the match that follows after the literals +ReadToken: ld a,(hl) : NEXT_HL : push af + and #70 : jr z,NoLiterals + + rrca : rrca : rrca : rrca ; LLL<7 means 0..6 literals... + cp #07 : call z,ReadLongBA ; LLL=7 means 7+ literals... + + ld c,a : BLOCKCOPY + + ; next we read the low byte of the -offset +NoLiterals: pop af : push de : ld e,(hl) : NEXT_HL : ld d,#FF + ; the top bit of token is set if + ; the offset contains the high byte as well + or a : jp p,ShortOffset + +LongOffset: ld d,(hl) : NEXT_HL + + ; last but not least, the match length is read +ShortOffset: and #0F : add 3 ; MMMM<15 means match lengths 0+3..14+3 + cp 15+3 : call z,ReadLongBA ; MMMM=15 means lengths 14+3+ + ld c,a + + ex (sp),hl ; BC = len, DE = -offset, HL = dest, SP -> [src] + ADD_OFFSET ; BC = len, DE = dest, HL = dest+(-offset), SP -> [src] + BLOCKCOPY ; BC = 0, DE = dest + pop hl : jr ReadToken ; HL = src + + ; a standard routine to read extended codes + ; into registers B (higher byte) and A (lower byte). +ReadLongBA: add (hl) : NEXT_HL : ret nc + + ; the codes are designed to overflow; + ; the overflow value 1 means read 1 extra byte + ; and overflow value 0 means read 2 extra bytes +.code1: ld b,a : ld a,(hl) : NEXT_HL : ret nz +.code0: ld c,a : ld b,(hl) : NEXT_HL + + ; the two-byte match length equal to zero + ; designates the end-of-data marker + or b : ld a,c : ret nz + pop de : pop de : ret + diff --git a/Tools/unix/lzsa/asm/z80/unlzsa2_fast.asm b/Tools/unix/lzsa/asm/z80/unlzsa2_fast.asm new file mode 100644 index 00000000..57026da7 --- /dev/null +++ b/Tools/unix/lzsa/asm/z80/unlzsa2_fast.asm @@ -0,0 +1,281 @@ +; +; Speed-optimized LZSA2 decompressor by spke & uniabis (216 bytes) +; +; ver.00 by spke for LZSA 1.0.0 (02-07/06/2019, 218 bytes); +; ver.01 by spke for LZSA 1.0.5 (24/07/2019, added support for backward decompression); +; ver.02 by spke for LZSA 1.0.6 (27/07/2019, fixed a bug in the backward decompressor); +; ver.03 by uniabis (30/07/2019, 213(-5) bytes, +3.8% speed and support for Hitachi HD64180); +; ver.04 by spke for LZSA 1.0.7 (01/08/2019, 214(+1) bytes, +0.2% speed and small re-organization of macros); +; ver.05 by spke (27/08/2019, 216(+2) bytes, +1.1% speed); +; ver.06 by spke for LZSA 1.1.0 (26/09/2019, added full revision history); +; ver.07 by spke for LZSA 1.1.1 (10/10/2019, +0.2% speed and an option for unrolled copying of long matches) +; +; The data must be compressed using the command line compressor by Emmanuel Marty +; The compression is done as follows: +; +; lzsa.exe -f2 -r +; +; where option -r asks for the generation of raw (frame-less) data. +; +; The decompression is done in the standard way: +; +; ld hl,FirstByteOfCompressedData +; ld de,FirstByteOfMemoryForDecompressedData +; call DecompressLZSA2 +; +; Backward compression is also supported; you can compress files backward using: +; +; lzsa.exe -f2 -r -b +; +; and decompress the resulting files using: +; +; ld hl,LastByteOfCompressedData +; ld de,LastByteOfMemoryForDecompressedData +; call DecompressLZSA2 +; +; (do not forget to uncomment the BACKWARD_DECOMPRESS option in the decompressor). +; +; Of course, LZSA2 compression algorithms are (c) 2019 Emmanuel Marty, +; see https://github.com/emmanuel-marty/lzsa for more information +; +; Drop me an email if you have any comments/ideas/suggestions: zxintrospec@gmail.com +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. + +; DEFINE UNROLL_LONG_MATCHES ; uncomment for faster decompression of very compressible data (+38 bytes) +; DEFINE BACKWARD_DECOMPRESS ; uncomment for data compressed with option -b +; DEFINE HD64180 ; uncomment for systems using Hitachi HD64180 + + IFNDEF BACKWARD_DECOMPRESS + + MACRO NEXT_HL + inc hl + ENDM + + MACRO ADD_OFFSET + ex de,hl : add hl,de + ENDM + + MACRO COPY1 + ldi + ENDM + + MACRO COPYBC + ldir + ENDM + + ELSE + + MACRO NEXT_HL + dec hl + ENDM + + MACRO ADD_OFFSET + ex de,hl : ld a,e : sub l : ld l,a + ld a,d : sbc h : ld h,a ; 4*4+3*4 = 28t / 7 bytes + ENDM + + MACRO COPY1 + ldd + ENDM + + MACRO COPYBC + lddr + ENDM + + ENDIF + + IFNDEF HD64180 + + MACRO LD_IX_DE + ld ixl,e : ld ixh,d + ENDM + + MACRO LD_DE_IX + ld e,ixl : ld d,ixh + ENDM + + ELSE + + MACRO LD_IX_DE + push de : pop ix + ENDM + + MACRO LD_DE_IX + push ix : pop de + ENDM + + ENDIF + +@DecompressLZSA2: + ; A' stores next nibble as %1111.... or assumed to contain trash + ; B is assumed to be 0 + ld b,0 : scf : exa : jr ReadToken + + + + +ManyLiterals: ld a,18 : add (hl) : NEXT_HL : jr nc,CopyLiterals + ld c,(hl) : NEXT_HL + ld a,b : ld b,(hl) + jr ReadToken.NEXTHLuseBC + + + + +MoreLiterals: ld b,(hl) : NEXT_HL + scf : exa : jr nc,.noUpdate + + ld a,(hl) : or #F0 : exa + ld a,(hl) : NEXT_HL : or #0F + rrca : rrca : rrca : rrca + +.noUpdate ;sub #F0-3 : cp 15+3 : jr z,ManyLiterals + inc a : jr z,ManyLiterals : sub #F0-3+1 + +CopyLiterals: ld c,a : ld a,b : ld b,0 + COPYBC + push de : or a : jp p,CASE0xx ;: jr CASE1xx + + cp %11000000 : jr c,CASE10x + +CASE11x cp %11100000 : jr c,CASE110 + + ; "111": repeated offset +CASE111: LD_DE_IX : jr MatchLen + + + + +Literals0011: jr nz,MoreLiterals + + ; if "LL" of the byte token is equal to 0, + ; there are no literals to copy +NoLiterals: or (hl) : NEXT_HL + push de : jp m,CASE1xx + + ; short (5 or 9 bit long) offsets +CASE0xx ld d,#FF : cp %01000000 : jr c,CASE00x + + ; "01x": the case of the 9-bit offset +CASE01x: cp %01100000 : rl d + +ReadOffsetE ld e,(hl) : NEXT_HL + +SaveOffset: LD_IX_DE + +MatchLen: inc a : and %00000111 : jr z,LongerMatch : inc a + +CopyMatch: ld c,a +.useC ex (sp),hl ; BC = len, DE = offset, HL = dest, SP ->[dest,src] + ADD_OFFSET ; BC = len, DE = dest, HL = dest-offset, SP->[src] + COPY1 + COPYBC +.popSrc pop hl + + ; compressed data stream contains records + ; each record begins with the byte token "XYZ|LL|MMM" +ReadToken: ld a,(hl) : and %00011000 : jp pe,Literals0011 ; process the cases 00 and 11 separately + + rrca : rrca : rrca + + ld c,a : ld a,(hl) ; token is re-read for further processing +.NEXTHLuseBC NEXT_HL + COPYBC + + ; the token and literals are followed by the offset + push de : or a : jp p,CASE0xx + +CASE1xx cp %11000000 : jr nc,CASE11x + + ; "10x": the case of the 13-bit offset +CASE10x: ld c,a : exa : jr nc,.noUpdate + + ld a,(hl) : or #F0 : exa + ld a,(hl) : NEXT_HL : or #0F + rrca : rrca : rrca : rrca + +.noUpdate ld d,a : ld a,c + cp %10100000 : dec d : rl d : jr ReadOffsetE + + + + ; "110": 16-bit offset +CASE110: ld d,(hl) : NEXT_HL : jr ReadOffsetE + + + + + ; "00x": the case of the 5-bit offset +CASE00x: ld c,a : exa : jr nc,.noUpdate + + ld a,(hl) : or #F0 : exa + ld a,(hl) : NEXT_HL : or #0F + rrca : rrca : rrca : rrca + +.noUpdate ld e,a : ld a,c + cp %00100000 : rl e : jp SaveOffset + + + + + +LongerMatch: scf : exa : jr nc,.noUpdate + + ld a,(hl) : or #F0 : exa + ld a,(hl) : NEXT_HL : or #0F + rrca : rrca : rrca : rrca + +.noUpdate sub #F0-9 : cp 15+9 : jr c,CopyMatch + + IFNDEF UNROLL_LONG_MATCHES + +LongMatch: add (hl) : NEXT_HL : jr nc,CopyMatch + ld c,(hl) : NEXT_HL + ld b,(hl) : NEXT_HL : jr nz,CopyMatch.useC + pop de : ret + + ELSE + +LongMatch: add (hl) : NEXT_HL : jr c,VeryLongMatch + + ld c,a +.useC ex (sp),hl + ADD_OFFSET + COPY1 + + ; this is an unrolled equivalent of LDIR + xor a : sub c + and 8-1 : add a + ld (.jrOffset),a : jr nz,$+2 +.jrOffset EQU $-1 +.fastLDIR DUP 8 + COPY1 + EDUP + jp pe,.fastLDIR + jp CopyMatch.popSrc + +VeryLongMatch: ld c,(hl) : NEXT_HL + ld b,(hl) : NEXT_HL : jr nz,LongMatch.useC + pop de : ret + + ENDIF + + + + + diff --git a/Tools/unix/lzsa/asm/z80/unlzsa2_small.asm b/Tools/unix/lzsa/asm/z80/unlzsa2_small.asm new file mode 100644 index 00000000..0544c9a7 --- /dev/null +++ b/Tools/unix/lzsa/asm/z80/unlzsa2_small.asm @@ -0,0 +1,187 @@ +; +; Size-optimized LZSA2 decompressor by spke & uniabis (139 bytes) +; +; ver.00 by spke for LZSA 1.0.0 (02-09/06/2019, 145 bytes); +; ver.01 by spke for LZSA 1.0.5 (24/07/2019, added support for backward decompression); +; ver.02 by uniabis (30/07/2019, 144(-1) bytes, +3.3% speed and support for Hitachi HD64180); +; ver.03 by spke for LZSA 1.0.7 (01/08/2019, 140(-4) bytes, -1.4% speed and small re-organization of macros); +; ver.04 by spke for LZSA 1.1.0 (26/09/2019, removed usage of IY, added full revision history) +; ver.05 by spke for LZSA 1.1.1 (11/10/2019, 139(-1) bytes, +0.1% speed) +; +; The data must be compressed using the command line compressor by Emmanuel Marty +; The compression is done as follows: +; +; lzsa.exe -f2 -r +; +; where option -r asks for the generation of raw (frame-less) data. +; +; The decompression is done in the standard way: +; +; ld hl,FirstByteOfCompressedData +; ld de,FirstByteOfMemoryForDecompressedData +; call DecompressLZSA2 +; +; Backward compression is also supported; you can compress files backward using: +; +; lzsa.exe -f2 -r -b +; +; and decompress the resulting files using: +; +; ld hl,LastByteOfCompressedData +; ld de,LastByteOfMemoryForDecompressedData +; call DecompressLZSA2 +; +; (do not forget to uncomment the BACKWARD_DECOMPRESS option in the decompressor). +; +; Of course, LZSA2 compression algorithms are (c) 2019 Emmanuel Marty, +; see https://github.com/emmanuel-marty/lzsa for more information +; +; Drop me an email if you have any comments/ideas/suggestions: zxintrospec@gmail.com +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software. +; 3. This notice may not be removed or altered from any source distribution. +; + +; DEFINE BACKWARD_DECOMPRESS ; uncomment for data compressed with option -b +; DEFINE HD64180 ; uncomment for systems using Hitachi HD64180 + + IFNDEF BACKWARD_DECOMPRESS + + MACRO NEXT_HL + inc hl + ENDM + + MACRO ADD_OFFSET + ex de,hl : add hl,de + ENDM + + MACRO BLOCKCOPY + ldir + ENDM + + ELSE + + MACRO NEXT_HL + dec hl + ENDM + + MACRO ADD_OFFSET + push hl : or a : sbc hl,de : pop de ; 11+4+15+10 = 40t / 5 bytes + ENDM + + MACRO BLOCKCOPY + lddr + ENDM + + ENDIF + + IFNDEF HD64180 + + MACRO LD_IX_DE + ld ixl,e : ld ixh,d + ENDM + + MACRO LD_DE_IX + ld e,ixl : ld d,ixh + ENDM + + ELSE + + MACRO LD_IX_DE + push de : pop ix + ENDM + + MACRO LD_DE_IX + push ix : pop de + ENDM + + ENDIF + +@DecompressLZSA2: + xor a : ld b,a : exa : jr ReadToken + +CASE00x: call ReadNibble + ld e,a : ld a,c + cp %00100000 : rl e : jr SaveOffset + +CASE0xx ld d,#FF : cp %01000000 : jr c,CASE00x + +CASE01x: cp %01100000 : rl d + +OffsetReadE: ld e,(hl) : NEXT_HL + +SaveOffset: LD_IX_DE + +MatchLen: and %00000111 : add 2 : cp 9 : call z,ExtendedCode + +CopyMatch: ld c,a + ex (sp),hl ; BC = len, DE = -offset, HL = dest, SP -> [src] + ADD_OFFSET ; BC = len, DE = dest, HL = dest+(-offset), SP -> [src] + BLOCKCOPY ; BC = 0, DE = dest + pop hl ; HL = src + +ReadToken: ld a,(hl) : NEXT_HL : push af + and %00011000 : jr z,NoLiterals + + rrca : rrca : rrca + call pe,ExtendedCode + + ld c,a + BLOCKCOPY + +NoLiterals: pop af : push de + or a : jp p,CASE0xx + +CASE1xx cp %11000000 : jr nc,CASE11x + +CASE10x: call ReadNibble + ld d,a : ld a,c + cp %10100000 ;: rl d + dec d : rl d : DB #CA ; jr OffsetReadE ; #CA is JP Z,.. to skip all commands in CASE110 before jr OffsetReadE + +CASE110: ld d,(hl) : NEXT_HL : jr OffsetReadE + +CASE11x cp %11100000 : jr c,CASE110 + +CASE111: LD_DE_IX : jr MatchLen + +ExtendedCode: call ReadNibble : inc a : jr z,ExtraByte + sub #F0+1 : add c : ret +ExtraByte ld a,15 : add c : add (hl) : NEXT_HL : ret nc + ld a,(hl) : NEXT_HL + ld b,(hl) : NEXT_HL : ret nz + pop de : pop de ; RET is not needed, because RET from ReadNibble is sufficient + +ReadNibble: ld c,a : xor a : exa : ret m +UpdateNibble ld a,(hl) : or #F0 : exa + ld a,(hl) : NEXT_HL : or #0F + rrca : rrca : rrca : rrca : ret + + + + + + + + + + + + + + + + diff --git a/Tools/unix/lzsa/pareto_graph.png b/Tools/unix/lzsa/pareto_graph.png new file mode 100644 index 00000000..5ab3a914 Binary files /dev/null and b/Tools/unix/lzsa/pareto_graph.png differ diff --git a/Tools/unix/lzsa/src/dictionary.c b/Tools/unix/lzsa/src/dictionary.c new file mode 100644 index 00000000..fbc4f526 --- /dev/null +++ b/Tools/unix/lzsa/src/dictionary.c @@ -0,0 +1,101 @@ +/* + * dictionary.c - dictionary implementation + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#include +#include +#include "format.h" +#include "lib.h" + +/** + * Load dictionary contents + * + * @param pszDictionaryFilename name of dictionary file, or NULL for none + * @param ppDictionaryData pointer to returned dictionary contents, or NULL for none + * @param pDictionaryDataSize pointer to returned size of dictionary contents, or 0 + * + * @return LZSA_OK for success, or an error value from lzsa_status_t + */ +int lzsa_dictionary_load(const char *pszDictionaryFilename, void **ppDictionaryData, int *pDictionaryDataSize) { + unsigned char *pDictionaryData = NULL; + int nDictionaryDataSize = 0; + + if (pszDictionaryFilename) { + pDictionaryData = (unsigned char *)malloc(BLOCK_SIZE); + if (!pDictionaryData) { + return LZSA_ERROR_MEMORY; + } + + FILE *pDictionaryFile = fopen(pszDictionaryFilename, "rb"); + if (!pDictionaryFile) { + free(pDictionaryData); + pDictionaryData = NULL; + return LZSA_ERROR_DICTIONARY; + } + + fseek(pDictionaryFile, 0, SEEK_END); +#ifdef _WIN32 + __int64 nDictionaryFileSize = _ftelli64(pDictionaryFile); +#else + off_t nDictionaryFileSize = ftello(pDictionaryFile); +#endif + if (nDictionaryFileSize > BLOCK_SIZE) { + /* Use the last BLOCK_SIZE bytes of the dictionary */ + fseek(pDictionaryFile, -BLOCK_SIZE, SEEK_END); + } + else { + fseek(pDictionaryFile, 0, SEEK_SET); + } + + nDictionaryDataSize = (int)fread(pDictionaryData, 1, BLOCK_SIZE, pDictionaryFile); + if (nDictionaryDataSize < 0) + nDictionaryDataSize = 0; + + fclose(pDictionaryFile); + pDictionaryFile = NULL; + } + + *ppDictionaryData = pDictionaryData; + *pDictionaryDataSize = nDictionaryDataSize; + return LZSA_OK; +} + +/** + * Free dictionary contents + * + * @param ppDictionaryData pointer to pointer to dictionary contents + */ +void lzsa_dictionary_free(void **ppDictionaryData) { + if (*ppDictionaryData) { + free(*ppDictionaryData); + *ppDictionaryData = NULL; + } +} diff --git a/Tools/unix/lzsa/src/dictionary.h b/Tools/unix/lzsa/src/dictionary.h new file mode 100644 index 00000000..a3635640 --- /dev/null +++ b/Tools/unix/lzsa/src/dictionary.h @@ -0,0 +1,64 @@ +/* + * dictionary.h - dictionary definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _DICTIONARY_H +#define _DICTIONARY_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Load dictionary contents + * + * @param pszDictionaryFilename name of dictionary file, or NULL for none + * @param ppDictionaryData pointer to returned dictionary contents, or NULL for none + * @param pDictionaryDataSize pointer to returned size of dictionary contents, or 0 + * + * @return LZSA_OK for success, or an error value from lzsa_status_t + */ +int lzsa_dictionary_load(const char *pszDictionaryFilename, void **ppDictionaryData, int *pDictionaryDataSize); + +/** + * Free dictionary contents + * + * @param ppDictionaryData pointer to pointer to dictionary contents + */ +void lzsa_dictionary_free(void **ppDictionaryData); + +#ifdef __cplusplus +} +#endif + +#endif /* _DICTIONARY_H */ diff --git a/Tools/unix/lzsa/src/expand_block_v1.c b/Tools/unix/lzsa/src/expand_block_v1.c new file mode 100644 index 00000000..c248fb33 --- /dev/null +++ b/Tools/unix/lzsa/src/expand_block_v1.c @@ -0,0 +1,224 @@ +/* + * expand_block_v1.c - LZSA1 block decompressor implementation + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#include +#include +#include "format.h" +#include "expand_block_v1.h" + +#ifdef _MSC_VER +#define FORCE_INLINE __forceinline +#else /* _MSC_VER */ +#define FORCE_INLINE __attribute__((always_inline)) +#endif /* _MSC_VER */ + +static inline FORCE_INLINE int lzsa_build_literals_len_v1(const unsigned char **ppInBlock, const unsigned char *pInBlockEnd, unsigned int *nLiterals) { + unsigned int nByte; + const unsigned char *pInBlock = *ppInBlock; + + if (pInBlock < pInBlockEnd) { + nByte = *pInBlock++; + (*nLiterals) += nByte; + + if (nByte == 250) { + if (pInBlock < pInBlockEnd) { + (*nLiterals) = 256 + ((unsigned int)*pInBlock++); + } + else { + return -1; + } + } + else if (nByte == 249) { + if ((pInBlock + 1) < pInBlockEnd) { + (*nLiterals) = ((unsigned int)*pInBlock++); + (*nLiterals) |= (((unsigned int)*pInBlock++) << 8); + } + else { + return -1; + } + } + + *ppInBlock = pInBlock; + return 0; + } + else { + return -1; + } +} + +static inline FORCE_INLINE int lzsa_build_match_len_v1(const unsigned char **ppInBlock, const unsigned char *pInBlockEnd, unsigned int *nMatchLen) { + unsigned int nByte; + const unsigned char *pInBlock = *ppInBlock; + + if (pInBlock < pInBlockEnd) { + nByte = *pInBlock++; + (*nMatchLen) += nByte; + + if (nByte == 239) { + if (pInBlock < pInBlockEnd) { + (*nMatchLen) = 256 + ((unsigned int)*pInBlock++); + } + else { + return -1; + } + } + else if (nByte == 238) { + if ((pInBlock + 1) < pInBlockEnd) { + (*nMatchLen) = ((unsigned int)*pInBlock++); + (*nMatchLen) |= (((unsigned int)*pInBlock++) << 8); + } + else { + return -1; + } + } + + *ppInBlock = pInBlock; + return 0; + } + else { + return -1; + } +} + +/** + * Decompress one LZSA1 data block + * + * @param pInBlock pointer to compressed data + * @param nBlockSize size of compressed data, in bytes + * @param pOutData pointer to output decompression buffer (previously decompressed bytes + room for decompressing this block) + * @param nOutDataOffset starting index of where to store decompressed bytes in output buffer (and size of previously decompressed bytes) + * @param nBlockMaxSize total size of output decompression buffer, in bytes + * + * @return size of decompressed data in bytes, or -1 for error + */ +int lzsa_decompressor_expand_block_v1(const unsigned char *pInBlock, int nBlockSize, unsigned char *pOutData, int nOutDataOffset, int nBlockMaxSize) { + const unsigned char *pInBlockEnd = pInBlock + nBlockSize; + unsigned char *pCurOutData = pOutData + nOutDataOffset; + const unsigned char *pOutDataEnd = pCurOutData + nBlockMaxSize; + const unsigned char *pOutDataFastEnd = pOutDataEnd - 18; + + while (pInBlock < pInBlockEnd) { + const unsigned char token = *pInBlock++; + unsigned int nLiterals = (unsigned int)((token & 0x70) >> 4); + + if (nLiterals != LITERALS_RUN_LEN_V1 && (pInBlock + 8) <= pInBlockEnd && pCurOutData < pOutDataFastEnd) { + memcpy(pCurOutData, pInBlock, 8); + pInBlock += nLiterals; + pCurOutData += nLiterals; + } + else { + if (nLiterals == LITERALS_RUN_LEN_V1) { + if (lzsa_build_literals_len_v1(&pInBlock, pInBlockEnd, &nLiterals)) + return -1; + } + + if (nLiterals != 0) { + if ((pInBlock + nLiterals) <= pInBlockEnd && + (pCurOutData + nLiterals) <= pOutDataEnd) { + memcpy(pCurOutData, pInBlock, nLiterals); + pInBlock += nLiterals; + pCurOutData += nLiterals; + } + else { + return -1; + } + } + } + + if ((pInBlock + 1) < pInBlockEnd) { /* The last token in the block does not include match information */ + unsigned int nMatchOffset; + + nMatchOffset = ((unsigned int)(*pInBlock++)) ^ 0xff; + if (token & 0x80) { + nMatchOffset |= (((unsigned int)(*pInBlock++)) << 8) ^ 0xff00; + } + nMatchOffset++; + + const unsigned char *pSrc = pCurOutData - nMatchOffset; + if (pSrc >= pOutData) { + unsigned int nMatchLen = (unsigned int)(token & 0x0f); + if (nMatchLen != MATCH_RUN_LEN_V1 && nMatchOffset >= 8 && pCurOutData < pOutDataFastEnd && (pSrc + 18) <= pOutDataEnd) { + memcpy(pCurOutData, pSrc, 8); + memcpy(pCurOutData + 8, pSrc + 8, 8); + memcpy(pCurOutData + 16, pSrc + 16, 2); + pCurOutData += (MIN_MATCH_SIZE_V1 + nMatchLen); + } + else { + nMatchLen += MIN_MATCH_SIZE_V1; + if (nMatchLen == (MATCH_RUN_LEN_V1 + MIN_MATCH_SIZE_V1)) { + if (lzsa_build_match_len_v1(&pInBlock, pInBlockEnd, &nMatchLen)) + return -1; + if (nMatchLen == 0) + break; + } + + if ((pSrc + nMatchLen) <= pOutDataEnd) { + if ((pCurOutData + nMatchLen) <= pOutDataEnd) { + /* Do a deterministic, left to right byte copy instead of memcpy() so as to handle overlaps */ + + if (nMatchOffset >= 16 && (pCurOutData + nMatchLen) < (pOutDataFastEnd - 15)) { + const unsigned char *pCopySrc = pSrc; + unsigned char *pCopyDst = pCurOutData; + const unsigned char *pCopyEndDst = pCurOutData + nMatchLen; + + do { + memcpy(pCopyDst, pCopySrc, 16); + pCopySrc += 16; + pCopyDst += 16; + } while (pCopyDst < pCopyEndDst); + + pCurOutData += nMatchLen; + } + else { + while (nMatchLen) { + *pCurOutData++ = *pSrc++; + nMatchLen--; + } + } + } + else { + return -1; + } + } + else { + return -1; + } + } + } + else { + return -1; + } + } + } + + return (int)(pCurOutData - (pOutData + nOutDataOffset)); +} diff --git a/Tools/unix/lzsa/src/expand_block_v1.h b/Tools/unix/lzsa/src/expand_block_v1.h new file mode 100644 index 00000000..a1d5651a --- /dev/null +++ b/Tools/unix/lzsa/src/expand_block_v1.h @@ -0,0 +1,49 @@ +/* + * expand_block_v1.h - LZSA1 block decompressor definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _EXPAND_BLOCK_V1_H +#define _EXPAND_BLOCK_V1_H + +/** + * Decompress one LZSA1 data block + * + * @param pInBlock pointer to compressed data + * @param nBlockSize size of compressed data, in bytes + * @param pOutData pointer to output decompression buffer (previously decompressed bytes + room for decompressing this block) + * @param nOutDataOffset starting index of where to store decompressed bytes in output buffer (and size of previously decompressed bytes) + * @param nBlockMaxSize total size of output decompression buffer, in bytes + * + * @return size of decompressed data in bytes, or -1 for error + */ +int lzsa_decompressor_expand_block_v1(const unsigned char *pInBlock, int nBlockSize, unsigned char *pOutData, int nOutDataOffset, int nBlockMaxSize); + +#endif /* _EXPAND_BLOCK_V1_H */ diff --git a/Tools/unix/lzsa/src/expand_block_v2.c b/Tools/unix/lzsa/src/expand_block_v2.c new file mode 100644 index 00000000..e024b4c9 --- /dev/null +++ b/Tools/unix/lzsa/src/expand_block_v2.c @@ -0,0 +1,253 @@ +/* + * expand_block_v2.c - LZSA2 block decompressor implementation + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#include +#include +#include "format.h" +#include "expand_block_v2.h" + +#ifdef _MSC_VER +#define FORCE_INLINE __forceinline +#else /* _MSC_VER */ +#define FORCE_INLINE __attribute__((always_inline)) +#endif /* _MSC_VER */ + +static inline FORCE_INLINE unsigned int lzsa_get_nibble_v2(const unsigned char **ppInBlock, const unsigned char *pInBlockEnd, int *nCurNibbles, unsigned char *nibbles, unsigned int *nValue) { + if ((*nCurNibbles ^= 1) != 0) { + const unsigned char *pInBlock = *ppInBlock; + if (pInBlock < pInBlockEnd) { + (*nibbles) = *pInBlock++; + *ppInBlock = pInBlock; + (*nValue) = ((unsigned int)((*nibbles) & 0xf0)) >> 4; + return 0; + } + else { + return -1; + } + } + + (*nValue) = (unsigned int)((*nibbles) & 0x0f); + return 0; +} + +static inline FORCE_INLINE int lzsa_build_len_v2(const unsigned char **ppInBlock, const unsigned char *pInBlockEnd, int *nCurNibbles, unsigned char *nibbles, unsigned int *nLength) { + unsigned int nValue; + + if (!lzsa_get_nibble_v2(ppInBlock, pInBlockEnd, nCurNibbles, nibbles, &nValue)) { + (*nLength) += nValue; + + if (nValue == 15) { + const unsigned char *pInBlock = *ppInBlock; + + if (pInBlock < pInBlockEnd) { + (*nLength) += ((unsigned int)*pInBlock++); + + if ((*nLength) == 257) { + if ((pInBlock + 1) < pInBlockEnd) { + (*nLength) = ((unsigned int)*pInBlock++); + (*nLength) |= (((unsigned int)*pInBlock++) << 8); + } + else { + return -1; + } + } + else if ((*nLength) == 256) { + (*nLength) = 0; + } + } + else { + return -1; + } + + *ppInBlock = pInBlock; + } + + return 0; + } + else { + return -1; + } +} + +/** + * Decompress one LZSA2 data block + * + * @param pInBlock pointer to compressed data + * @param nBlockSize size of compressed data, in bytes + * @param pOutData pointer to output decompression buffer (previously decompressed bytes + room for decompressing this block) + * @param nOutDataOffset starting index of where to store decompressed bytes in output buffer (and size of previously decompressed bytes) + * @param nBlockMaxSize total size of output decompression buffer, in bytes + * + * @return size of decompressed data in bytes, or -1 for error + */ +int lzsa_decompressor_expand_block_v2(const unsigned char *pInBlock, int nBlockSize, unsigned char *pOutData, int nOutDataOffset, int nBlockMaxSize) { + const unsigned char *pInBlockEnd = pInBlock + nBlockSize; + unsigned char *pCurOutData = pOutData + nOutDataOffset; + const unsigned char *pOutDataEnd = pCurOutData + nBlockMaxSize; + const unsigned char *pOutDataFastEnd = pOutDataEnd - 20; + int nCurNibbles = 0; + unsigned char nibbles; + int nMatchOffset = 0; + + while (pInBlock < pInBlockEnd) { + const unsigned char token = *pInBlock++; + unsigned int nLiterals = (unsigned int)((token & 0x18) >> 3); + + if (nLiterals != LITERALS_RUN_LEN_V2 && (pInBlock + 4) <= pInBlockEnd && pCurOutData < pOutDataFastEnd) { + memcpy(pCurOutData, pInBlock, 4); + pInBlock += nLiterals; + pCurOutData += nLiterals; + } + else { + if (nLiterals == LITERALS_RUN_LEN_V2) { + if (lzsa_build_len_v2(&pInBlock, pInBlockEnd, &nCurNibbles, &nibbles, &nLiterals)) + return -1; + } + + if (nLiterals != 0) { + if ((pInBlock + nLiterals) <= pInBlockEnd && + (pCurOutData + nLiterals) <= pOutDataEnd) { + memcpy(pCurOutData, pInBlock, nLiterals); + pInBlock += nLiterals; + pCurOutData += nLiterals; + } + else { + return -1; + } + } + } + + if (pInBlock < pInBlockEnd) { /* The last token in the block does not include match information */ + unsigned char nOffsetMode = token & 0xc0; + unsigned int nValue; + + switch (nOffsetMode) { + case 0x00: + /* 5 bit offset */ + if (lzsa_get_nibble_v2(&pInBlock, pInBlockEnd, &nCurNibbles, &nibbles, &nValue)) + return -1; + nMatchOffset = nValue << 1; + nMatchOffset |= ((token & 0x20) >> 5); + nMatchOffset ^= 0x1e; + nMatchOffset++; + break; + + case 0x40: + /* 9 bit offset */ + nMatchOffset = (unsigned int)(*pInBlock++); + nMatchOffset |= (((unsigned int)(token & 0x20)) << 3); + nMatchOffset ^= 0x0ff; + nMatchOffset++; + break; + + case 0x80: + /* 13 bit offset */ + if (lzsa_get_nibble_v2(&pInBlock, pInBlockEnd, &nCurNibbles, &nibbles, &nValue)) + return -1; + nMatchOffset = (unsigned int)(*pInBlock++); + nMatchOffset |= (nValue << 9); + nMatchOffset |= (((unsigned int)(token & 0x20)) << 3); + nMatchOffset ^= 0x1eff; + nMatchOffset += (512 + 1); + break; + + default: + /* Check if this is a 16 bit offset or a rep-match */ + if ((token & 0x20) == 0) { + /* 16 bit offset */ + nMatchOffset = (((unsigned int)(*pInBlock++)) << 8); + if (pInBlock >= pInBlockEnd) return -1; + nMatchOffset |= (unsigned int)(*pInBlock++); + nMatchOffset ^= 0xffff; + nMatchOffset++; + } + break; + } + + const unsigned char *pSrc = pCurOutData - nMatchOffset; + if (pSrc >= pOutData) { + unsigned int nMatchLen = (unsigned int)(token & 0x07); + if (nMatchLen != MATCH_RUN_LEN_V2 && nMatchOffset >= 8 && pCurOutData < pOutDataFastEnd && (pSrc + 10) <= pOutDataEnd) { + memcpy(pCurOutData, pSrc, 8); + memcpy(pCurOutData + 8, pSrc + 8, 2); + pCurOutData += (MIN_MATCH_SIZE_V2 + nMatchLen); + } + else { + nMatchLen += MIN_MATCH_SIZE_V2; + if (nMatchLen == (MATCH_RUN_LEN_V2 + MIN_MATCH_SIZE_V2)) { + if (lzsa_build_len_v2(&pInBlock, pInBlockEnd, &nCurNibbles, &nibbles, &nMatchLen)) + return -1; + if (nMatchLen == 0) + break; + } + + if ((pSrc + nMatchLen) <= pOutDataEnd) { + if ((pCurOutData + nMatchLen) <= pOutDataEnd) { + /* Do a deterministic, left to right byte copy instead of memcpy() so as to handle overlaps */ + + if (nMatchOffset >= 16 && (pCurOutData + nMatchLen) < (pOutDataFastEnd - 15)) { + const unsigned char *pCopySrc = pSrc; + unsigned char *pCopyDst = pCurOutData; + const unsigned char *pCopyEndDst = pCurOutData + nMatchLen; + + do { + memcpy(pCopyDst, pCopySrc, 16); + pCopySrc += 16; + pCopyDst += 16; + } while (pCopyDst < pCopyEndDst); + + pCurOutData += nMatchLen; + } + else { + while (nMatchLen) { + *pCurOutData++ = *pSrc++; + nMatchLen--; + } + } + } + else { + return -1; + } + } + else { + return -1; + } + } + } + else { + return -1; + } + } + } + + return (int)(pCurOutData - (pOutData + nOutDataOffset)); +} diff --git a/Tools/unix/lzsa/src/expand_block_v2.h b/Tools/unix/lzsa/src/expand_block_v2.h new file mode 100644 index 00000000..f612f7a1 --- /dev/null +++ b/Tools/unix/lzsa/src/expand_block_v2.h @@ -0,0 +1,49 @@ +/* + * expand_block_v2.h - LZSA2 block decompressor definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _EXPAND_BLOCK_V2_H +#define _EXPAND_BLOCK_V2_H + +/** + * Decompress one LZSA2 data block + * + * @param pInBlock pointer to compressed data + * @param nBlockSize size of compressed data, in bytes + * @param pOutData pointer to output decompression buffer (previously decompressed bytes + room for decompressing this block) + * @param nOutDataOffset starting index of where to store decompressed bytes in output buffer (and size of previously decompressed bytes) + * @param nBlockMaxSize total size of output decompression buffer, in bytes + * + * @return size of decompressed data in bytes, or -1 for error + */ +int lzsa_decompressor_expand_block_v2(const unsigned char *pInBlock, int nBlockSize, unsigned char *pOutData, int nOutDataOffset, int nBlockMaxSize); + +#endif /* _EXPAND_BLOCK_V2_H */ diff --git a/Tools/unix/lzsa/src/expand_context.c b/Tools/unix/lzsa/src/expand_context.c new file mode 100644 index 00000000..b02620d1 --- /dev/null +++ b/Tools/unix/lzsa/src/expand_context.c @@ -0,0 +1,76 @@ +/* + * expand_context.h - decompressor context definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#include +#include +#include "expand_context.h" +#include "expand_block_v1.h" +#include "expand_block_v2.h" +#include "lib.h" + +/** + * Decompress one data block + * + * @param pInBlock pointer to compressed data + * @param nBlockSize size of compressed data, in bytes + * @param pOutData pointer to output decompression buffer (previously decompressed bytes + room for decompressing this block) + * @param nOutDataOffset starting index of where to store decompressed bytes in output buffer (and size of previously decompressed bytes) + * @param nBlockMaxSize total size of output decompression buffer, in bytes + * @param nFormatVersion version of format to use (1-2) + * @param nFlags compression flags (LZSA_FLAG_xxx) + * + * @return size of decompressed data in bytes, or -1 for error + */ +int lzsa_decompressor_expand_block(unsigned char *pInBlock, int nBlockSize, unsigned char *pOutData, int nOutDataOffset, int nBlockMaxSize, const int nFormatVersion, const int nFlags) { + int nDecompressedSize; + + if (nFlags & LZSA_FLAG_RAW_BACKWARD) { + lzsa_reverse_buffer(pInBlock, nBlockSize); + } + + if (nFormatVersion == 1) + nDecompressedSize = lzsa_decompressor_expand_block_v1(pInBlock, nBlockSize, pOutData, nOutDataOffset, nBlockMaxSize); + else if (nFormatVersion == 2) + nDecompressedSize = lzsa_decompressor_expand_block_v2(pInBlock, nBlockSize, pOutData, nOutDataOffset, nBlockMaxSize); + else + nDecompressedSize = -1; + + if (nDecompressedSize != -1 && (nFlags & LZSA_FLAG_RAW_BACKWARD)) { + lzsa_reverse_buffer(pOutData + nOutDataOffset, nDecompressedSize); + } + + if (nFlags & LZSA_FLAG_RAW_BACKWARD) { + lzsa_reverse_buffer(pInBlock, nBlockSize); + } + + return nDecompressedSize; +} diff --git a/Tools/unix/lzsa/src/expand_context.h b/Tools/unix/lzsa/src/expand_context.h new file mode 100644 index 00000000..698e0392 --- /dev/null +++ b/Tools/unix/lzsa/src/expand_context.h @@ -0,0 +1,61 @@ +/* + * expand_context.h - decompressor context definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _EXPAND_CONTEXT_H +#define _EXPAND_CONTEXT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Decompress one data block + * + * @param pInBlock pointer to compressed data + * @param nBlockSize size of compressed data, in bytes + * @param pOutData pointer to output decompression buffer (previously decompressed bytes + room for decompressing this block) + * @param nOutDataOffset starting index of where to store decompressed bytes in output buffer (and size of previously decompressed bytes) + * @param nBlockMaxSize total size of output decompression buffer, in bytes + * @param nFormatVersion version of format to use (1-2) + * @param nFlags compression flags (LZSA_FLAG_xxx) + * + * @return size of decompressed data in bytes, or -1 for error + */ +int lzsa_decompressor_expand_block(unsigned char *pInBlock, int nBlockSize, unsigned char *pOutData, int nOutDataOffset, int nBlockMaxSize, const int nFormatVersion, const int nFlags); + +#ifdef __cplusplus +} +#endif + +#endif /* _EXPAND_CONTEXT_H */ diff --git a/Tools/unix/lzsa/src/expand_inmem.c b/Tools/unix/lzsa/src/expand_inmem.c new file mode 100644 index 00000000..050bdbca --- /dev/null +++ b/Tools/unix/lzsa/src/expand_inmem.c @@ -0,0 +1,163 @@ +/* + * expand_inmem.c - in-memory decompression implementation + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#include +#include +#include "expand_inmem.h" +#include "lib.h" +#include "frame.h" + +#define BLOCK_SIZE 65536 + +/** + * Get maximum decompressed size of compressed data + * + * @param pFileData compressed data + * @param nFileSize compressed size in bytes + * + * @return maximum decompressed size + */ +size_t lzsa_get_max_decompressed_size_inmem(const unsigned char *pFileData, size_t nFileSize) { + const unsigned char *pCurFileData = pFileData; + const unsigned char *pEndFileData = pCurFileData + nFileSize; + int nFormatVersion = 0; + size_t nMaxDecompressedSize = 0; + const int nHeaderSize = lzsa_get_header_size(); + + /* Check header */ + if ((pCurFileData + nHeaderSize) > pEndFileData || + lzsa_decode_header(pCurFileData, nHeaderSize, &nFormatVersion) != 0) + return -1; + + pCurFileData += nHeaderSize; + + while (pCurFileData < pEndFileData) { + unsigned int nBlockDataSize = 0; + int nIsUncompressed = 0; + const int nFrameSize = lzsa_get_frame_size(); + + /* Decode frame header */ + if ((pCurFileData + nFrameSize) > pEndFileData || + lzsa_decode_frame(pCurFileData, nFrameSize, &nBlockDataSize, &nIsUncompressed) != 0) + return -1; + pCurFileData += nFrameSize; + + if (!nBlockDataSize) + break; + + /* Add one potentially full block to the decompressed size */ + nMaxDecompressedSize += BLOCK_SIZE; + + if ((pCurFileData + nBlockDataSize) > pEndFileData) + return -1; + + pCurFileData += nBlockDataSize; + } + + return nMaxDecompressedSize; +} + +/** + * Decompress data in memory + * + * @param pFileData compressed data + * @param pOutBuffer buffer for decompressed data + * @param nFileSize compressed size in bytes + * @param nMaxOutBufferSize maximum capacity of decompression buffer + * @param nFlags compression flags (LZSA_FLAG_xxx) + * @param pFormatVersion pointer to format version, updated if this function is successful + * + * @return actual decompressed size, or -1 for error + */ +size_t lzsa_decompress_inmem(unsigned char *pFileData, unsigned char *pOutBuffer, size_t nFileSize, size_t nMaxOutBufferSize, const unsigned int nFlags, int *pFormatVersion) { + unsigned char *pCurFileData = pFileData; + const unsigned char *pEndFileData = pCurFileData + nFileSize; + unsigned char *pCurOutBuffer = pOutBuffer; + const unsigned char *pEndOutBuffer = pCurOutBuffer + nMaxOutBufferSize; + int nPreviousBlockSize; + const int nHeaderSize = lzsa_get_header_size(); + + if (nFlags & LZSA_FLAG_RAW_BLOCK) { + return (size_t)lzsa_decompressor_expand_block(pFileData, (int)nFileSize, pOutBuffer, 0, (int)nMaxOutBufferSize, *pFormatVersion, nFlags); + } + + /* Check header */ + if ((pCurFileData + nHeaderSize) > pEndFileData || + lzsa_decode_header(pCurFileData, nHeaderSize, pFormatVersion) != 0) + return -1; + + pCurFileData += nHeaderSize; + nPreviousBlockSize = 0; + + while (pCurFileData < pEndFileData) { + unsigned int nBlockDataSize = 0; + int nIsUncompressed = 0; + const int nFrameSize = lzsa_get_frame_size(); + + /* Decode frame header */ + if ((pCurFileData + nFrameSize) > pEndFileData || + lzsa_decode_frame(pCurFileData, nFrameSize, &nBlockDataSize, &nIsUncompressed) != 0) + return -1; + pCurFileData += nFrameSize; + + if (!nBlockDataSize) + break; + + if (!nIsUncompressed) { + int nDecompressedSize; + + /* Decompress block */ + if ((pCurFileData + nBlockDataSize) > pEndFileData) + return -1; + + nDecompressedSize = lzsa_decompressor_expand_block(pCurFileData, nBlockDataSize, pCurOutBuffer - nPreviousBlockSize, nPreviousBlockSize, (int)(pEndOutBuffer - pCurOutBuffer + nPreviousBlockSize), *pFormatVersion, nFlags); + if (nDecompressedSize < 0) + return -1; + + pCurOutBuffer += nDecompressedSize; + nPreviousBlockSize = nDecompressedSize; + } + else { + /* Copy uncompressed block */ + if ((pCurFileData + nBlockDataSize) > pEndFileData) + return -1; + if ((pCurOutBuffer + nBlockDataSize) > pEndOutBuffer) + return -1; + memcpy(pCurOutBuffer, pCurFileData, nBlockDataSize); + pCurOutBuffer += nBlockDataSize; + } + + pCurFileData += nBlockDataSize; + } + + return (int)(pCurOutBuffer - pOutBuffer); +} diff --git a/Tools/unix/lzsa/src/expand_inmem.h b/Tools/unix/lzsa/src/expand_inmem.h new file mode 100644 index 00000000..b52ee687 --- /dev/null +++ b/Tools/unix/lzsa/src/expand_inmem.h @@ -0,0 +1,70 @@ +/* + * expand_inmem.h - in-memory decompression definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _EXPAND_INMEM_H +#define _EXPAND_INMEM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Get maximum decompressed size of compressed data + * + * @param pFileData compressed data + * @param nFileSize compressed size in bytes + * + * @return maximum decompressed size + */ +size_t lzsa_get_max_decompressed_size_inmem(const unsigned char *pFileData, size_t nFileSize); + +/** + * Decompress data in memory + * + * @param pFileData compressed data + * @param pOutBuffer buffer for decompressed data + * @param nFileSize compressed size in bytes + * @param nMaxOutBufferSize maximum capacity of decompression buffer + * @param nFlags compression flags (LZSA_FLAG_xxx) + * @param pFormatVersion pointer to format version, updated if this function is successful + * + * @return actual decompressed size, or -1 for error + */ +size_t lzsa_decompress_inmem(unsigned char *pFileData, unsigned char *pOutBuffer, size_t nFileSize, size_t nMaxOutBufferSize, const unsigned int nFlags, int *pFormatVersion); + +#ifdef __cplusplus +} +#endif + +#endif /* _EXPAND_INMEM_H */ diff --git a/Tools/unix/lzsa/src/expand_streaming.c b/Tools/unix/lzsa/src/expand_streaming.c new file mode 100644 index 00000000..b241c68b --- /dev/null +++ b/Tools/unix/lzsa/src/expand_streaming.c @@ -0,0 +1,236 @@ +/* + * expand_streaming.c - streaming decompression definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + + +#include +#include +#include "expand_streaming.h" +#include "format.h" +#include "frame.h" +#include "lib.h" + +/*-------------- File API -------------- */ + +/** + * Decompress file + * + * @param pszInFilename name of input(compressed) file to decompress + * @param pszOutFilename name of output(decompressed) file to generate + * @param pszDictionaryFilename name of dictionary file, or NULL for none + * @param nFlags compression flags (LZSA_FLAG_RAW_BLOCK to decompress a raw block, or 0) + * @param nFormatVersion default version of format to use (1-2). This is used when decompressing a raw block, otherwise the version is extracted from the source file + * @param pOriginalSize pointer to returned output(decompressed) size, updated when this function is successful + * @param pCompressedSize pointer to returned input(compressed) size, updated when this function is successful + * + * @return LZSA_OK for success, or an error value from lzsa_status_t + */ +lzsa_status_t lzsa_decompress_file(const char *pszInFilename, const char *pszOutFilename, const char *pszDictionaryFilename, const unsigned int nFlags, int nFormatVersion, + long long *pOriginalSize, long long *pCompressedSize) { + lzsa_stream_t inStream, outStream; + void *pDictionaryData = NULL; + int nDictionaryDataSize = 0; + lzsa_status_t nStatus; + + if (lzsa_filestream_open(&inStream, pszInFilename, "rb") < 0) { + return LZSA_ERROR_SRC; + } + + if (lzsa_filestream_open(&outStream, pszOutFilename, "wb") < 0) { + inStream.close(&inStream); + return LZSA_ERROR_DST; + } + + nStatus = lzsa_dictionary_load(pszDictionaryFilename, &pDictionaryData, &nDictionaryDataSize); + if (nStatus) { + outStream.close(&outStream); + inStream.close(&inStream); + return nStatus; + } + + nStatus = lzsa_decompress_stream(&inStream, &outStream, pDictionaryData, nDictionaryDataSize, nFlags, nFormatVersion, pOriginalSize, pCompressedSize); + + lzsa_dictionary_free(&pDictionaryData); + outStream.close(&outStream); + inStream.close(&inStream); + + return nStatus; +} + +/*-------------- Streaming API -------------- */ + +/** + * Decompress stream + * + * @param pInStream input(compressed) stream to decompress + * @param pOutStream output(decompressed) stream to write to + * @param pDictionaryData dictionary contents, or NULL for none + * @param nDictionaryDataSize size of dictionary contents, or 0 + * @param nFlags compression flags (LZSA_FLAG_RAW_BLOCK to decompress a raw block, or 0) + * @param nFormatVersion default version of format to use (1-2). This is used when decompressing a raw block, otherwise the version is extracted from the source file + * @param pOriginalSize pointer to returned output(decompressed) size, updated when this function is successful + * @param pCompressedSize pointer to returned input(compressed) size, updated when this function is successful + * + * @return LZSA_OK for success, or an error value from lzsa_status_t + */ +lzsa_status_t lzsa_decompress_stream(lzsa_stream_t *pInStream, lzsa_stream_t *pOutStream, const void *pDictionaryData, int nDictionaryDataSize, const unsigned int nFlags, int nFormatVersion, + long long *pOriginalSize, long long *pCompressedSize) { + long long nOriginalSize = 0LL, nCompressedSize = 0LL; + unsigned char cFrameData[16]; + unsigned char *pInBlock; + unsigned char *pOutData; + + if ((nFlags & LZSA_FLAG_RAW_BLOCK) == 0) { + const int nHeaderSize = lzsa_get_header_size(); + + memset(cFrameData, 0, 16); + if (pInStream->read(pInStream, cFrameData, nHeaderSize) != nHeaderSize) { + return LZSA_ERROR_SRC; + } + + if (lzsa_decode_header(cFrameData, nHeaderSize, &nFormatVersion) < 0) { + return LZSA_ERROR_FORMAT; + } + + nCompressedSize += (long long)nHeaderSize; + } + + pInBlock = (unsigned char*)malloc(BLOCK_SIZE); + if (!pInBlock) { + return LZSA_ERROR_MEMORY; + } + + pOutData = (unsigned char*)malloc(BLOCK_SIZE * 2); + if (!pOutData) { + free(pInBlock); + pInBlock = NULL; + + return LZSA_ERROR_MEMORY; + } + + int nDecompressionError = 0; + int nPrevDecompressedSize = 0; + int nNumBlocks = 0; + + while (!pInStream->eof(pInStream) && !nDecompressionError) { + unsigned int nBlockSize = 0; + int nIsUncompressed = 0; + + if (nPrevDecompressedSize != 0) { + memcpy(pOutData + BLOCK_SIZE - nPrevDecompressedSize, pOutData + BLOCK_SIZE, nPrevDecompressedSize); + } + else if (nDictionaryDataSize && pDictionaryData) { + nPrevDecompressedSize = nDictionaryDataSize; + memcpy(pOutData + BLOCK_SIZE - nPrevDecompressedSize, pDictionaryData, nPrevDecompressedSize); + } + + if ((nFlags & LZSA_FLAG_RAW_BLOCK) == 0) { + const int nFrameSize = lzsa_get_frame_size(); + + memset(cFrameData, 0, 16); + if (pInStream->read(pInStream, cFrameData, nFrameSize) == nFrameSize) { + if (lzsa_decode_frame(cFrameData, nFrameSize, &nBlockSize, &nIsUncompressed) < 0) { + nDecompressionError = LZSA_ERROR_FORMAT; + nBlockSize = 0; + } + + nCompressedSize += (long long)nFrameSize; + } + else { + nDecompressionError = LZSA_ERROR_SRC; + nBlockSize = 0; + } + } + else { + if (!nNumBlocks) + nBlockSize = BLOCK_SIZE; + else + nBlockSize = 0; + } + + if (nBlockSize != 0) { + int nDecompressedSize = 0; + + if ((int)nBlockSize > BLOCK_SIZE) { + nDecompressionError = LZSA_ERROR_FORMAT; + break; + } + size_t nReadBytes = pInStream->read(pInStream, pInBlock, nBlockSize); + if (nFlags & LZSA_FLAG_RAW_BLOCK) { + nBlockSize = (unsigned int)nReadBytes; + } + + if (nReadBytes == nBlockSize) { + nCompressedSize += (long long)nReadBytes; + + if (nIsUncompressed) { + memcpy(pOutData + BLOCK_SIZE, pInBlock, nBlockSize); + nDecompressedSize = nBlockSize; + } + else { + nDecompressedSize = lzsa_decompressor_expand_block(pInBlock, nBlockSize, pOutData, BLOCK_SIZE, BLOCK_SIZE, nFormatVersion, nFlags); + if (nDecompressedSize < 0) { + nDecompressionError = LZSA_ERROR_DECOMPRESSION; + break; + } + } + + if (nDecompressedSize != 0) { + nOriginalSize += (long long)nDecompressedSize; + + if (pOutStream->write(pOutStream, pOutData + BLOCK_SIZE, nDecompressedSize) != nDecompressedSize) + nDecompressionError = LZSA_ERROR_DST; + nPrevDecompressedSize = nDecompressedSize; + nDecompressedSize = 0; + } + } + else { + break; + } + + nNumBlocks++; + } + else { + break; + } + } + + free(pOutData); + pOutData = NULL; + + free(pInBlock); + pInBlock = NULL; + + *pOriginalSize = nOriginalSize; + *pCompressedSize = nCompressedSize; + return nDecompressionError; +} + diff --git a/Tools/unix/lzsa/src/expand_streaming.h b/Tools/unix/lzsa/src/expand_streaming.h new file mode 100644 index 00000000..30dd88d8 --- /dev/null +++ b/Tools/unix/lzsa/src/expand_streaming.h @@ -0,0 +1,86 @@ +/* + * expand_streaming.h - streaming decompression definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _EXPAND_STREAMING_H +#define _EXPAND_STREAMING_H + +#include "stream.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declaration */ +typedef enum _lzsa_status_t lzsa_status_t; + +/*-------------- File API -------------- */ + +/** + * Decompress file + * + * @param pszInFilename name of input(compressed) file to decompress + * @param pszOutFilename name of output(decompressed) file to generate + * @param pszDictionaryFilename name of dictionary file, or NULL for none + * @param nFlags compression flags (LZSA_FLAG_RAW_BLOCK to decompress a raw block, or 0) + * @param nFormatVersion default version of format to use (1-2). This is used when decompressing a raw block, otherwise the version is extracted from the source file + * @param pOriginalSize pointer to returned output(decompressed) size, updated when this function is successful + * @param pCompressedSize pointer to returned input(compressed) size, updated when this function is successful + * + * @return LZSA_OK for success, or an error value from lzsa_status_t + */ +lzsa_status_t lzsa_decompress_file(const char *pszInFilename, const char *pszOutFilename, const char *pszDictionaryFilename, const unsigned int nFlags, int nFormatVersion, + long long *pOriginalSize, long long *pCompressedSize); + +/*-------------- Streaming API -------------- */ + +/** + * Decompress stream + * + * @param pInStream input(compressed) stream to decompress + * @param pOutStream output(decompressed) stream to write to + * @param pDictionaryData dictionary contents, or NULL for none + * @param nDictionaryDataSize size of dictionary contents, or 0 + * @param nFlags compression flags (LZSA_FLAG_RAW_BLOCK to decompress a raw block, or 0) + * @param nFormatVersion default version of format to use (1-2). This is used when decompressing a raw block, otherwise the version is extracted from the source file + * @param pOriginalSize pointer to returned output(decompressed) size, updated when this function is successful + * @param pCompressedSize pointer to returned input(compressed) size, updated when this function is successful + * + * @return LZSA_OK for success, or an error value from lzsa_status_t + */ +lzsa_status_t lzsa_decompress_stream(lzsa_stream_t *pInStream, lzsa_stream_t *pOutStream, const void *pDictionaryData, int nDictionaryDataSize, const unsigned int nFlags, int nFormatVersion, + long long *pOriginalSize, long long *pCompressedSize); + +#ifdef __cplusplus +} +#endif + +#endif /* _EXPAND_STREAMING_H */ diff --git a/Tools/unix/lzsa/src/format.h b/Tools/unix/lzsa/src/format.h new file mode 100644 index 00000000..ace4f15c --- /dev/null +++ b/Tools/unix/lzsa/src/format.h @@ -0,0 +1,51 @@ +/* + * format.h - byte stream format definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _FORMAT_H +#define _FORMAT_H + +#define MIN_OFFSET 1 +#define MAX_OFFSET 0xffff + +#define MAX_VARLEN 0xffff + +#define BLOCK_SIZE 65536 + +#define MIN_MATCH_SIZE_V1 3 +#define LITERALS_RUN_LEN_V1 7 +#define MATCH_RUN_LEN_V1 15 + +#define MIN_MATCH_SIZE_V2 2 +#define LITERALS_RUN_LEN_V2 3 +#define MATCH_RUN_LEN_V2 7 + +#endif /* _FORMAT_H */ diff --git a/Tools/unix/lzsa/src/frame.c b/Tools/unix/lzsa/src/frame.c new file mode 100644 index 00000000..f1d6be2d --- /dev/null +++ b/Tools/unix/lzsa/src/frame.c @@ -0,0 +1,189 @@ +/* + * frame.c - frame implementation + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#include +#include +#include "frame.h" + +#define LZSA_ID_0 0x7b +#define LZSA_ID_1 0x9e + +/** + * Get compressed file header size + * + * @return file header size + */ +int lzsa_get_header_size(void) { + return 3; +} + +/** + * Get compressed frame header size + * + * @return frame header size + */ +int lzsa_get_frame_size(void) { + return 3; +} + +/** + * Encode file header + * + * @param pFrameData encoding buffer + * @param nMaxFrameDataSize max encoding buffer size, in bytes + * + * @return number of encoded bytes, or -1 for failure + */ +int lzsa_encode_header(unsigned char *pFrameData, const int nMaxFrameDataSize, int nFormatVersion) { + if (nMaxFrameDataSize >= 3 && (nFormatVersion == 1 || nFormatVersion == 2)) { + pFrameData[0] = LZSA_ID_0; /* Magic number */ + pFrameData[1] = LZSA_ID_1; + pFrameData[2] = (nFormatVersion == 2) ? 0x20 : 0; /* Format version 1 */ + + return 3; + } + else { + return -1; + } +} + +/** + * Encode compressed block frame header + * + * @param pFrameData encoding buffer + * @param nMaxFrameDataSize max encoding buffer size, in bytes + * @param nBlockDataSize compressed block's data size, in bytes + * + * @return number of encoded bytes, or -1 for failure + */ +int lzsa_encode_compressed_block_frame(unsigned char *pFrameData, const int nMaxFrameDataSize, const int nBlockDataSize) { + if (nMaxFrameDataSize >= 3 && nBlockDataSize <= 0x7fffff) { + pFrameData[0] = nBlockDataSize & 0xff; + pFrameData[1] = (nBlockDataSize >> 8) & 0xff; + pFrameData[2] = (nBlockDataSize >> 16) & 0x7f; + + return 3; + } + else { + return -1; + } +} + +/** + * Encode uncompressed block frame header + * + * @param pFrameData encoding buffer + * @param nMaxFrameDataSize max encoding buffer size, in bytes + * @param nBlockDataSize uncompressed block's data size, in bytes + * + * @return number of encoded bytes, or -1 for failure + */ +int lzsa_encode_uncompressed_block_frame(unsigned char *pFrameData, const int nMaxFrameDataSize, const int nBlockDataSize) { + if (nMaxFrameDataSize >= 3 && nBlockDataSize <= 0x7fffff) { + pFrameData[0] = nBlockDataSize & 0xff; + pFrameData[1] = (nBlockDataSize >> 8) & 0xff; + pFrameData[2] = ((nBlockDataSize >> 16) & 0x7f) | 0x80; /* Uncompressed block */ + + return 3; + } + else { + return -1; + } +} + +/** + * Encode terminal frame header + * + * @param pFrameData encoding buffer + * @param nMaxFrameDataSize max encoding buffer size, in bytes + * + * @return number of encoded bytes, or -1 for failure + */ +int lzsa_encode_footer_frame(unsigned char *pFrameData, const int nMaxFrameDataSize) { + if (nMaxFrameDataSize >= 3) { + pFrameData[0] = 0x00; /* EOD frame */ + pFrameData[1] = 0x00; + pFrameData[2] = 0x00; + + return 3; + } + else { + return -1; + } +} + +/** + * Decode file header + * + * @param pFrameData data bytes + * @param nFrameDataSize number of bytes to decode + * + * @return 0 for success, or -1 for failure + */ +int lzsa_decode_header(const unsigned char *pFrameData, const int nFrameDataSize, int *nFormatVersion) { + if (nFrameDataSize != 3 || + pFrameData[0] != LZSA_ID_0 || + pFrameData[1] != LZSA_ID_1 || + (pFrameData[2] & 0x1f) != 0 || + ((pFrameData[2] & 0xe0) != 0x00 && (pFrameData[2] & 0xe0) != 0x20)) { + return -1; + } + else { + *nFormatVersion = (pFrameData[2] & 0xe0) ? 2 : 1; + return 0; + } +} + +/** + * Decode frame header + * + * @param pFrameData data bytes + * @param nFrameDataSize number of bytes to decode + * @param nBlockSize pointer to block size, updated if this function succeeds (set to 0 if this is the terminal frame) + * @param nIsUncompressed pointer to compressed block flag, updated if this function succeeds + * + * @return 0 for success, or -1 for failure + */ +int lzsa_decode_frame(const unsigned char *pFrameData, const int nFrameDataSize, unsigned int *nBlockSize, int *nIsUncompressed) { + if (nFrameDataSize == 3) { + *nBlockSize = ((unsigned int)pFrameData[0]) | + (((unsigned int)pFrameData[1]) << 8) | + (((unsigned int)pFrameData[2]) << 16); + + *nIsUncompressed = ((*nBlockSize & 0x800000) != 0) ? 1 : 0; + *nBlockSize &= 0x7fffff; + return 0; + } + else { + return -1; + } +} diff --git a/Tools/unix/lzsa/src/frame.h b/Tools/unix/lzsa/src/frame.h new file mode 100644 index 00000000..7b5db99f --- /dev/null +++ b/Tools/unix/lzsa/src/frame.h @@ -0,0 +1,122 @@ +/* + * frame.h - frame definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _FRAME_H +#define _FRAME_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Get compressed file header size + * + * @return file header size + */ +int lzsa_get_header_size(void); + +/** + * Get compressed frame header size + * + * @return frame header size + */ +int lzsa_get_frame_size(void); + +/** + * Encode file header + * + * @param pFrameData encoding buffer + * @param nMaxFrameDataSize max encoding buffer size, in bytes + * + * @return number of encoded bytes, or -1 for failure + */ +int lzsa_encode_header(unsigned char *pFrameData, const int nMaxFrameDataSize, int nFormatVersion); + +/** + * Encode compressed block frame header + * + * @param pFrameData encoding buffer + * @param nMaxFrameDataSize max encoding buffer size, in bytes + * @param nBlockDataSize compressed block's data size, in bytes + * + * @return number of encoded bytes, or -1 for failure + */ +int lzsa_encode_compressed_block_frame(unsigned char *pFrameData, const int nMaxFrameDataSize, const int nBlockDataSize); + +/** + * Encode uncompressed block frame header + * + * @param pFrameData encoding buffer + * @param nMaxFrameDataSize max encoding buffer size, in bytes + * @param nBlockDataSize uncompressed block's data size, in bytes + * + * @return number of encoded bytes, or -1 for failure + */ +int lzsa_encode_uncompressed_block_frame(unsigned char *pFrameData, const int nMaxFrameDataSize, const int nBlockDataSize); + +/** + * Encode terminal frame header + * + * @param pFrameData encoding buffer + * @param nMaxFrameDataSize max encoding buffer size, in bytes + * + * @return number of encoded bytes, or -1 for failure + */ +int lzsa_encode_footer_frame(unsigned char *pFrameData, const int nMaxFrameDataSize); + +/** + * Decode file header + * + * @param pFrameData data bytes + * @param nFrameDataSize number of bytes to decode + * + * @return 0 for success, or -1 for failure + */ +int lzsa_decode_header(const unsigned char *pFrameData, const int nFrameDataSize, int *nFormatVersion); + +/** + * Decode frame header + * + * @param pFrameData data bytes + * @param nFrameDataSize number of bytes to decode + * @param nBlockSize pointer to block size, updated if this function succeeds (set to 0 if this is the terminal frame) + * @param nIsUncompressed pointer to compressed block flag, updated if this function succeeds + * + * @return 0 for success, or -1 for failure + */ +int lzsa_decode_frame(const unsigned char *pFrameData, const int nFrameDataSize, unsigned int *nBlockSize, int *nIsUncompressed); + +#ifdef __cplusplus +} +#endif + +#endif /* _FRAME_H */ diff --git a/Tools/unix/lzsa/src/lib.h b/Tools/unix/lzsa/src/lib.h new file mode 100644 index 00000000..5556d2a9 --- /dev/null +++ b/Tools/unix/lzsa/src/lib.h @@ -0,0 +1,95 @@ +/* + * lib.h - LZSA library definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _LIB_H +#define _LIB_H + +#include "stream.h" +#include "dictionary.h" +#include "frame.h" +#include "format.h" +#include "shrink_context.h" +#include "shrink_streaming.h" +#include "shrink_inmem.h" +#include "expand_context.h" +#include "expand_streaming.h" +#include "expand_inmem.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** High level status for compression and decompression */ +typedef enum _lzsa_status_t { + LZSA_OK = 0, /**< Success */ + LZSA_ERROR_SRC, /**< Error reading input */ + LZSA_ERROR_DST, /**< Error reading output */ + LZSA_ERROR_DICTIONARY, /**< Error reading dictionary */ + LZSA_ERROR_MEMORY, /**< Out of memory */ + + /* Compression-specific status codes */ + LZSA_ERROR_COMPRESSION, /**< Internal compression error */ + LZSA_ERROR_RAW_TOOLARGE, /**< Input is too large to be compressed to a raw block */ + LZSA_ERROR_RAW_UNCOMPRESSED, /**< Input is incompressible and raw blocks don't support uncompressed data */ + + /* Decompression-specific status codes */ + LZSA_ERROR_FORMAT, /**< Invalid input format or magic number when decompressing */ + LZSA_ERROR_DECOMPRESSION, /**< Internal decompression error */ +} lzsa_status_t; + +/* Compression flags */ +#define LZSA_FLAG_FAVOR_RATIO (1<<0) /**< 1 to compress with the best ratio, 0 to trade some compression ratio for extra decompression speed */ +#define LZSA_FLAG_RAW_BLOCK (1<<1) /**< 1 to emit raw block */ +#define LZSA_FLAG_RAW_BACKWARD (1<<2) /**< 1 to compress or decompress raw block backward */ + +/** + * Reverse bytes in the specified buffer + * + * @param pBuffer pointer to buffer whose contents are to be reversed + * @param nBufferSize size of buffer in bytes + */ +static inline void lzsa_reverse_buffer(unsigned char *pBuffer, const int nBufferSize) { + int nMidPoint = nBufferSize / 2; + int i, j; + + for (i = 0, j = nBufferSize - 1; i < nMidPoint; i++, j--) { + unsigned char c = pBuffer[i]; + pBuffer[i] = pBuffer[j]; + pBuffer[j] = c; + } +} + +#ifdef __cplusplus +} +#endif + +#endif /* _LIB_H */ diff --git a/Tools/unix/lzsa/src/libdivsufsort/CHANGELOG.md b/Tools/unix/lzsa/src/libdivsufsort/CHANGELOG.md new file mode 100644 index 00000000..fe9d0040 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/CHANGELOG.md @@ -0,0 +1,21 @@ +# libdivsufsort Change Log + +See full changelog at: https://github.com/y-256/libdivsufsort/commits + +## [2.0.1] - 2010-11-11 +### Fixed +* Wrong variable used in `divbwt` function +* Enclose some string variables with double quotation marks in include/CMakeLists.txt +* Fix typo in include/CMakeLists.txt + +## 2.0.0 - 2008-08-23 +### Changed +* Switch the build system to [CMake](http://www.cmake.org/) +* Improve the performance of the suffix-sorting algorithm + +### Added +* OpenMP support +* 64-bit version of divsufsort + +[Unreleased]: https://github.com/y-256/libdivsufsort/compare/2.0.1...HEAD +[2.0.1]: https://github.com/y-256/libdivsufsort/compare/2.0.0...2.0.1 diff --git a/Tools/unix/lzsa/src/libdivsufsort/CMakeLists.txt b/Tools/unix/lzsa/src/libdivsufsort/CMakeLists.txt new file mode 100644 index 00000000..78599439 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/CMakeLists.txt @@ -0,0 +1,99 @@ +### cmake file for building libdivsufsort Package ### +cmake_minimum_required(VERSION 2.4.4) +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") +include(AppendCompilerFlags) + +## Project information ## +project(libdivsufsort C) +set(PROJECT_VENDOR "Yuta Mori") +set(PROJECT_CONTACT "yuta.256@gmail.com") +set(PROJECT_URL "https://github.com/y-256/libdivsufsort") +set(PROJECT_DESCRIPTION "A lightweight suffix sorting library") +include(VERSION.cmake) + +## CPack configuration ## +set(CPACK_GENERATOR "TGZ;TBZ2;ZIP") +set(CPACK_SOURCE_GENERATOR "TGZ;TBZ2;ZIP") +include(ProjectCPack) + +## Project options ## +option(BUILD_SHARED_LIBS "Set to OFF to build static libraries" ON) +option(BUILD_EXAMPLES "Build examples" ON) +option(BUILD_DIVSUFSORT64 "Build libdivsufsort64" OFF) +option(USE_OPENMP "Use OpenMP for parallelization" OFF) +option(WITH_LFS "Enable Large File Support" ON) + +## Installation directories ## +set(LIB_SUFFIX "" CACHE STRING "Define suffix of directory name (32 or 64)") + +set(CMAKE_INSTALL_RUNTIMEDIR "" CACHE PATH "Specify the output directory for dll runtimes (default is bin)") +if(NOT CMAKE_INSTALL_RUNTIMEDIR) + set(CMAKE_INSTALL_RUNTIMEDIR "${CMAKE_INSTALL_PREFIX}/bin") +endif(NOT CMAKE_INSTALL_RUNTIMEDIR) + +set(CMAKE_INSTALL_LIBDIR "" CACHE PATH "Specify the output directory for libraries (default is lib)") +if(NOT CMAKE_INSTALL_LIBDIR) + set(CMAKE_INSTALL_LIBDIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}") +endif(NOT CMAKE_INSTALL_LIBDIR) + +set(CMAKE_INSTALL_INCLUDEDIR "" CACHE PATH "Specify the output directory for header files (default is include)") +if(NOT CMAKE_INSTALL_INCLUDEDIR) + set(CMAKE_INSTALL_INCLUDEDIR "${CMAKE_INSTALL_PREFIX}/include") +endif(NOT CMAKE_INSTALL_INCLUDEDIR) + +set(CMAKE_INSTALL_PKGCONFIGDIR "" CACHE PATH "Specify the output directory for pkgconfig files (default is lib/pkgconfig)") +if(NOT CMAKE_INSTALL_PKGCONFIGDIR) + set(CMAKE_INSTALL_PKGCONFIGDIR "${CMAKE_INSTALL_LIBDIR}/pkgconfig") +endif(NOT CMAKE_INSTALL_PKGCONFIGDIR) + +## Build type ## +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +elseif(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(CMAKE_VERBOSE_MAKEFILE ON) +endif(NOT CMAKE_BUILD_TYPE) + +## Compiler options ## +if(MSVC) + append_c_compiler_flags("/W4" "VC" CMAKE_C_FLAGS) + append_c_compiler_flags("/Oi;/Ot;/Ox;/Oy" "VC" CMAKE_C_FLAGS_RELEASE) + if(USE_OPENMP) + append_c_compiler_flags("/openmp" "VC" CMAKE_C_FLAGS) + endif(USE_OPENMP) +elseif(BORLAND) + append_c_compiler_flags("-w" "BCC" CMAKE_C_FLAGS) + append_c_compiler_flags("-Oi;-Og;-Os;-Ov;-Ox" "BCC" CMAKE_C_FLAGS_RELEASE) +else(MSVC) + if(CMAKE_COMPILER_IS_GNUCC) + append_c_compiler_flags("-Wall" "GCC" CMAKE_C_FLAGS) + append_c_compiler_flags("-fomit-frame-pointer" "GCC" CMAKE_C_FLAGS_RELEASE) + if(USE_OPENMP) + append_c_compiler_flags("-fopenmp" "GCC" CMAKE_C_FLAGS) + endif(USE_OPENMP) + else(CMAKE_COMPILER_IS_GNUCC) + append_c_compiler_flags("-Wall" "UNKNOWN" CMAKE_C_FLAGS) + append_c_compiler_flags("-fomit-frame-pointer" "UNKNOWN" CMAKE_C_FLAGS_RELEASE) + if(USE_OPENMP) + append_c_compiler_flags("-fopenmp;-openmp;-omp" "UNKNOWN" CMAKE_C_FLAGS) + endif(USE_OPENMP) + endif(CMAKE_COMPILER_IS_GNUCC) +endif(MSVC) + +## Add definitions ## +add_definitions(-DHAVE_CONFIG_H=1 -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS) + +## Add subdirectories ## +add_subdirectory(pkgconfig) +add_subdirectory(include) +add_subdirectory(lib) +if(BUILD_EXAMPLES) + add_subdirectory(examples) +endif(BUILD_EXAMPLES) + +## Add 'uninstall' target ## +CONFIGURE_FILE( + "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/CMakeModules/cmake_uninstall.cmake" + IMMEDIATE @ONLY) +ADD_CUSTOM_TARGET(uninstall + "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/CMakeModules/cmake_uninstall.cmake") diff --git a/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/AppendCompilerFlags.cmake b/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/AppendCompilerFlags.cmake new file mode 100644 index 00000000..58d3f99e --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/AppendCompilerFlags.cmake @@ -0,0 +1,38 @@ +include(CheckCSourceCompiles) +include(CheckCXXSourceCompiles) + +macro(append_c_compiler_flags _flags _name _result) + set(SAFE_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) + string(REGEX REPLACE "[-+/ ]" "_" cname "${_name}") + string(TOUPPER "${cname}" cname) + foreach(flag ${_flags}) + string(REGEX REPLACE "^[-+/ ]+(.*)[-+/ ]*$" "\\1" flagname "${flag}") + string(REGEX REPLACE "[-+/ ]" "_" flagname "${flagname}") + string(TOUPPER "${flagname}" flagname) + set(have_flag "HAVE_${cname}_${flagname}") + set(CMAKE_REQUIRED_FLAGS "${flag}") + check_c_source_compiles("int main() { return 0; }" ${have_flag}) + if(${have_flag}) + set(${_result} "${${_result}} ${flag}") + endif(${have_flag}) + endforeach(flag) + set(CMAKE_REQUIRED_FLAGS ${SAFE_CMAKE_REQUIRED_FLAGS}) +endmacro(append_c_compiler_flags) + +macro(append_cxx_compiler_flags _flags _name _result) + set(SAFE_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) + string(REGEX REPLACE "[-+/ ]" "_" cname "${_name}") + string(TOUPPER "${cname}" cname) + foreach(flag ${_flags}) + string(REGEX REPLACE "^[-+/ ]+(.*)[-+/ ]*$" "\\1" flagname "${flag}") + string(REGEX REPLACE "[-+/ ]" "_" flagname "${flagname}") + string(TOUPPER "${flagname}" flagname) + set(have_flag "HAVE_${cname}_${flagname}") + set(CMAKE_REQUIRED_FLAGS "${flag}") + check_cxx_source_compiles("int main() { return 0; }" ${have_flag}) + if(${have_flag}) + set(${_result} "${${_result}} ${flag}") + endif(${have_flag}) + endforeach(flag) + set(CMAKE_REQUIRED_FLAGS ${SAFE_CMAKE_REQUIRED_FLAGS}) +endmacro(append_cxx_compiler_flags) diff --git a/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/CheckFunctionKeywords.cmake b/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/CheckFunctionKeywords.cmake new file mode 100644 index 00000000..44601fd4 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/CheckFunctionKeywords.cmake @@ -0,0 +1,15 @@ +include(CheckCSourceCompiles) + +macro(check_function_keywords _wordlist) + set(${_result} "") + foreach(flag ${_wordlist}) + string(REGEX REPLACE "[-+/ ()]" "_" flagname "${flag}") + string(TOUPPER "${flagname}" flagname) + set(have_flag "HAVE_${flagname}") + check_c_source_compiles("${flag} void func(); void func() { } int main() { func(); return 0; }" ${have_flag}) + if(${have_flag} AND NOT ${_result}) + set(${_result} "${flag}") +# break() + endif(${have_flag} AND NOT ${_result}) + endforeach(flag) +endmacro(check_function_keywords) diff --git a/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/CheckLFS.cmake b/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/CheckLFS.cmake new file mode 100644 index 00000000..e2b00991 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/CheckLFS.cmake @@ -0,0 +1,109 @@ +## Checks for large file support ## +include(CheckIncludeFile) +include(CheckSymbolExists) +include(CheckTypeSize) + +macro(check_lfs _isenable) + set(LFS_OFF_T "") + set(LFS_FOPEN "") + set(LFS_FSEEK "") + set(LFS_FTELL "") + set(LFS_PRID "") + + if(${_isenable}) + set(SAFE_CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS}") + set(CMAKE_REQUIRED_DEFINITIONS ${CMAKE_REQUIRED_DEFINITIONS} + -D_LARGEFILE_SOURCE -D_LARGE_FILES -D_FILE_OFFSET_BITS=64 + -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS) + + check_include_file("sys/types.h" HAVE_SYS_TYPES_H) + check_include_file("inttypes.h" HAVE_INTTYPES_H) + check_include_file("stddef.h" HAVE_STDDEF_H) + check_include_file("stdint.h" HAVE_STDINT_H) + + # LFS type1: 8 <= sizeof(off_t), fseeko, ftello + check_type_size("off_t" SIZEOF_OFF_T) + if(SIZEOF_OFF_T GREATER 7) + check_symbol_exists("fseeko" "stdio.h" HAVE_FSEEKO) + check_symbol_exists("ftello" "stdio.h" HAVE_FTELLO) + if(HAVE_FSEEKO AND HAVE_FTELLO) + set(LFS_OFF_T "off_t") + set(LFS_FOPEN "fopen") + set(LFS_FSEEK "fseeko") + set(LFS_FTELL "ftello") + check_symbol_exists("PRIdMAX" "inttypes.h" HAVE_PRIDMAX) + if(HAVE_PRIDMAX) + set(LFS_PRID "PRIdMAX") + else(HAVE_PRIDMAX) + check_type_size("long" SIZEOF_LONG) + check_type_size("int" SIZEOF_INT) + if(SIZEOF_OFF_T GREATER SIZEOF_LONG) + set(LFS_PRID "\"lld\"") + elseif(SIZEOF_LONG GREATER SIZEOF_INT) + set(LFS_PRID "\"ld\"") + else(SIZEOF_OFF_T GREATER SIZEOF_LONG) + set(LFS_PRID "\"d\"") + endif(SIZEOF_OFF_T GREATER SIZEOF_LONG) + endif(HAVE_PRIDMAX) + endif(HAVE_FSEEKO AND HAVE_FTELLO) + endif(SIZEOF_OFF_T GREATER 7) + + # LFS type2: 8 <= sizeof(off64_t), fopen64, fseeko64, ftello64 + if(NOT LFS_OFF_T) + check_type_size("off64_t" SIZEOF_OFF64_T) + if(SIZEOF_OFF64_T GREATER 7) + check_symbol_exists("fopen64" "stdio.h" HAVE_FOPEN64) + check_symbol_exists("fseeko64" "stdio.h" HAVE_FSEEKO64) + check_symbol_exists("ftello64" "stdio.h" HAVE_FTELLO64) + if(HAVE_FOPEN64 AND HAVE_FSEEKO64 AND HAVE_FTELLO64) + set(LFS_OFF_T "off64_t") + set(LFS_FOPEN "fopen64") + set(LFS_FSEEK "fseeko64") + set(LFS_FTELL "ftello64") + check_symbol_exists("PRIdMAX" "inttypes.h" HAVE_PRIDMAX) + if(HAVE_PRIDMAX) + set(LFS_PRID "PRIdMAX") + else(HAVE_PRIDMAX) + check_type_size("long" SIZEOF_LONG) + check_type_size("int" SIZEOF_INT) + if(SIZEOF_OFF64_T GREATER SIZEOF_LONG) + set(LFS_PRID "\"lld\"") + elseif(SIZEOF_LONG GREATER SIZEOF_INT) + set(LFS_PRID "\"ld\"") + else(SIZEOF_OFF64_T GREATER SIZEOF_LONG) + set(LFS_PRID "\"d\"") + endif(SIZEOF_OFF64_T GREATER SIZEOF_LONG) + endif(HAVE_PRIDMAX) + endif(HAVE_FOPEN64 AND HAVE_FSEEKO64 AND HAVE_FTELLO64) + endif(SIZEOF_OFF64_T GREATER 7) + endif(NOT LFS_OFF_T) + + # LFS type3: 8 <= sizeof(__int64), _fseeki64, _ftelli64 + if(NOT LFS_OFF_T) + check_type_size("__int64" SIZEOF___INT64) + if(SIZEOF___INT64 GREATER 7) + check_symbol_exists("_fseeki64" "stdio.h" HAVE__FSEEKI64) + check_symbol_exists("_ftelli64" "stdio.h" HAVE__FTELLI64) + if(HAVE__FSEEKI64 AND HAVE__FTELLI64) + set(LFS_OFF_T "__int64") + set(LFS_FOPEN "fopen") + set(LFS_FSEEK "_fseeki64") + set(LFS_FTELL "_ftelli64") + set(LFS_PRID "\"I64d\"") + endif(HAVE__FSEEKI64 AND HAVE__FTELLI64) + endif(SIZEOF___INT64 GREATER 7) + endif(NOT LFS_OFF_T) + + set(CMAKE_REQUIRED_DEFINITIONS "${SAFE_CMAKE_REQUIRED_DEFINITIONS}") + endif(${_isenable}) + + if(NOT LFS_OFF_T) + ## not found + set(LFS_OFF_T "long") + set(LFS_FOPEN "fopen") + set(LFS_FSEEK "fseek") + set(LFS_FTELL "ftell") + set(LFS_PRID "\"ld\"") + endif(NOT LFS_OFF_T) + +endmacro(check_lfs) diff --git a/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/ProjectCPack.cmake b/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/ProjectCPack.cmake new file mode 100644 index 00000000..7c105f93 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/ProjectCPack.cmake @@ -0,0 +1,38 @@ +# If the cmake version includes cpack, use it +IF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake") + SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_DESCRIPTION}") + SET(CPACK_PACKAGE_VENDOR "${PROJECT_VENDOR}") + SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") + SET(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") + SET(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}") + SET(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}") +# SET(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME} ${PROJECT_VERSION}") + SET(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_FULL}") + + IF(NOT DEFINED CPACK_SYSTEM_NAME) + SET(CPACK_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") + ENDIF(NOT DEFINED CPACK_SYSTEM_NAME) + + IF(${CPACK_SYSTEM_NAME} MATCHES Windows) + IF(CMAKE_CL_64) + SET(CPACK_SYSTEM_NAME win64-${CMAKE_SYSTEM_PROCESSOR}) + ELSE(CMAKE_CL_64) + SET(CPACK_SYSTEM_NAME win32-${CMAKE_SYSTEM_PROCESSOR}) + ENDIF(CMAKE_CL_64) + ENDIF(${CPACK_SYSTEM_NAME} MATCHES Windows) + + IF(NOT DEFINED CPACK_PACKAGE_FILE_NAME) + SET(CPACK_PACKAGE_FILE_NAME "${CPACK_SOURCE_PACKAGE_FILE_NAME}-${CPACK_SYSTEM_NAME}") + ENDIF(NOT DEFINED CPACK_PACKAGE_FILE_NAME) + + SET(CPACK_PACKAGE_CONTACT "${PROJECT_CONTACT}") + IF(UNIX) + SET(CPACK_STRIP_FILES "") + SET(CPACK_SOURCE_STRIP_FILES "") +# SET(CPACK_PACKAGE_EXECUTABLES "ccmake" "CMake") + ENDIF(UNIX) + SET(CPACK_SOURCE_IGNORE_FILES "/CVS/" "/build/" "/\\\\.build/" "/\\\\.svn/" "~$") + # include CPack model once all variables are set + INCLUDE(CPack) +ENDIF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake") diff --git a/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/cmake_uninstall.cmake.in b/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/cmake_uninstall.cmake.in new file mode 100644 index 00000000..8366a835 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/CMakeModules/cmake_uninstall.cmake.in @@ -0,0 +1,36 @@ +IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +STRING(REGEX REPLACE "\n" ";" files "${files}") + +SET(NUM 0) +FOREACH(file ${files}) + IF(EXISTS "$ENV{DESTDIR}${file}") + MESSAGE(STATUS "Looking for \"$ENV{DESTDIR}${file}\" - found") + SET(UNINSTALL_CHECK_${NUM} 1) + ELSE(EXISTS "$ENV{DESTDIR}${file}") + MESSAGE(STATUS "Looking for \"$ENV{DESTDIR}${file}\" - not found") + SET(UNINSTALL_CHECK_${NUM} 0) + ENDIF(EXISTS "$ENV{DESTDIR}${file}") + MATH(EXPR NUM "1 + ${NUM}") +ENDFOREACH(file) + +SET(NUM 0) +FOREACH(file ${files}) + IF(${UNINSTALL_CHECK_${NUM}}) + MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + EXEC_PROGRAM( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + IF(NOT "${rm_retval}" STREQUAL 0) + MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + ENDIF(NOT "${rm_retval}" STREQUAL 0) + ENDIF(${UNINSTALL_CHECK_${NUM}}) + MATH(EXPR NUM "1 + ${NUM}") +ENDFOREACH(file) + +FILE(REMOVE "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") diff --git a/Tools/unix/lzsa/src/libdivsufsort/LICENSE b/Tools/unix/lzsa/src/libdivsufsort/LICENSE new file mode 100644 index 00000000..249efa49 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2003 Yuta Mori All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Tools/unix/lzsa/src/libdivsufsort/README.md b/Tools/unix/lzsa/src/libdivsufsort/README.md new file mode 100644 index 00000000..381a1888 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/README.md @@ -0,0 +1,140 @@ +# libdivsufsort + +libdivsufsort is a software library that implements a lightweight suffix array construction algorithm. + +## News +* 2015-03-21: The project has moved from [Google Code](http://code.google.com/p/libdivsufsort/) to [GitHub](https://github.com/y-256/libdivsufsort) + +## Introduction +This library provides a simple and an efficient C API to construct a suffix array and a Burrows-Wheeler transformed string from a given string over a constant-size alphabet. +The algorithm runs in O(n log n) worst-case time using only 5n+O(1) bytes of memory space, where n is the length of +the string. + +## Build requirements +* An ANSI C Compiler (e.g. GNU GCC) +* [CMake](http://www.cmake.org/ "CMake") version 2.4.2 or newer +* CMake-supported build tool + +## Building on GNU/Linux +1. Get the source code from GitHub. You can either + * use git to clone the repository + ``` + git clone https://github.com/y-256/libdivsufsort.git + ``` + * or download a [zip file](../../archive/master.zip) directly +2. Create a `build` directory in the package source directory. +```shell +$ cd libdivsufsort +$ mkdir build +$ cd build +``` +3. Configure the package for your system. +If you want to install to a different location, change the -DCMAKE_INSTALL_PREFIX option. +```shell +$ cmake -DCMAKE_BUILD_TYPE="Release" \ +-DCMAKE_INSTALL_PREFIX="/usr/local" .. +``` +4. Compile the package. +```shell +$ make +``` +5. (Optional) Install the library and header files. +```shell +$ sudo make install +``` + +## API +```c +/* Data types */ +typedef int32_t saint_t; +typedef int32_t saidx_t; +typedef uint8_t sauchar_t; + +/* + * Constructs the suffix array of a given string. + * @param T[0..n-1] The input string. + * @param SA[0..n-1] The output array or suffixes. + * @param n The length of the given string. + * @return 0 if no error occurred, -1 or -2 otherwise. + */ +saint_t +divsufsort(const sauchar_t *T, saidx_t *SA, saidx_t n); + +/* + * Constructs the burrows-wheeler transformed string of a given string. + * @param T[0..n-1] The input string. + * @param U[0..n-1] The output string. (can be T) + * @param A[0..n-1] The temporary array. (can be NULL) + * @param n The length of the given string. + * @return The primary index if no error occurred, -1 or -2 otherwise. + */ +saidx_t +divbwt(const sauchar_t *T, sauchar_t *U, saidx_t *A, saidx_t n); +``` + +## Example Usage +```c +#include +#include +#include + +#include + +int main() { + // intput data + char *Text = "abracadabra"; + int n = strlen(Text); + int i, j; + + // allocate + int *SA = (int *)malloc(n * sizeof(int)); + + // sort + divsufsort((unsigned char *)Text, SA, n); + + // output + for(i = 0; i < n; ++i) { + printf("SA[%2d] = %2d: ", i, SA[i]); + for(j = SA[i]; j < n; ++j) { + printf("%c", Text[j]); + } + printf("$\n"); + } + + // deallocate + free(SA); + + return 0; +} +``` +See the [examples](examples) directory for a few other examples. + +## Benchmarks +See [Benchmarks](https://github.com/y-256/libdivsufsort/blob/wiki/SACA_Benchmarks.md) page for details. + +## License +libdivsufsort is released under the [MIT license](LICENSE "MIT license"). +> The MIT License (MIT) +> +> Copyright (c) 2003 Yuta Mori All rights reserved. +> +> Permission is hereby granted, free of charge, to any person obtaining a copy +> of this software and associated documentation files (the "Software"), to deal +> in the Software without restriction, including without limitation the rights +> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +> copies of the Software, and to permit persons to whom the Software is +> furnished to do so, subject to the following conditions: +> +> The above copyright notice and this permission notice shall be included in all +> copies or substantial portions of the Software. +> +> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +> SOFTWARE. + +## Author +* Yuta Mori diff --git a/Tools/unix/lzsa/src/libdivsufsort/VERSION.cmake b/Tools/unix/lzsa/src/libdivsufsort/VERSION.cmake new file mode 100644 index 00000000..3f11ac18 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/VERSION.cmake @@ -0,0 +1,23 @@ +set(PROJECT_VERSION_MAJOR "2") +set(PROJECT_VERSION_MINOR "0") +set(PROJECT_VERSION_PATCH "2") +set(PROJECT_VERSION_EXTRA "-1") +set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}") +set(PROJECT_VERSION_FULL "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${PROJECT_VERSION_EXTRA}") + +set(LIBRARY_VERSION "3.0.1") +set(LIBRARY_SOVERSION "3") + +## Git revision number ## +if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") + execute_process(COMMAND git describe --tags HEAD + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE GIT_DESCRIBE_TAGS ERROR_QUIET) + if(GIT_DESCRIBE_TAGS) + string(REGEX REPLACE "^v(.*)" "\\1" GIT_REVISION "${GIT_DESCRIBE_TAGS}") + string(STRIP "${GIT_REVISION}" GIT_REVISION) + if(GIT_REVISION) + set(PROJECT_VERSION_FULL "${GIT_REVISION}") + endif(GIT_REVISION) + endif(GIT_DESCRIBE_TAGS) +endif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") diff --git a/Tools/unix/lzsa/src/libdivsufsort/examples/CMakeLists.txt b/Tools/unix/lzsa/src/libdivsufsort/examples/CMakeLists.txt new file mode 100644 index 00000000..e801c81a --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/examples/CMakeLists.txt @@ -0,0 +1,11 @@ +## Add definitions ## +add_definitions(-D_LARGEFILE_SOURCE -D_LARGE_FILES -D_FILE_OFFSET_BITS=64) + +## Targets ## +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../include" + "${CMAKE_CURRENT_BINARY_DIR}/../include") +link_directories("${CMAKE_CURRENT_BINARY_DIR}/../lib") +foreach(src suftest mksary sasearch bwt unbwt) + add_executable(${src} ${src}.c) + target_link_libraries(${src} divsufsort) +endforeach(src) diff --git a/Tools/unix/lzsa/src/libdivsufsort/examples/bwt.c b/Tools/unix/lzsa/src/libdivsufsort/examples/bwt.c new file mode 100644 index 00000000..5a362d01 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/examples/bwt.c @@ -0,0 +1,220 @@ +/* + * bwt.c for libdivsufsort + * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#if HAVE_STRING_H +# include +#endif +#if HAVE_STDLIB_H +# include +#endif +#if HAVE_MEMORY_H +# include +#endif +#if HAVE_STDDEF_H +# include +#endif +#if HAVE_STRINGS_H +# include +#endif +#if HAVE_SYS_TYPES_H +# include +#endif +#if HAVE_IO_H && HAVE_FCNTL_H +# include +# include +#endif +#include +#include +#include "lfs.h" + + +static +size_t +write_int(FILE *fp, saidx_t n) { + unsigned char c[4]; + c[0] = (unsigned char)((n >> 0) & 0xff), c[1] = (unsigned char)((n >> 8) & 0xff), + c[2] = (unsigned char)((n >> 16) & 0xff), c[3] = (unsigned char)((n >> 24) & 0xff); + return fwrite(c, sizeof(unsigned char), 4, fp); +} + +static +void +print_help(const char *progname, int status) { + fprintf(stderr, + "bwt, a burrows-wheeler transform program, version %s.\n", + divsufsort_version()); + fprintf(stderr, "usage: %s [-b num] INFILE OUTFILE\n", progname); + fprintf(stderr, " -b num set block size to num MiB [1..512] (default: 32)\n\n"); + exit(status); +} + +int +main(int argc, const char *argv[]) { + FILE *fp, *ofp; + const char *fname, *ofname; + sauchar_t *T; + saidx_t *SA; + LFS_OFF_T n; + size_t m; + saidx_t pidx; + clock_t start,finish; + saint_t i, blocksize = 32, needclose = 3; + + /* Check arguments. */ + if((argc == 1) || + (strcmp(argv[1], "-h") == 0) || + (strcmp(argv[1], "--help") == 0)) { print_help(argv[0], EXIT_SUCCESS); } + if((argc != 3) && (argc != 5)) { print_help(argv[0], EXIT_FAILURE); } + i = 1; + if(argc == 5) { + if(strcmp(argv[i], "-b") != 0) { print_help(argv[0], EXIT_FAILURE); } + blocksize = atoi(argv[i + 1]); + if(blocksize < 0) { blocksize = 1; } + else if(512 < blocksize) { blocksize = 512; } + i += 2; + } + blocksize <<= 20; + + /* Open a file for reading. */ + if(strcmp(argv[i], "-") != 0) { +#if HAVE_FOPEN_S + if(fopen_s(&fp, fname = argv[i], "rb") != 0) { +#else + if((fp = LFS_FOPEN(fname = argv[i], "rb")) == NULL) { +#endif + fprintf(stderr, "%s: Cannot open file `%s': ", argv[0], fname); + perror(NULL); + exit(EXIT_FAILURE); + } + } else { +#if HAVE__SETMODE && HAVE__FILENO + if(_setmode(_fileno(stdin), _O_BINARY) == -1) { + fprintf(stderr, "%s: Cannot set mode: ", argv[0]); + perror(NULL); + exit(EXIT_FAILURE); + } +#endif + fp = stdin; + fname = "stdin"; + needclose ^= 1; + } + i += 1; + + /* Open a file for writing. */ + if(strcmp(argv[i], "-") != 0) { +#if HAVE_FOPEN_S + if(fopen_s(&ofp, ofname = argv[i], "wb") != 0) { +#else + if((ofp = LFS_FOPEN(ofname = argv[i], "wb")) == NULL) { +#endif + fprintf(stderr, "%s: Cannot open file `%s': ", argv[0], ofname); + perror(NULL); + exit(EXIT_FAILURE); + } + } else { +#if HAVE__SETMODE && HAVE__FILENO + if(_setmode(_fileno(stdout), _O_BINARY) == -1) { + fprintf(stderr, "%s: Cannot set mode: ", argv[0]); + perror(NULL); + exit(EXIT_FAILURE); + } +#endif + ofp = stdout; + ofname = "stdout"; + needclose ^= 2; + } + + /* Get the file size. */ + if(LFS_FSEEK(fp, 0, SEEK_END) == 0) { + n = LFS_FTELL(fp); + rewind(fp); + if(n < 0) { + fprintf(stderr, "%s: Cannot ftell `%s': ", argv[0], fname); + perror(NULL); + exit(EXIT_FAILURE); + } + if(0x20000000L < n) { n = 0x20000000L; } + if((blocksize == 0) || (n < blocksize)) { blocksize = (saidx_t)n; } + } else if(blocksize == 0) { blocksize = 32 << 20; } + + /* Allocate 5blocksize bytes of memory. */ + T = (sauchar_t *)malloc(blocksize * sizeof(sauchar_t)); + SA = (saidx_t *)malloc(blocksize * sizeof(saidx_t)); + if((T == NULL) || (SA == NULL)) { + fprintf(stderr, "%s: Cannot allocate memory.\n", argv[0]); + exit(EXIT_FAILURE); + } + + /* Write the blocksize. */ + if(write_int(ofp, blocksize) != 4) { + fprintf(stderr, "%s: Cannot write to `%s': ", argv[0], ofname); + perror(NULL); + exit(EXIT_FAILURE); + } + + fprintf(stderr, " BWT (blocksize %" PRIdSAINT_T ") ... ", blocksize); + start = clock(); + for(n = 0; 0 < (m = fread(T, sizeof(sauchar_t), blocksize, fp)); n += m) { + /* Burrows-Wheeler Transform. */ + pidx = divbwt(T, T, SA, m); + if(pidx < 0) { + fprintf(stderr, "%s (bw_transform): %s.\n", + argv[0], + (pidx == -1) ? "Invalid arguments" : "Cannot allocate memory"); + exit(EXIT_FAILURE); + } + + /* Write the bwted data. */ + if((write_int(ofp, pidx) != 4) || + (fwrite(T, sizeof(sauchar_t), m, ofp) != m)) { + fprintf(stderr, "%s: Cannot write to `%s': ", argv[0], ofname); + perror(NULL); + exit(EXIT_FAILURE); + } + } + if(ferror(fp)) { + fprintf(stderr, "%s: Cannot read from `%s': ", argv[0], fname); + perror(NULL); + exit(EXIT_FAILURE); + } + finish = clock(); + fprintf(stderr, "%" PRIdOFF_T " bytes: %.4f sec\n", + n, (double)(finish - start) / (double)CLOCKS_PER_SEC); + + /* Close files */ + if(needclose & 1) { fclose(fp); } + if(needclose & 2) { fclose(ofp); } + + /* Deallocate memory. */ + free(SA); + free(T); + + return 0; +} diff --git a/Tools/unix/lzsa/src/libdivsufsort/examples/mksary.c b/Tools/unix/lzsa/src/libdivsufsort/examples/mksary.c new file mode 100644 index 00000000..b48177cf --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/examples/mksary.c @@ -0,0 +1,193 @@ +/* + * mksary.c for libdivsufsort + * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#if HAVE_STRING_H +# include +#endif +#if HAVE_STDLIB_H +# include +#endif +#if HAVE_MEMORY_H +# include +#endif +#if HAVE_STDDEF_H +# include +#endif +#if HAVE_STRINGS_H +# include +#endif +#if HAVE_SYS_TYPES_H +# include +#endif +#if HAVE_IO_H && HAVE_FCNTL_H +# include +# include +#endif +#include +#include +#include "lfs.h" + + +static +void +print_help(const char *progname, int status) { + fprintf(stderr, + "mksary, a simple suffix array builder, version %s.\n", + divsufsort_version()); + fprintf(stderr, "usage: %s INFILE OUTFILE\n\n", progname); + exit(status); +} + +int +main(int argc, const char *argv[]) { + FILE *fp, *ofp; + const char *fname, *ofname; + sauchar_t *T; + saidx_t *SA; + LFS_OFF_T n; + clock_t start, finish; + saint_t needclose = 3; + + /* Check arguments. */ + if((argc == 1) || + (strcmp(argv[1], "-h") == 0) || + (strcmp(argv[1], "--help") == 0)) { print_help(argv[0], EXIT_SUCCESS); } + if(argc != 3) { print_help(argv[0], EXIT_FAILURE); } + + /* Open a file for reading. */ + if(strcmp(argv[1], "-") != 0) { +#if HAVE_FOPEN_S + if(fopen_s(&fp, fname = argv[1], "rb") != 0) { +#else + if((fp = LFS_FOPEN(fname = argv[1], "rb")) == NULL) { +#endif + fprintf(stderr, "%s: Cannot open file `%s': ", argv[0], fname); + perror(NULL); + exit(EXIT_FAILURE); + } + } else { +#if HAVE__SETMODE && HAVE__FILENO + if(_setmode(_fileno(stdin), _O_BINARY) == -1) { + fprintf(stderr, "%s: Cannot set mode: ", argv[0]); + perror(NULL); + exit(EXIT_FAILURE); + } +#endif + fp = stdin; + fname = "stdin"; + needclose ^= 1; + } + + /* Open a file for writing. */ + if(strcmp(argv[2], "-") != 0) { +#if HAVE_FOPEN_S + if(fopen_s(&ofp, ofname = argv[2], "wb") != 0) { +#else + if((ofp = LFS_FOPEN(ofname = argv[2], "wb")) == NULL) { +#endif + fprintf(stderr, "%s: Cannot open file `%s': ", argv[0], ofname); + perror(NULL); + exit(EXIT_FAILURE); + } + } else { +#if HAVE__SETMODE && HAVE__FILENO + if(_setmode(_fileno(stdout), _O_BINARY) == -1) { + fprintf(stderr, "%s: Cannot set mode: ", argv[0]); + perror(NULL); + exit(EXIT_FAILURE); + } +#endif + ofp = stdout; + ofname = "stdout"; + needclose ^= 2; + } + + /* Get the file size. */ + if(LFS_FSEEK(fp, 0, SEEK_END) == 0) { + n = LFS_FTELL(fp); + rewind(fp); + if(n < 0) { + fprintf(stderr, "%s: Cannot ftell `%s': ", argv[0], fname); + perror(NULL); + exit(EXIT_FAILURE); + } + if(0x7fffffff <= n) { + fprintf(stderr, "%s: Input file `%s' is too big.\n", argv[0], fname); + exit(EXIT_FAILURE); + } + } else { + fprintf(stderr, "%s: Cannot fseek `%s': ", argv[0], fname); + perror(NULL); + exit(EXIT_FAILURE); + } + + /* Allocate 5blocksize bytes of memory. */ + T = (sauchar_t *)malloc((size_t)n * sizeof(sauchar_t)); + SA = (saidx_t *)malloc((size_t)n * sizeof(saidx_t)); + if((T == NULL) || (SA == NULL)) { + fprintf(stderr, "%s: Cannot allocate memory.\n", argv[0]); + exit(EXIT_FAILURE); + } + + /* Read n bytes of data. */ + if(fread(T, sizeof(sauchar_t), (size_t)n, fp) != (size_t)n) { + fprintf(stderr, "%s: %s `%s': ", + argv[0], + (ferror(fp) || !feof(fp)) ? "Cannot read from" : "Unexpected EOF in", + fname); + perror(NULL); + exit(EXIT_FAILURE); + } + if(needclose & 1) { fclose(fp); } + + /* Construct the suffix array. */ + fprintf(stderr, "%s: %" PRIdOFF_T " bytes ... ", fname, n); + start = clock(); + if(divsufsort(T, SA, (saidx_t)n) != 0) { + fprintf(stderr, "%s: Cannot allocate memory.\n", argv[0]); + exit(EXIT_FAILURE); + } + finish = clock(); + fprintf(stderr, "%.4f sec\n", (double)(finish - start) / (double)CLOCKS_PER_SEC); + + /* Write the suffix array. */ + if(fwrite(SA, sizeof(saidx_t), (size_t)n, ofp) != (size_t)n) { + fprintf(stderr, "%s: Cannot write to `%s': ", argv[0], ofname); + perror(NULL); + exit(EXIT_FAILURE); + } + if(needclose & 2) { fclose(ofp); } + + /* Deallocate memory. */ + free(SA); + free(T); + + return 0; +} diff --git a/Tools/unix/lzsa/src/libdivsufsort/examples/sasearch.c b/Tools/unix/lzsa/src/libdivsufsort/examples/sasearch.c new file mode 100644 index 00000000..7e5ca4fe --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/examples/sasearch.c @@ -0,0 +1,165 @@ +/* + * sasearch.c for libdivsufsort + * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#if HAVE_STRING_H +# include +#endif +#if HAVE_STDLIB_H +# include +#endif +#if HAVE_MEMORY_H +# include +#endif +#if HAVE_STDDEF_H +# include +#endif +#if HAVE_STRINGS_H +# include +#endif +#if HAVE_SYS_TYPES_H +# include +#endif +#if HAVE_IO_H && HAVE_FCNTL_H +# include +# include +#endif +#include +#include "lfs.h" + + +static +void +print_help(const char *progname, int status) { + fprintf(stderr, + "sasearch, a simple SA-based full-text search tool, version %s\n", + divsufsort_version()); + fprintf(stderr, "usage: %s PATTERN FILE SAFILE\n\n", progname); + exit(status); +} + +int +main(int argc, const char *argv[]) { + FILE *fp; + const char *P; + sauchar_t *T; + saidx_t *SA; + LFS_OFF_T n; + size_t Psize; + saidx_t i, size, left; + + if((argc == 1) || + (strcmp(argv[1], "-h") == 0) || + (strcmp(argv[1], "--help") == 0)) { print_help(argv[0], EXIT_SUCCESS); } + if(argc != 4) { print_help(argv[0], EXIT_FAILURE); } + + P = argv[1]; + Psize = strlen(P); + + /* Open a file for reading. */ +#if HAVE_FOPEN_S + if(fopen_s(&fp, argv[2], "rb") != 0) { +#else + if((fp = LFS_FOPEN(argv[2], "rb")) == NULL) { +#endif + fprintf(stderr, "%s: Cannot open file `%s': ", argv[0], argv[2]); + perror(NULL); + exit(EXIT_FAILURE); + } + + /* Get the file size. */ + if(LFS_FSEEK(fp, 0, SEEK_END) == 0) { + n = LFS_FTELL(fp); + rewind(fp); + if(n < 0) { + fprintf(stderr, "%s: Cannot ftell `%s': ", argv[0], argv[2]); + perror(NULL); + exit(EXIT_FAILURE); + } + } else { + fprintf(stderr, "%s: Cannot fseek `%s': ", argv[0], argv[2]); + perror(NULL); + exit(EXIT_FAILURE); + } + + /* Allocate 5n bytes of memory. */ + T = (sauchar_t *)malloc((size_t)n * sizeof(sauchar_t)); + SA = (saidx_t *)malloc((size_t)n * sizeof(saidx_t)); + if((T == NULL) || (SA == NULL)) { + fprintf(stderr, "%s: Cannot allocate memory.\n", argv[0]); + exit(EXIT_FAILURE); + } + + /* Read n bytes of data. */ + if(fread(T, sizeof(sauchar_t), (size_t)n, fp) != (size_t)n) { + fprintf(stderr, "%s: %s `%s': ", + argv[0], + (ferror(fp) || !feof(fp)) ? "Cannot read from" : "Unexpected EOF in", + argv[2]); + perror(NULL); + exit(EXIT_FAILURE); + } + fclose(fp); + + /* Open the SA file for reading. */ +#if HAVE_FOPEN_S + if(fopen_s(&fp, argv[3], "rb") != 0) { +#else + if((fp = LFS_FOPEN(argv[3], "rb")) == NULL) { +#endif + fprintf(stderr, "%s: Cannot open file `%s': ", argv[0], argv[3]); + perror(NULL); + exit(EXIT_FAILURE); + } + + /* Read n * sizeof(saidx_t) bytes of data. */ + if(fread(SA, sizeof(saidx_t), (size_t)n, fp) != (size_t)n) { + fprintf(stderr, "%s: %s `%s': ", + argv[0], + (ferror(fp) || !feof(fp)) ? "Cannot read from" : "Unexpected EOF in", + argv[3]); + perror(NULL); + exit(EXIT_FAILURE); + } + fclose(fp); + + /* Search and print */ + size = sa_search(T, (saidx_t)n, + (const sauchar_t *)P, (saidx_t)Psize, + SA, (saidx_t)n, &left); + for(i = 0; i < size; ++i) { + fprintf(stdout, "%" PRIdSAIDX_T "\n", SA[left + i]); + } + + /* Deallocate memory. */ + free(SA); + free(T); + + return 0; +} diff --git a/Tools/unix/lzsa/src/libdivsufsort/examples/suftest.c b/Tools/unix/lzsa/src/libdivsufsort/examples/suftest.c new file mode 100644 index 00000000..71892ac1 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/examples/suftest.c @@ -0,0 +1,164 @@ +/* + * suftest.c for libdivsufsort + * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#if HAVE_STRING_H +# include +#endif +#if HAVE_STDLIB_H +# include +#endif +#if HAVE_MEMORY_H +# include +#endif +#if HAVE_STDDEF_H +# include +#endif +#if HAVE_STRINGS_H +# include +#endif +#if HAVE_SYS_TYPES_H +# include +#endif +#if HAVE_IO_H && HAVE_FCNTL_H +# include +# include +#endif +#include +#include +#include "lfs.h" + + +static +void +print_help(const char *progname, int status) { + fprintf(stderr, + "suftest, a suffixsort tester, version %s.\n", + divsufsort_version()); + fprintf(stderr, "usage: %s FILE\n\n", progname); + exit(status); +} + +int +main(int argc, const char *argv[]) { + FILE *fp; + const char *fname; + sauchar_t *T; + saidx_t *SA; + LFS_OFF_T n; + clock_t start, finish; + saint_t needclose = 1; + + /* Check arguments. */ + if((argc == 1) || + (strcmp(argv[1], "-h") == 0) || + (strcmp(argv[1], "--help") == 0)) { print_help(argv[0], EXIT_SUCCESS); } + if(argc != 2) { print_help(argv[0], EXIT_FAILURE); } + + /* Open a file for reading. */ + if(strcmp(argv[1], "-") != 0) { +#if HAVE_FOPEN_S + if(fopen_s(&fp, fname = argv[1], "rb") != 0) { +#else + if((fp = LFS_FOPEN(fname = argv[1], "rb")) == NULL) { +#endif + fprintf(stderr, "%s: Cannot open file `%s': ", argv[0], fname); + perror(NULL); + exit(EXIT_FAILURE); + } + } else { +#if HAVE__SETMODE && HAVE__FILENO + if(_setmode(_fileno(stdin), _O_BINARY) == -1) { + fprintf(stderr, "%s: Cannot set mode: ", argv[0]); + perror(NULL); + exit(EXIT_FAILURE); + } +#endif + fp = stdin; + fname = "stdin"; + needclose = 0; + } + + /* Get the file size. */ + if(LFS_FSEEK(fp, 0, SEEK_END) == 0) { + n = LFS_FTELL(fp); + rewind(fp); + if(n < 0) { + fprintf(stderr, "%s: Cannot ftell `%s': ", argv[0], fname); + perror(NULL); + exit(EXIT_FAILURE); + } + if(0x7fffffff <= n) { + fprintf(stderr, "%s: Input file `%s' is too big.\n", argv[0], fname); + exit(EXIT_FAILURE); + } + } else { + fprintf(stderr, "%s: Cannot fseek `%s': ", argv[0], fname); + perror(NULL); + exit(EXIT_FAILURE); + } + + /* Allocate 5n bytes of memory. */ + T = (sauchar_t *)malloc((size_t)n * sizeof(sauchar_t)); + SA = (saidx_t *)malloc((size_t)n * sizeof(saidx_t)); + if((T == NULL) || (SA == NULL)) { + fprintf(stderr, "%s: Cannot allocate memory.\n", argv[0]); + exit(EXIT_FAILURE); + } + + /* Read n bytes of data. */ + if(fread(T, sizeof(sauchar_t), (size_t)n, fp) != (size_t)n) { + fprintf(stderr, "%s: %s `%s': ", + argv[0], + (ferror(fp) || !feof(fp)) ? "Cannot read from" : "Unexpected EOF in", + argv[1]); + perror(NULL); + exit(EXIT_FAILURE); + } + if(needclose & 1) { fclose(fp); } + + /* Construct the suffix array. */ + fprintf(stderr, "%s: %" PRIdOFF_T " bytes ... ", fname, n); + start = clock(); + if(divsufsort(T, SA, (saidx_t)n) != 0) { + fprintf(stderr, "%s: Cannot allocate memory.\n", argv[0]); + exit(EXIT_FAILURE); + } + finish = clock(); + fprintf(stderr, "%.4f sec\n", (double)(finish - start) / (double)CLOCKS_PER_SEC); + + /* Check the suffix array. */ + if(sufcheck(T, SA, (saidx_t)n, 1) != 0) { exit(EXIT_FAILURE); } + + /* Deallocate memory. */ + free(SA); + free(T); + + return 0; +} diff --git a/Tools/unix/lzsa/src/libdivsufsort/examples/unbwt.c b/Tools/unix/lzsa/src/libdivsufsort/examples/unbwt.c new file mode 100644 index 00000000..c0f19e97 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/examples/unbwt.c @@ -0,0 +1,207 @@ +/* + * unbwt.c for libdivsufsort + * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#if HAVE_STRING_H +# include +#endif +#if HAVE_STDLIB_H +# include +#endif +#if HAVE_MEMORY_H +# include +#endif +#if HAVE_STDDEF_H +# include +#endif +#if HAVE_STRINGS_H +# include +#endif +#if HAVE_SYS_TYPES_H +# include +#endif +#if HAVE_IO_H && HAVE_FCNTL_H +# include +# include +#endif +#include +#include +#include "lfs.h" + + +static +size_t +read_int(FILE *fp, saidx_t *n) { + unsigned char c[4]; + size_t m = fread(c, sizeof(unsigned char), 4, fp); + if(m == 4) { + *n = (c[0] << 0) | (c[1] << 8) | + (c[2] << 16) | (c[3] << 24); + } + return m; +} + +static +void +print_help(const char *progname, int status) { + fprintf(stderr, + "unbwt, an inverse burrows-wheeler transform program, version %s.\n", + divsufsort_version()); + fprintf(stderr, "usage: %s INFILE OUTFILE\n\n", progname); + exit(status); +} + +int +main(int argc, const char *argv[]) { + FILE *fp, *ofp; + const char *fname, *ofname; + sauchar_t *T; + saidx_t *A; + LFS_OFF_T n; + size_t m; + saidx_t pidx; + clock_t start, finish; + saint_t err, blocksize, needclose = 3; + + /* Check arguments. */ + if((argc == 1) || + (strcmp(argv[1], "-h") == 0) || + (strcmp(argv[1], "--help") == 0)) { print_help(argv[0], EXIT_SUCCESS); } + if(argc != 3) { print_help(argv[0], EXIT_FAILURE); } + + /* Open a file for reading. */ + if(strcmp(argv[1], "-") != 0) { +#if HAVE_FOPEN_S + if(fopen_s(&fp, fname = argv[1], "rb") != 0) { +#else + if((fp = LFS_FOPEN(fname = argv[1], "rb")) == NULL) { +#endif + fprintf(stderr, "%s: Cannot open file `%s': ", argv[0], fname); + perror(NULL); + exit(EXIT_FAILURE); + } + } else { +#if HAVE__SETMODE && HAVE__FILENO + if(_setmode(_fileno(stdin), _O_BINARY) == -1) { + fprintf(stderr, "%s: Cannot set mode: ", argv[0]); + perror(NULL); + exit(EXIT_FAILURE); + } +#endif + fp = stdin; + fname = "stdin"; + needclose ^= 1; + } + + /* Open a file for writing. */ + if(strcmp(argv[2], "-") != 0) { +#if HAVE_FOPEN_S + if(fopen_s(&ofp, ofname = argv[2], "wb") != 0) { +#else + if((ofp = LFS_FOPEN(ofname = argv[2], "wb")) == NULL) { +#endif + fprintf(stderr, "%s: Cannot open file `%s': ", argv[0], ofname); + perror(NULL); + exit(EXIT_FAILURE); + } + } else { +#if HAVE__SETMODE && HAVE__FILENO + if(_setmode(_fileno(stdout), _O_BINARY) == -1) { + fprintf(stderr, "%s: Cannot set mode: ", argv[0]); + perror(NULL); + exit(EXIT_FAILURE); + } +#endif + ofp = stdout; + ofname = "stdout"; + needclose ^= 2; + } + + /* Read the blocksize. */ + if(read_int(fp, &blocksize) != 4) { + fprintf(stderr, "%s: Cannot read from `%s': ", argv[0], fname); + perror(NULL); + exit(EXIT_FAILURE); + } + + /* Allocate 5blocksize bytes of memory. */ + T = (sauchar_t *)malloc(blocksize * sizeof(sauchar_t)); + A = (saidx_t *)malloc(blocksize * sizeof(saidx_t)); + if((T == NULL) || (A == NULL)) { + fprintf(stderr, "%s: Cannot allocate memory.\n", argv[0]); + exit(EXIT_FAILURE); + } + + fprintf(stderr, "UnBWT (blocksize %" PRIdSAINT_T ") ... ", blocksize); + start = clock(); + for(n = 0; (m = read_int(fp, &pidx)) != 0; n += m) { + /* Read blocksize bytes of data. */ + if((m != 4) || ((m = fread(T, sizeof(sauchar_t), blocksize, fp)) == 0)) { + fprintf(stderr, "%s: %s `%s': ", + argv[0], + (ferror(fp) || !feof(fp)) ? "Cannot read from" : "Unexpected EOF in", + fname); + perror(NULL); + exit(EXIT_FAILURE); + } + + /* Inverse Burrows-Wheeler Transform. */ + if((err = inverse_bw_transform(T, T, A, m, pidx)) != 0) { + fprintf(stderr, "%s (reverseBWT): %s.\n", + argv[0], + (err == -1) ? "Invalid data" : "Cannot allocate memory"); + exit(EXIT_FAILURE); + } + + /* Write m bytes of data. */ + if(fwrite(T, sizeof(sauchar_t), m, ofp) != m) { + fprintf(stderr, "%s: Cannot write to `%s': ", argv[0], ofname); + perror(NULL); + exit(EXIT_FAILURE); + } + } + if(ferror(fp)) { + fprintf(stderr, "%s: Cannot read from `%s': ", argv[0], fname); + perror(NULL); + exit(EXIT_FAILURE); + } + finish = clock(); + fprintf(stderr, "%" PRIdOFF_T " bytes: %.4f sec\n", + n, (double)(finish - start) / (double)CLOCKS_PER_SEC); + + /* Close files */ + if(needclose & 1) { fclose(fp); } + if(needclose & 2) { fclose(ofp); } + + /* Deallocate memory. */ + free(A); + free(T); + + return 0; +} diff --git a/Tools/unix/lzsa/src/libdivsufsort/include/CMakeLists.txt b/Tools/unix/lzsa/src/libdivsufsort/include/CMakeLists.txt new file mode 100644 index 00000000..37781ccb --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/include/CMakeLists.txt @@ -0,0 +1,162 @@ +include(CheckIncludeFiles) +include(CheckIncludeFile) +include(CheckSymbolExists) +include(CheckTypeSize) +include(CheckFunctionKeywords) +include(CheckLFS) + +## Checks for header files ## +check_include_file("inttypes.h" HAVE_INTTYPES_H) +check_include_file("memory.h" HAVE_MEMORY_H) +check_include_file("stddef.h" HAVE_STDDEF_H) +check_include_file("stdint.h" HAVE_STDINT_H) +check_include_file("stdlib.h" HAVE_STDLIB_H) +check_include_file("string.h" HAVE_STRING_H) +check_include_file("strings.h" HAVE_STRINGS_H) +check_include_file("sys/types.h" HAVE_SYS_TYPES_H) +if(HAVE_INTTYPES_H) + set(INCFILE "#include ") +elseif(HAVE_STDINT_H) + set(INCFILE "#include ") +else(HAVE_INTTYPES_H) + set(INCFILE "") +endif(HAVE_INTTYPES_H) + +## create configuration files from .cmake file ## +if(BUILD_EXAMPLES) + ## Checks for WinIO ## + if(WIN32) + check_include_file("io.h" HAVE_IO_H) + check_include_file("fcntl.h" HAVE_FCNTL_H) + check_symbol_exists("_setmode" "io.h;fcntl.h" HAVE__SETMODE) + if(NOT HAVE__SETMODE) + check_symbol_exists("setmode" "io.h;fcntl.h" HAVE_SETMODE) + endif(NOT HAVE__SETMODE) + check_symbol_exists("_fileno" "stdio.h" HAVE__FILENO) + check_symbol_exists("fopen_s" "stdio.h" HAVE_FOPEN_S) + check_symbol_exists("_O_BINARY" "fcntl.h" HAVE__O_BINARY) + endif(WIN32) + + ## Checks for large file support ## + check_lfs(WITH_LFS) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/lfs.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/lfs.h" @ONLY) +endif(BUILD_EXAMPLES) + +## generate config.h ## +check_function_keywords("inline;__inline;__inline__;__declspec(dllexport);__declspec(dllimport)") +if(HAVE_INLINE) + set(INLINE "inline") +elseif(HAVE___INLINE) + set(INLINE "__inline") +elseif(HAVE___INLINE__) + set(INLINE "__inline__") +else(HAVE_INLINE) + set(INLINE "") +endif(HAVE_INLINE) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/config.h") + +## Checks for types ## +# sauchar_t (8bit) +check_type_size("uint8_t" UINT8_T) +if(HAVE_UINT8_T) + set(SAUCHAR_TYPE "uint8_t") +else(HAVE_UINT8_T) + check_type_size("unsigned char" SIZEOF_UNSIGNED_CHAR) + if("${SIZEOF_UNSIGNED_CHAR}" STREQUAL "1") + set(SAUCHAR_TYPE "unsigned char") + else("${SIZEOF_UNSIGNED_CHAR}" STREQUAL "1") + message(FATAL_ERROR "Cannot find unsigned 8-bit integer type") + endif("${SIZEOF_UNSIGNED_CHAR}" STREQUAL "1") +endif(HAVE_UINT8_T) +# saint_t (32bit) +check_type_size("int32_t" INT32_T) +if(HAVE_INT32_T) + set(SAINT32_TYPE "int32_t") + check_symbol_exists("PRId32" "inttypes.h" HAVE_PRID32) + if(HAVE_PRID32) + set(SAINT32_PRId "PRId32") + else(HAVE_PRID32) + set(SAINT32_PRId "\"d\"") + endif(HAVE_PRID32) +else(HAVE_INT32_T) + check_type_size("int" SIZEOF_INT) + check_type_size("long" SIZEOF_LONG) + check_type_size("short" SIZEOF_SHORT) + check_type_size("__int32" SIZEOF___INT32) + if("${SIZEOF_INT}" STREQUAL "4") + set(SAINT32_TYPE "int") + set(SAINT32_PRId "\"d\"") + elseif("${SIZEOF_LONG}" STREQUAL "4") + set(SAINT32_TYPE "long") + set(SAINT32_PRId "\"ld\"") + elseif("${SIZEOF_SHORT}" STREQUAL "4") + set(SAINT32_TYPE "short") + set(SAINT32_PRId "\"d\"") + elseif("${SIZEOF___INT32}" STREQUAL "4") + set(SAINT32_TYPE "__int32") + set(SAINT32_PRId "\"d\"") + else("${SIZEOF_INT}" STREQUAL "4") + message(FATAL_ERROR "Cannot find 32-bit integer type") + endif("${SIZEOF_INT}" STREQUAL "4") +endif(HAVE_INT32_T) +# saint64_t (64bit) +if(BUILD_DIVSUFSORT64) + check_type_size("int64_t" INT64_T) + if(HAVE_INT64_T) + set(SAINT64_TYPE "int64_t") + check_symbol_exists("PRId64" "inttypes.h" HAVE_PRID64) + if(HAVE_PRID64) + set(SAINT64_PRId "PRId64") + else(HAVE_PRID64) + set(SAINT64_PRId "\"lld\"") + endif(HAVE_PRID64) + else(HAVE_INT64_T) + check_type_size("int" SIZEOF_INT) + check_type_size("long" SIZEOF_LONG) + check_type_size("long long" SIZEOF_LONG_LONG) + check_type_size("__int64" SIZEOF___INT64) + if("${SIZEOF_INT}" STREQUAL "8") + set(SAINT64_TYPE "int") + set(SAINT64_PRId "\"d\"") + elseif("${SIZEOF_LONG}" STREQUAL "8") + set(SAINT64_TYPE "long") + set(SAINT64_PRId "\"ld\"") + elseif("${SIZEOF_LONG_LONG}" STREQUAL "8") + set(SAINT64_TYPE "long long") + set(SAINT64_PRId "\"lld\"") + elseif("${SIZEOF___INT64}" STREQUAL "8") + set(SAINT64_TYPE "__int64") + set(SAINT64_PRId "\"I64d\"") + else("${SIZEOF_INT}" STREQUAL "8") + message(SEND_ERROR "Cannot find 64-bit integer type") + set(BUILD_DIVSUFSORT64 OFF) + endif("${SIZEOF_INT}" STREQUAL "8") + endif(HAVE_INT64_T) +endif(BUILD_DIVSUFSORT64) + +## generate divsufsort.h ## +set(DIVSUFSORT_IMPORT "") +set(DIVSUFSORT_EXPORT "") +if(BUILD_SHARED_LIBS) + if(HAVE___DECLSPEC_DLLIMPORT_) + set(DIVSUFSORT_IMPORT "__declspec(dllimport)") + endif(HAVE___DECLSPEC_DLLIMPORT_) + if(HAVE___DECLSPEC_DLLEXPORT_) + set(DIVSUFSORT_EXPORT "__declspec(dllexport)") + endif(HAVE___DECLSPEC_DLLEXPORT_) +endif(BUILD_SHARED_LIBS) +set(W64BIT "") +set(SAINDEX_TYPE "${SAINT32_TYPE}") +set(SAINDEX_PRId "${SAINT32_PRId}") +set(SAINT_PRId "${SAINT32_PRId}") +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/divsufsort.h.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/divsufsort.h" @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/divsufsort.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +if(BUILD_DIVSUFSORT64) + set(W64BIT "64") + set(SAINDEX_TYPE "${SAINT64_TYPE}") + set(SAINDEX_PRId "${SAINT64_PRId}") + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/divsufsort.h.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/divsufsort64.h" @ONLY) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/divsufsort64.h" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +endif(BUILD_DIVSUFSORT64) diff --git a/Tools/unix/lzsa/src/libdivsufsort/include/config.h.cmake b/Tools/unix/lzsa/src/libdivsufsort/include/config.h.cmake new file mode 100644 index 00000000..6a1cf47d --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/include/config.h.cmake @@ -0,0 +1,81 @@ +/* + * config.h for libdivsufsort + * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _CONFIG_H +#define _CONFIG_H 1 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** Define to the version of this package. **/ +#cmakedefine PROJECT_VERSION_FULL "${PROJECT_VERSION_FULL}" + +/** Define to 1 if you have the header files. **/ +#cmakedefine HAVE_INTTYPES_H 1 +#cmakedefine HAVE_STDDEF_H 1 +#cmakedefine HAVE_STDINT_H 1 +#cmakedefine HAVE_STDLIB_H 1 +#cmakedefine HAVE_STRING_H 1 +#cmakedefine HAVE_STRINGS_H 1 +#cmakedefine HAVE_MEMORY_H 1 +#cmakedefine HAVE_SYS_TYPES_H 1 + +/** for WinIO **/ +#cmakedefine HAVE_IO_H 1 +#cmakedefine HAVE_FCNTL_H 1 +#cmakedefine HAVE__SETMODE 1 +#cmakedefine HAVE_SETMODE 1 +#cmakedefine HAVE__FILENO 1 +#cmakedefine HAVE_FOPEN_S 1 +#cmakedefine HAVE__O_BINARY 1 +#ifndef HAVE__SETMODE +# if HAVE_SETMODE +# define _setmode setmode +# define HAVE__SETMODE 1 +# endif +# if HAVE__SETMODE && !HAVE__O_BINARY +# define _O_BINARY 0 +# define HAVE__O_BINARY 1 +# endif +#endif + +/** for inline **/ +#ifndef INLINE +# define INLINE @INLINE@ +#endif + +/** for VC++ warning **/ +#ifdef _MSC_VER +#pragma warning(disable: 4127) +#endif + + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* _CONFIG_H */ diff --git a/Tools/unix/lzsa/src/libdivsufsort/include/divsufsort.h b/Tools/unix/lzsa/src/libdivsufsort/include/divsufsort.h new file mode 100644 index 00000000..7ebb4126 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/include/divsufsort.h @@ -0,0 +1,189 @@ +/* + * divsufsort.h for libdivsufsort + * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _DIVSUFSORT_H +#define _DIVSUFSORT_H 1 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define DIVSUFSORT_API + +/*- Datatypes -*/ +#ifndef SAUCHAR_T +#define SAUCHAR_T +typedef unsigned char sauchar_t; +#endif /* SAUCHAR_T */ +#ifndef SAINT_T +#define SAINT_T +typedef int saint_t; +#endif /* SAINT_T */ +#ifndef SAIDX_T +#define SAIDX_T +typedef int saidx_t; +#endif /* SAIDX_T */ +#ifndef PRIdSAIDX_T +#define PRIdSAIDX_T "d" +#endif + +/*- divsufsort context */ +typedef struct _divsufsort_ctx_t { + saidx_t *bucket_A; + saidx_t *bucket_B; +} divsufsort_ctx_t; + +/*- Prototypes -*/ + +/** + * Initialize suffix array context + * + * @return 0 for success, or non-zero in case of an error + */ +int divsufsort_init(divsufsort_ctx_t *ctx); + +/** + * Destroy suffix array context + * + * @param ctx suffix array context to destroy + */ +void divsufsort_destroy(divsufsort_ctx_t *ctx); + +/** + * Constructs the suffix array of a given string. + * @param ctx suffix array context + * @param T[0..n-1] The input string. + * @param SA[0..n-1] The output array of suffixes. + * @param n The length of the given string. + * @return 0 if no error occurred, -1 or -2 otherwise. + */ +DIVSUFSORT_API +saint_t divsufsort_build_array(divsufsort_ctx_t *ctx, const sauchar_t *T, saidx_t *SA, saidx_t n); + +#if 0 +/** + * Constructs the burrows-wheeler transformed string of a given string. + * @param T[0..n-1] The input string. + * @param U[0..n-1] The output string. (can be T) + * @param A[0..n-1] The temporary array. (can be NULL) + * @param n The length of the given string. + * @return The primary index if no error occurred, -1 or -2 otherwise. + */ +DIVSUFSORT_API +saidx_t +divbwt(const sauchar_t *T, sauchar_t *U, saidx_t *A, saidx_t n); + +/** + * Returns the version of the divsufsort library. + * @return The version number string. + */ +DIVSUFSORT_API +const char * +divsufsort_version(void); + + +/** + * Constructs the burrows-wheeler transformed string of a given string and suffix array. + * @param T[0..n-1] The input string. + * @param U[0..n-1] The output string. (can be T) + * @param SA[0..n-1] The suffix array. (can be NULL) + * @param n The length of the given string. + * @param idx The output primary index. + * @return 0 if no error occurred, -1 or -2 otherwise. + */ +DIVSUFSORT_API +saint_t +bw_transform(const sauchar_t *T, sauchar_t *U, + saidx_t *SA /* can NULL */, + saidx_t n, saidx_t *idx); + +/** + * Inverse BW-transforms a given BWTed string. + * @param T[0..n-1] The input string. + * @param U[0..n-1] The output string. (can be T) + * @param A[0..n-1] The temporary array. (can be NULL) + * @param n The length of the given string. + * @param idx The primary index. + * @return 0 if no error occurred, -1 or -2 otherwise. + */ +DIVSUFSORT_API +saint_t +inverse_bw_transform(const sauchar_t *T, sauchar_t *U, + saidx_t *A /* can NULL */, + saidx_t n, saidx_t idx); + +/** + * Checks the correctness of a given suffix array. + * @param T[0..n-1] The input string. + * @param SA[0..n-1] The input suffix array. + * @param n The length of the given string. + * @param verbose The verbose mode. + * @return 0 if no error occurred. + */ +DIVSUFSORT_API +saint_t +sufcheck(const sauchar_t *T, const saidx_t *SA, saidx_t n, saint_t verbose); + +/** + * Search for the pattern P in the string T. + * @param T[0..Tsize-1] The input string. + * @param Tsize The length of the given string. + * @param P[0..Psize-1] The input pattern string. + * @param Psize The length of the given pattern string. + * @param SA[0..SAsize-1] The input suffix array. + * @param SAsize The length of the given suffix array. + * @param idx The output index. + * @return The count of matches if no error occurred, -1 otherwise. + */ +DIVSUFSORT_API +saidx_t +sa_search(const sauchar_t *T, saidx_t Tsize, + const sauchar_t *P, saidx_t Psize, + const saidx_t *SA, saidx_t SAsize, + saidx_t *left); + +/** + * Search for the character c in the string T. + * @param T[0..Tsize-1] The input string. + * @param Tsize The length of the given string. + * @param SA[0..SAsize-1] The input suffix array. + * @param SAsize The length of the given suffix array. + * @param c The input character. + * @param idx The output index. + * @return The count of matches if no error occurred, -1 otherwise. + */ +DIVSUFSORT_API +saidx_t +sa_simplesearch(const sauchar_t *T, saidx_t Tsize, + const saidx_t *SA, saidx_t SAsize, + saint_t c, saidx_t *left); +#endif + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* _DIVSUFSORT_H */ diff --git a/Tools/unix/lzsa/src/libdivsufsort/include/divsufsort.h.cmake b/Tools/unix/lzsa/src/libdivsufsort/include/divsufsort.h.cmake new file mode 100644 index 00000000..bcaba7c6 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/include/divsufsort.h.cmake @@ -0,0 +1,180 @@ +/* + * divsufsort@W64BIT@.h for libdivsufsort@W64BIT@ + * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _DIVSUFSORT@W64BIT@_H +#define _DIVSUFSORT@W64BIT@_H 1 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +@INCFILE@ + +#ifndef DIVSUFSORT_API +# ifdef DIVSUFSORT_BUILD_DLL +# define DIVSUFSORT_API @DIVSUFSORT_EXPORT@ +# else +# define DIVSUFSORT_API @DIVSUFSORT_IMPORT@ +# endif +#endif + +/*- Datatypes -*/ +#ifndef SAUCHAR_T +#define SAUCHAR_T +typedef @SAUCHAR_TYPE@ sauchar_t; +#endif /* SAUCHAR_T */ +#ifndef SAINT_T +#define SAINT_T +typedef @SAINT32_TYPE@ saint_t; +#endif /* SAINT_T */ +#ifndef SAIDX@W64BIT@_T +#define SAIDX@W64BIT@_T +typedef @SAINDEX_TYPE@ saidx@W64BIT@_t; +#endif /* SAIDX@W64BIT@_T */ +#ifndef PRIdSAINT_T +#define PRIdSAINT_T @SAINT_PRId@ +#endif /* PRIdSAINT_T */ +#ifndef PRIdSAIDX@W64BIT@_T +#define PRIdSAIDX@W64BIT@_T @SAINDEX_PRId@ +#endif /* PRIdSAIDX@W64BIT@_T */ + + +/*- Prototypes -*/ + +/** + * Constructs the suffix array of a given string. + * @param T[0..n-1] The input string. + * @param SA[0..n-1] The output array of suffixes. + * @param n The length of the given string. + * @return 0 if no error occurred, -1 or -2 otherwise. + */ +DIVSUFSORT_API +saint_t +divsufsort@W64BIT@(const sauchar_t *T, saidx@W64BIT@_t *SA, saidx@W64BIT@_t n); + +/** + * Constructs the burrows-wheeler transformed string of a given string. + * @param T[0..n-1] The input string. + * @param U[0..n-1] The output string. (can be T) + * @param A[0..n-1] The temporary array. (can be NULL) + * @param n The length of the given string. + * @return The primary index if no error occurred, -1 or -2 otherwise. + */ +DIVSUFSORT_API +saidx@W64BIT@_t +divbwt@W64BIT@(const sauchar_t *T, sauchar_t *U, saidx@W64BIT@_t *A, saidx@W64BIT@_t n); + +/** + * Returns the version of the divsufsort library. + * @return The version number string. + */ +DIVSUFSORT_API +const char * +divsufsort@W64BIT@_version(void); + + +/** + * Constructs the burrows-wheeler transformed string of a given string and suffix array. + * @param T[0..n-1] The input string. + * @param U[0..n-1] The output string. (can be T) + * @param SA[0..n-1] The suffix array. (can be NULL) + * @param n The length of the given string. + * @param idx The output primary index. + * @return 0 if no error occurred, -1 or -2 otherwise. + */ +DIVSUFSORT_API +saint_t +bw_transform@W64BIT@(const sauchar_t *T, sauchar_t *U, + saidx@W64BIT@_t *SA /* can NULL */, + saidx@W64BIT@_t n, saidx@W64BIT@_t *idx); + +/** + * Inverse BW-transforms a given BWTed string. + * @param T[0..n-1] The input string. + * @param U[0..n-1] The output string. (can be T) + * @param A[0..n-1] The temporary array. (can be NULL) + * @param n The length of the given string. + * @param idx The primary index. + * @return 0 if no error occurred, -1 or -2 otherwise. + */ +DIVSUFSORT_API +saint_t +inverse_bw_transform@W64BIT@(const sauchar_t *T, sauchar_t *U, + saidx@W64BIT@_t *A /* can NULL */, + saidx@W64BIT@_t n, saidx@W64BIT@_t idx); + +/** + * Checks the correctness of a given suffix array. + * @param T[0..n-1] The input string. + * @param SA[0..n-1] The input suffix array. + * @param n The length of the given string. + * @param verbose The verbose mode. + * @return 0 if no error occurred. + */ +DIVSUFSORT_API +saint_t +sufcheck@W64BIT@(const sauchar_t *T, const saidx@W64BIT@_t *SA, saidx@W64BIT@_t n, saint_t verbose); + +/** + * Search for the pattern P in the string T. + * @param T[0..Tsize-1] The input string. + * @param Tsize The length of the given string. + * @param P[0..Psize-1] The input pattern string. + * @param Psize The length of the given pattern string. + * @param SA[0..SAsize-1] The input suffix array. + * @param SAsize The length of the given suffix array. + * @param idx The output index. + * @return The count of matches if no error occurred, -1 otherwise. + */ +DIVSUFSORT_API +saidx@W64BIT@_t +sa_search@W64BIT@(const sauchar_t *T, saidx@W64BIT@_t Tsize, + const sauchar_t *P, saidx@W64BIT@_t Psize, + const saidx@W64BIT@_t *SA, saidx@W64BIT@_t SAsize, + saidx@W64BIT@_t *left); + +/** + * Search for the character c in the string T. + * @param T[0..Tsize-1] The input string. + * @param Tsize The length of the given string. + * @param SA[0..SAsize-1] The input suffix array. + * @param SAsize The length of the given suffix array. + * @param c The input character. + * @param idx The output index. + * @return The count of matches if no error occurred, -1 otherwise. + */ +DIVSUFSORT_API +saidx@W64BIT@_t +sa_simplesearch@W64BIT@(const sauchar_t *T, saidx@W64BIT@_t Tsize, + const saidx@W64BIT@_t *SA, saidx@W64BIT@_t SAsize, + saint_t c, saidx@W64BIT@_t *left); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* _DIVSUFSORT@W64BIT@_H */ diff --git a/Tools/unix/lzsa/src/libdivsufsort/include/divsufsort_config.h b/Tools/unix/lzsa/src/libdivsufsort/include/divsufsort_config.h new file mode 100644 index 00000000..4054a8aa --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/include/divsufsort_config.h @@ -0,0 +1,9 @@ +#define HAVE_STRING_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_MEMORY_H 1 +#define HAVE_STDINT_H 1 +#define INLINE inline + +#ifdef _MSC_VER +#pragma warning( disable : 4244 ) +#endif /* _MSC_VER */ diff --git a/Tools/unix/lzsa/src/libdivsufsort/include/divsufsort_private.h b/Tools/unix/lzsa/src/libdivsufsort/include/divsufsort_private.h new file mode 100644 index 00000000..b4d97ad4 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/include/divsufsort_private.h @@ -0,0 +1,205 @@ +/* + * divsufsort_private.h for libdivsufsort + * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _DIVSUFSORT_PRIVATE_H +#define _DIVSUFSORT_PRIVATE_H 1 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "divsufsort_config.h" +#include +#include +#if HAVE_STRING_H +# include +#endif +#if HAVE_STDLIB_H +# include +#endif +#if HAVE_MEMORY_H +# include +#endif +#if HAVE_STDDEF_H +# include +#endif +#if HAVE_STRINGS_H +# include +#endif +#if HAVE_INTTYPES_H +# include +#else +# if HAVE_STDINT_H +# include +# endif +#endif +#if defined(BUILD_DIVSUFSORT64) +# include "divsufsort64.h" +# ifndef SAIDX_T +# define SAIDX_T +# define saidx_t saidx64_t +# endif /* SAIDX_T */ +# ifndef PRIdSAIDX_T +# define PRIdSAIDX_T PRIdSAIDX64_T +# endif /* PRIdSAIDX_T */ +# define divsufsort divsufsort64 +# define divbwt divbwt64 +# define divsufsort_version divsufsort64_version +# define bw_transform bw_transform64 +# define inverse_bw_transform inverse_bw_transform64 +# define sufcheck sufcheck64 +# define sa_search sa_search64 +# define sa_simplesearch sa_simplesearch64 +# define sssort sssort64 +# define trsort trsort64 +#else +# include "divsufsort.h" +#endif + + +/*- Constants -*/ +#if !defined(UINT8_MAX) +# define UINT8_MAX (255) +#endif /* UINT8_MAX */ +#if defined(ALPHABET_SIZE) && (ALPHABET_SIZE < 1) +# undef ALPHABET_SIZE +#endif +#if !defined(ALPHABET_SIZE) +# define ALPHABET_SIZE (UINT8_MAX + 1) +#endif +/* for divsufsort.c */ +#define BUCKET_A_SIZE (ALPHABET_SIZE) +#define BUCKET_B_SIZE (ALPHABET_SIZE * ALPHABET_SIZE) +/* for sssort.c */ +#if defined(SS_INSERTIONSORT_THRESHOLD) +# if SS_INSERTIONSORT_THRESHOLD < 1 +# undef SS_INSERTIONSORT_THRESHOLD +# define SS_INSERTIONSORT_THRESHOLD (1) +# endif +#else +# define SS_INSERTIONSORT_THRESHOLD (8) +#endif +#if defined(SS_BLOCKSIZE) +# if SS_BLOCKSIZE < 0 +# undef SS_BLOCKSIZE +# define SS_BLOCKSIZE (0) +# elif 32768 <= SS_BLOCKSIZE +# undef SS_BLOCKSIZE +# define SS_BLOCKSIZE (32767) +# endif +#else +# define SS_BLOCKSIZE (1024) +#endif +/* minstacksize = log(SS_BLOCKSIZE) / log(3) * 2 */ +#if SS_BLOCKSIZE == 0 +# if defined(BUILD_DIVSUFSORT64) +# define SS_MISORT_STACKSIZE (96) +# else +# define SS_MISORT_STACKSIZE (64) +# endif +#elif SS_BLOCKSIZE <= 4096 +# define SS_MISORT_STACKSIZE (16) +#else +# define SS_MISORT_STACKSIZE (24) +#endif +#if defined(BUILD_DIVSUFSORT64) +# define SS_SMERGE_STACKSIZE (64) +#else +# define SS_SMERGE_STACKSIZE (32) +#endif +/* for trsort.c */ +#define TR_INSERTIONSORT_THRESHOLD (8) +#if defined(BUILD_DIVSUFSORT64) +# define TR_STACKSIZE (96) +#else +# define TR_STACKSIZE (64) +#endif + + +/*- Macros -*/ +#ifndef SWAP +# define SWAP(_a, _b) do { t = (_a); (_a) = (_b); (_b) = t; } while(0) +#endif /* SWAP */ +#ifndef MIN +# define MIN(_a, _b) (((_a) < (_b)) ? (_a) : (_b)) +#endif /* MIN */ +#ifndef MAX +# define MAX(_a, _b) (((_a) > (_b)) ? (_a) : (_b)) +#endif /* MAX */ +#define STACK_PUSH(_a, _b, _c, _d)\ + do {\ + assert(ssize < STACK_SIZE);\ + stack[ssize].a = (_a), stack[ssize].b = (_b),\ + stack[ssize].c = (_c), stack[ssize++].d = (_d);\ + } while(0) +#define STACK_PUSH5(_a, _b, _c, _d, _e)\ + do {\ + assert(ssize < STACK_SIZE);\ + stack[ssize].a = (_a), stack[ssize].b = (_b),\ + stack[ssize].c = (_c), stack[ssize].d = (_d), stack[ssize++].e = (_e);\ + } while(0) +#define STACK_POP(_a, _b, _c, _d)\ + do {\ + assert(0 <= ssize);\ + if(ssize == 0) { return; }\ + (_a) = stack[--ssize].a, (_b) = stack[ssize].b,\ + (_c) = stack[ssize].c, (_d) = stack[ssize].d;\ + } while(0) +#define STACK_POP5(_a, _b, _c, _d, _e)\ + do {\ + assert(0 <= ssize);\ + if(ssize == 0) { return; }\ + (_a) = stack[--ssize].a, (_b) = stack[ssize].b,\ + (_c) = stack[ssize].c, (_d) = stack[ssize].d, (_e) = stack[ssize].e;\ + } while(0) +/* for divsufsort.c */ +#define BUCKET_A(_c0) bucket_A[(_c0)] +#if ALPHABET_SIZE == 256 +#define BUCKET_B(_c0, _c1) (bucket_B[((_c1) << 8) | (_c0)]) +#define BUCKET_BSTAR(_c0, _c1) (bucket_B[((_c0) << 8) | (_c1)]) +#else +#define BUCKET_B(_c0, _c1) (bucket_B[(_c1) * ALPHABET_SIZE + (_c0)]) +#define BUCKET_BSTAR(_c0, _c1) (bucket_B[(_c0) * ALPHABET_SIZE + (_c1)]) +#endif + + +/*- Private Prototypes -*/ +/* sssort.c */ +void +sssort(const sauchar_t *Td, const saidx_t *PA, + saidx_t *first, saidx_t *last, + saidx_t *buf, saidx_t bufsize, + saidx_t depth, saidx_t n, saint_t lastsuffix); +/* trsort.c */ +void +trsort(saidx_t *ISA, saidx_t *SA, saidx_t n, saidx_t depth); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* _DIVSUFSORT_PRIVATE_H */ diff --git a/Tools/unix/lzsa/src/libdivsufsort/include/lfs.h.cmake b/Tools/unix/lzsa/src/libdivsufsort/include/lfs.h.cmake new file mode 100644 index 00000000..d5b84a84 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/include/lfs.h.cmake @@ -0,0 +1,56 @@ +/* + * lfs.h for libdivsufsort + * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _LFS_H +#define _LFS_H 1 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifndef __STRICT_ANSI__ +# define LFS_OFF_T @LFS_OFF_T@ +# define LFS_FOPEN @LFS_FOPEN@ +# define LFS_FTELL @LFS_FTELL@ +# define LFS_FSEEK @LFS_FSEEK@ +# define LFS_PRId @LFS_PRID@ +#else +# define LFS_OFF_T long +# define LFS_FOPEN fopen +# define LFS_FTELL ftell +# define LFS_FSEEK fseek +# define LFS_PRId "ld" +#endif +#ifndef PRIdOFF_T +# define PRIdOFF_T LFS_PRId +#endif + + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* _LFS_H */ diff --git a/Tools/unix/lzsa/src/libdivsufsort/lib/CMakeLists.txt b/Tools/unix/lzsa/src/libdivsufsort/lib/CMakeLists.txt new file mode 100644 index 00000000..abc90e61 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/lib/CMakeLists.txt @@ -0,0 +1,31 @@ +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../include" + "${CMAKE_CURRENT_BINARY_DIR}/../include") + +set(divsufsort_SRCS divsufsort.c sssort.c trsort.c utils.c) + +## libdivsufsort ## +add_library(divsufsort ${divsufsort_SRCS}) +install(TARGETS divsufsort + RUNTIME DESTINATION ${CMAKE_INSTALL_RUNTIMEDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +set_target_properties(divsufsort PROPERTIES + VERSION "${LIBRARY_VERSION}" + SOVERSION "${LIBRARY_SOVERSION}" + DEFINE_SYMBOL DIVSUFSORT_BUILD_DLL + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../examples") + +## libdivsufsort64 ## +if(BUILD_DIVSUFSORT64) + add_library(divsufsort64 ${divsufsort_SRCS}) + install(TARGETS divsufsort64 + RUNTIME DESTINATION ${CMAKE_INSTALL_RUNTIMEDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + set_target_properties(divsufsort64 PROPERTIES + VERSION "${LIBRARY_VERSION}" + SOVERSION "${LIBRARY_SOVERSION}" + DEFINE_SYMBOL DIVSUFSORT_BUILD_DLL + COMPILE_FLAGS "-DBUILD_DIVSUFSORT64" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../examples") +endif(BUILD_DIVSUFSORT64) diff --git a/Tools/unix/lzsa/src/libdivsufsort/lib/divsufsort.c b/Tools/unix/lzsa/src/libdivsufsort/lib/divsufsort.c new file mode 100644 index 00000000..50631ace --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/lib/divsufsort.c @@ -0,0 +1,431 @@ +/* + * divsufsort.c for libdivsufsort + * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "divsufsort_private.h" +#ifdef _OPENMP +# include +#endif + + +/*- Private Functions -*/ + +/* Sorts suffixes of type B*. */ +static +saidx_t +sort_typeBstar(const sauchar_t *T, saidx_t *SA, + saidx_t *bucket_A, saidx_t *bucket_B, + saidx_t n) { + saidx_t *PAb, *ISAb, *buf; +#ifdef _OPENMP + saidx_t *curbuf; + saidx_t l; +#endif + saidx_t i, j, k, t, m, bufsize; + saint_t c0, c1; +#ifdef _OPENMP + saint_t d0, d1; + int tmp; +#endif + + /* Initialize bucket arrays. */ + for(i = 0; i < BUCKET_A_SIZE; ++i) { bucket_A[i] = 0; } + for(i = 0; i < BUCKET_B_SIZE; ++i) { bucket_B[i] = 0; } + + /* Count the number of occurrences of the first one or two characters of each + type A, B and B* suffix. Moreover, store the beginning position of all + type B* suffixes into the array SA. */ + for(i = n - 1, m = n, c0 = T[n - 1]; 0 <= i;) { + /* type A suffix. */ + do { ++BUCKET_A(c1 = c0); } while((0 <= --i) && ((c0 = T[i]) >= c1)); + if(0 <= i) { + /* type B* suffix. */ + ++BUCKET_BSTAR(c0, c1); + SA[--m] = i; + /* type B suffix. */ + for(--i, c1 = c0; (0 <= i) && ((c0 = T[i]) <= c1); --i, c1 = c0) { + ++BUCKET_B(c0, c1); + } + } + } + m = n - m; +/* +note: + A type B* suffix is lexicographically smaller than a type B suffix that + begins with the same first two characters. +*/ + + /* Calculate the index of start/end point of each bucket. */ + for(c0 = 0, i = 0, j = 0; c0 < ALPHABET_SIZE; ++c0) { + t = i + BUCKET_A(c0); + BUCKET_A(c0) = i + j; /* start point */ + i = t + BUCKET_B(c0, c0); + for(c1 = c0 + 1; c1 < ALPHABET_SIZE; ++c1) { + j += BUCKET_BSTAR(c0, c1); + BUCKET_BSTAR(c0, c1) = j; /* end point */ + i += BUCKET_B(c0, c1); + } + } + + if(0 < m) { + /* Sort the type B* suffixes by their first two characters. */ + PAb = SA + n - m; ISAb = SA + m; + for(i = m - 2; 0 <= i; --i) { + t = PAb[i], c0 = T[t], c1 = T[t + 1]; + SA[--BUCKET_BSTAR(c0, c1)] = i; + } + t = PAb[m - 1], c0 = T[t], c1 = T[t + 1]; + SA[--BUCKET_BSTAR(c0, c1)] = m - 1; + + /* Sort the type B* substrings using sssort. */ +#ifdef _OPENMP + tmp = omp_get_max_threads(); + buf = SA + m, bufsize = (n - (2 * m)) / tmp; + c0 = ALPHABET_SIZE - 2, c1 = ALPHABET_SIZE - 1, j = m; +#pragma omp parallel default(shared) private(curbuf, k, l, d0, d1, tmp) + { + tmp = omp_get_thread_num(); + curbuf = buf + tmp * bufsize; + k = 0; + for(;;) { + #pragma omp critical(sssort_lock) + { + if(0 < (l = j)) { + d0 = c0, d1 = c1; + do { + k = BUCKET_BSTAR(d0, d1); + if(--d1 <= d0) { + d1 = ALPHABET_SIZE - 1; + if(--d0 < 0) { break; } + } + } while(((l - k) <= 1) && (0 < (l = k))); + c0 = d0, c1 = d1, j = k; + } + } + if(l == 0) { break; } + sssort(T, PAb, SA + k, SA + l, + curbuf, bufsize, 2, n, *(SA + k) == (m - 1)); + } + } +#else + buf = SA + m, bufsize = n - (2 * m); + for(c0 = ALPHABET_SIZE - 2, j = m; 0 < j; --c0) { + for(c1 = ALPHABET_SIZE - 1; c0 < c1; j = i, --c1) { + i = BUCKET_BSTAR(c0, c1); + if(1 < (j - i)) { + sssort(T, PAb, SA + i, SA + j, + buf, bufsize, 2, n, *(SA + i) == (m - 1)); + } + } + } +#endif + + /* Compute ranks of type B* substrings. */ + for(i = m - 1; 0 <= i; --i) { + if(0 <= SA[i]) { + j = i; + do { ISAb[SA[i]] = i; } while((0 <= --i) && (0 <= SA[i])); + SA[i + 1] = i - j; + if(i <= 0) { break; } + } + j = i; + do { ISAb[SA[i] = ~SA[i]] = j; } while(SA[--i] < 0); + ISAb[SA[i]] = j; + } + + /* Construct the inverse suffix array of type B* suffixes using trsort. */ + trsort(ISAb, SA, m, 1); + + /* Set the sorted order of tyoe B* suffixes. */ + for(i = n - 1, j = m, c0 = T[n - 1]; 0 <= i;) { + for(--i, c1 = c0; (0 <= i) && ((c0 = T[i]) >= c1); --i, c1 = c0) { } + if(0 <= i) { + t = i; + for(--i, c1 = c0; (0 <= i) && ((c0 = T[i]) <= c1); --i, c1 = c0) { } + SA[ISAb[--j]] = ((t == 0) || (1 < (t - i))) ? t : ~t; + } + } + + /* Calculate the index of start/end point of each bucket. */ + BUCKET_B(ALPHABET_SIZE - 1, ALPHABET_SIZE - 1) = n; /* end point */ + for(c0 = ALPHABET_SIZE - 2, k = m - 1; 0 <= c0; --c0) { + i = BUCKET_A(c0 + 1) - 1; + for(c1 = ALPHABET_SIZE - 1; c0 < c1; --c1) { + t = i - BUCKET_B(c0, c1); + BUCKET_B(c0, c1) = i; /* end point */ + + /* Move all type B* suffixes to the correct position. */ + for(i = t, j = BUCKET_BSTAR(c0, c1); + j <= k; + --i, --k) { SA[i] = SA[k]; } + } + BUCKET_BSTAR(c0, c0 + 1) = i - BUCKET_B(c0, c0) + 1; /* start point */ + BUCKET_B(c0, c0) = i; /* end point */ + } + } + + return m; +} + +/* Constructs the suffix array by using the sorted order of type B* suffixes. */ +static +void +construct_SA(const sauchar_t *T, saidx_t *SA, + saidx_t *bucket_A, saidx_t *bucket_B, + saidx_t n, saidx_t m) { + saidx_t *i, *j, *k; + saidx_t s; + saint_t c0, c1, c2; + + if(0 < m) { + /* Construct the sorted order of type B suffixes by using + the sorted order of type B* suffixes. */ + for(c1 = ALPHABET_SIZE - 2; 0 <= c1; --c1) { + /* Scan the suffix array from right to left. */ + for(i = SA + BUCKET_BSTAR(c1, c1 + 1), + j = SA + BUCKET_A(c1 + 1) - 1, k = NULL, c2 = -1; + i <= j; + --j) { + if(0 < (s = *j)) { + assert(T[s] == c1); + assert(((s + 1) < n) && (T[s] <= T[s + 1])); + assert(T[s - 1] <= T[s]); + *j = ~s; + c0 = T[--s]; + if((0 < s) && (T[s - 1] > c0)) { s = ~s; } + if(c0 != c2) { + if(0 <= c2) { BUCKET_B(c2, c1) = k - SA; } + k = SA + BUCKET_B(c2 = c0, c1); + } + assert(k < j); + *k-- = s; + } else { + assert(((s == 0) && (T[s] == c1)) || (s < 0)); + *j = ~s; + } + } + } + } + + /* Construct the suffix array by using + the sorted order of type B suffixes. */ + k = SA + BUCKET_A(c2 = T[n - 1]); + *k++ = (T[n - 2] < c2) ? ~(n - 1) : (n - 1); + /* Scan the suffix array from left to right. */ + for(i = SA, j = SA + n; i < j; ++i) { + if(0 < (s = *i)) { + assert(T[s - 1] >= T[s]); + c0 = T[--s]; + if((s == 0) || (T[s - 1] < c0)) { s = ~s; } + if(c0 != c2) { + BUCKET_A(c2) = k - SA; + k = SA + BUCKET_A(c2 = c0); + } + assert(i < k); + *k++ = s; + } else { + assert(s < 0); + *i = ~s; + } + } +} + +#if 0 +/* Constructs the burrows-wheeler transformed string directly + by using the sorted order of type B* suffixes. */ +static +saidx_t +construct_BWT(const sauchar_t *T, saidx_t *SA, + saidx_t *bucket_A, saidx_t *bucket_B, + saidx_t n, saidx_t m) { + saidx_t *i, *j, *k, *orig; + saidx_t s; + saint_t c0, c1, c2; + + if(0 < m) { + /* Construct the sorted order of type B suffixes by using + the sorted order of type B* suffixes. */ + for(c1 = ALPHABET_SIZE - 2; 0 <= c1; --c1) { + /* Scan the suffix array from right to left. */ + for(i = SA + BUCKET_BSTAR(c1, c1 + 1), + j = SA + BUCKET_A(c1 + 1) - 1, k = NULL, c2 = -1; + i <= j; + --j) { + if(0 < (s = *j)) { + assert(T[s] == c1); + assert(((s + 1) < n) && (T[s] <= T[s + 1])); + assert(T[s - 1] <= T[s]); + c0 = T[--s]; + *j = ~((saidx_t)c0); + if((0 < s) && (T[s - 1] > c0)) { s = ~s; } + if(c0 != c2) { + if(0 <= c2) { BUCKET_B(c2, c1) = k - SA; } + k = SA + BUCKET_B(c2 = c0, c1); + } + assert(k < j); + *k-- = s; + } else if(s != 0) { + *j = ~s; +#ifndef NDEBUG + } else { + assert(T[s] == c1); +#endif + } + } + } + } + + /* Construct the BWTed string by using + the sorted order of type B suffixes. */ + k = SA + BUCKET_A(c2 = T[n - 1]); + *k++ = (T[n - 2] < c2) ? ~((saidx_t)T[n - 2]) : (n - 1); + /* Scan the suffix array from left to right. */ + for(i = SA, j = SA + n, orig = SA; i < j; ++i) { + if(0 < (s = *i)) { + assert(T[s - 1] >= T[s]); + c0 = T[--s]; + *i = c0; + if((0 < s) && (T[s - 1] < c0)) { s = ~((saidx_t)T[s - 1]); } + if(c0 != c2) { + BUCKET_A(c2) = k - SA; + k = SA + BUCKET_A(c2 = c0); + } + assert(i < k); + *k++ = s; + } else if(s != 0) { + *i = ~s; + } else { + orig = i; + } + } + + return orig - SA; +} +#endif + +/*---------------------------------------------------------------------------*/ + +/** + * Initialize suffix array context + * + * @return 0 for success, or non-zero in case of an error + */ +int divsufsort_init(divsufsort_ctx_t *ctx) { + ctx->bucket_A = (saidx_t *)malloc(BUCKET_A_SIZE * sizeof(saidx_t)); + ctx->bucket_B = NULL; + + if (ctx->bucket_A) { + ctx->bucket_B = (saidx_t *)malloc(BUCKET_B_SIZE * sizeof(saidx_t)); + + if (ctx->bucket_B) + return 0; + } + + divsufsort_destroy(ctx); + return -1; +} + +/** + * Destroy suffix array context + * + * @param ctx suffix array context to destroy + */ +void divsufsort_destroy(divsufsort_ctx_t *ctx) { + if (ctx->bucket_B) { + free(ctx->bucket_B); + ctx->bucket_B = NULL; + } + + if (ctx->bucket_A) { + free(ctx->bucket_A); + ctx->bucket_A = NULL; + } +} + +/*- Function -*/ + +saint_t +divsufsort_build_array(divsufsort_ctx_t *ctx, const sauchar_t *T, saidx_t *SA, saidx_t n) { + saidx_t m; + saint_t err = 0; + + /* Check arguments. */ + if((T == NULL) || (SA == NULL) || (n < 0)) { return -1; } + else if(n == 0) { return 0; } + else if(n == 1) { SA[0] = 0; return 0; } + else if(n == 2) { m = (T[0] < T[1]); SA[m ^ 1] = 0, SA[m] = 1; return 0; } + + /* Suffixsort. */ + if((ctx->bucket_A != NULL) && (ctx->bucket_B != NULL)) { + m = sort_typeBstar(T, SA, ctx->bucket_A, ctx->bucket_B, n); + construct_SA(T, SA, ctx->bucket_A, ctx->bucket_B, n, m); + } else { + err = -2; + } + + return err; +} + +#if 0 +saidx_t +divbwt(const sauchar_t *T, sauchar_t *U, saidx_t *A, saidx_t n) { + saidx_t *B; + saidx_t *bucket_A, *bucket_B; + saidx_t m, pidx, i; + + /* Check arguments. */ + if((T == NULL) || (U == NULL) || (n < 0)) { return -1; } + else if(n <= 1) { if(n == 1) { U[0] = T[0]; } return n; } + + if((B = A) == NULL) { B = (saidx_t *)malloc((size_t)(n + 1) * sizeof(saidx_t)); } + bucket_A = (saidx_t *)malloc(BUCKET_A_SIZE * sizeof(saidx_t)); + bucket_B = (saidx_t *)malloc(BUCKET_B_SIZE * sizeof(saidx_t)); + + /* Burrows-Wheeler Transform. */ + if((B != NULL) && (bucket_A != NULL) && (bucket_B != NULL)) { + m = sort_typeBstar(T, B, bucket_A, bucket_B, n); + pidx = construct_BWT(T, B, bucket_A, bucket_B, n, m); + + /* Copy to output string. */ + U[0] = T[n - 1]; + for(i = 0; i < pidx; ++i) { U[i + 1] = (sauchar_t)B[i]; } + for(i += 1; i < n; ++i) { U[i] = (sauchar_t)B[i]; } + pidx += 1; + } else { + pidx = -2; + } + + free(bucket_B); + free(bucket_A); + if(A == NULL) { free(B); } + + return pidx; +} + +const char * +divsufsort_version(void) { + return PROJECT_VERSION_FULL; +} +#endif diff --git a/Tools/unix/lzsa/src/libdivsufsort/lib/divsufsort_utils.c b/Tools/unix/lzsa/src/libdivsufsort/lib/divsufsort_utils.c new file mode 100644 index 00000000..f7cbc0d5 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/lib/divsufsort_utils.c @@ -0,0 +1,383 @@ +/* + * utils.c for libdivsufsort + * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "divsufsort_private.h" + + +/*- Private Function -*/ + +#if 0 +/* Binary search for inverse bwt. */ +static +saidx_t +binarysearch_lower(const saidx_t *A, saidx_t size, saidx_t value) { + saidx_t half, i; + for(i = 0, half = size >> 1; + 0 < size; + size = half, half >>= 1) { + if(A[i + half] < value) { + i += half + 1; + half -= (size & 1) ^ 1; + } + } + return i; +} + + +/*- Functions -*/ + +/* Burrows-Wheeler transform. */ +saint_t +bw_transform(const sauchar_t *T, sauchar_t *U, saidx_t *SA, + saidx_t n, saidx_t *idx) { + saidx_t *A, i, j, p, t; + saint_t c; + + /* Check arguments. */ + if((T == NULL) || (U == NULL) || (n < 0) || (idx == NULL)) { return -1; } + if(n <= 1) { + if(n == 1) { U[0] = T[0]; } + *idx = n; + return 0; + } + + if((A = SA) == NULL) { + i = divbwt(T, U, NULL, n); + if(0 <= i) { *idx = i; i = 0; } + return (saint_t)i; + } + + /* BW transform. */ + if(T == U) { + t = n; + for(i = 0, j = 0; i < n; ++i) { + p = t - 1; + t = A[i]; + if(0 <= p) { + c = T[j]; + U[j] = (j <= p) ? T[p] : (sauchar_t)A[p]; + A[j] = c; + j++; + } else { + *idx = i; + } + } + p = t - 1; + if(0 <= p) { + c = T[j]; + U[j] = (j <= p) ? T[p] : (sauchar_t)A[p]; + A[j] = c; + } else { + *idx = i; + } + } else { + U[0] = T[n - 1]; + for(i = 0; A[i] != 0; ++i) { U[i + 1] = T[A[i] - 1]; } + *idx = i + 1; + for(++i; i < n; ++i) { U[i] = T[A[i] - 1]; } + } + + if(SA == NULL) { + /* Deallocate memory. */ + free(A); + } + + return 0; +} + +/* Inverse Burrows-Wheeler transform. */ +saint_t +inverse_bw_transform(const sauchar_t *T, sauchar_t *U, saidx_t *A, + saidx_t n, saidx_t idx) { + saidx_t C[ALPHABET_SIZE]; + sauchar_t D[ALPHABET_SIZE]; + saidx_t *B; + saidx_t i, p; + saint_t c, d; + + /* Check arguments. */ + if((T == NULL) || (U == NULL) || (n < 0) || (idx < 0) || + (n < idx) || ((0 < n) && (idx == 0))) { + return -1; + } + if(n <= 1) { return 0; } + + if((B = A) == NULL) { + /* Allocate n*sizeof(saidx_t) bytes of memory. */ + if((B = (saidx_t *)malloc((size_t)n * sizeof(saidx_t))) == NULL) { return -2; } + } + + /* Inverse BW transform. */ + for(c = 0; c < ALPHABET_SIZE; ++c) { C[c] = 0; } + for(i = 0; i < n; ++i) { ++C[T[i]]; } + for(c = 0, d = 0, i = 0; c < ALPHABET_SIZE; ++c) { + p = C[c]; + if(0 < p) { + C[c] = i; + D[d++] = (sauchar_t)c; + i += p; + } + } + for(i = 0; i < idx; ++i) { B[C[T[i]]++] = i; } + for( ; i < n; ++i) { B[C[T[i]]++] = i + 1; } + for(c = 0; c < d; ++c) { C[c] = C[D[c]]; } + for(i = 0, p = idx; i < n; ++i) { + U[i] = D[binarysearch_lower(C, d, p)]; + p = B[p - 1]; + } + + if(A == NULL) { + /* Deallocate memory. */ + free(B); + } + + return 0; +} + +/* Checks the suffix array SA of the string T. */ +saint_t +sufcheck(const sauchar_t *T, const saidx_t *SA, + saidx_t n, saint_t verbose) { + saidx_t C[ALPHABET_SIZE]; + saidx_t i, p, q, t; + saint_t c; + + if(verbose) { fprintf(stderr, "sufcheck: "); } + + /* Check arguments. */ + if((T == NULL) || (SA == NULL) || (n < 0)) { + if(verbose) { fprintf(stderr, "Invalid arguments.\n"); } + return -1; + } + if(n == 0) { + if(verbose) { fprintf(stderr, "Done.\n"); } + return 0; + } + + /* check range: [0..n-1] */ + for(i = 0; i < n; ++i) { + if((SA[i] < 0) || (n <= SA[i])) { + if(verbose) { + fprintf(stderr, "Out of the range [0,%" PRIdSAIDX_T "].\n" + " SA[%" PRIdSAIDX_T "]=%" PRIdSAIDX_T "\n", + n - 1, i, SA[i]); + } + return -2; + } + } + + /* check first characters. */ + for(i = 1; i < n; ++i) { + if(T[SA[i - 1]] > T[SA[i]]) { + if(verbose) { + fprintf(stderr, "Suffixes in wrong order.\n" + " T[SA[%" PRIdSAIDX_T "]=%" PRIdSAIDX_T "]=%d" + " > T[SA[%" PRIdSAIDX_T "]=%" PRIdSAIDX_T "]=%d\n", + i - 1, SA[i - 1], T[SA[i - 1]], i, SA[i], T[SA[i]]); + } + return -3; + } + } + + /* check suffixes. */ + for(i = 0; i < ALPHABET_SIZE; ++i) { C[i] = 0; } + for(i = 0; i < n; ++i) { ++C[T[i]]; } + for(i = 0, p = 0; i < ALPHABET_SIZE; ++i) { + t = C[i]; + C[i] = p; + p += t; + } + + q = C[T[n - 1]]; + C[T[n - 1]] += 1; + for(i = 0; i < n; ++i) { + p = SA[i]; + if(0 < p) { + c = T[--p]; + t = C[c]; + } else { + c = T[p = n - 1]; + t = q; + } + if((t < 0) || (p != SA[t])) { + if(verbose) { + fprintf(stderr, "Suffix in wrong position.\n" + " SA[%" PRIdSAIDX_T "]=%" PRIdSAIDX_T " or\n" + " SA[%" PRIdSAIDX_T "]=%" PRIdSAIDX_T "\n", + t, (0 <= t) ? SA[t] : -1, i, SA[i]); + } + return -4; + } + if(t != q) { + ++C[c]; + if((n <= C[c]) || (T[SA[C[c]]] != c)) { C[c] = -1; } + } + } + + if(1 <= verbose) { fprintf(stderr, "Done.\n"); } + return 0; +} + + +static +int +_compare(const sauchar_t *T, saidx_t Tsize, + const sauchar_t *P, saidx_t Psize, + saidx_t suf, saidx_t *match) { + saidx_t i, j; + saint_t r; + for(i = suf + *match, j = *match, r = 0; + (i < Tsize) && (j < Psize) && ((r = T[i] - P[j]) == 0); ++i, ++j) { } + *match = j; + return (r == 0) ? -(j != Psize) : r; +} + +/* Search for the pattern P in the string T. */ +saidx_t +sa_search(const sauchar_t *T, saidx_t Tsize, + const sauchar_t *P, saidx_t Psize, + const saidx_t *SA, saidx_t SAsize, + saidx_t *idx) { + saidx_t size, lsize, rsize, half; + saidx_t match, lmatch, rmatch; + saidx_t llmatch, lrmatch, rlmatch, rrmatch; + saidx_t i, j, k; + saint_t r; + + if(idx != NULL) { *idx = -1; } + if((T == NULL) || (P == NULL) || (SA == NULL) || + (Tsize < 0) || (Psize < 0) || (SAsize < 0)) { return -1; } + if((Tsize == 0) || (SAsize == 0)) { return 0; } + if(Psize == 0) { if(idx != NULL) { *idx = 0; } return SAsize; } + + for(i = j = k = 0, lmatch = rmatch = 0, size = SAsize, half = size >> 1; + 0 < size; + size = half, half >>= 1) { + match = MIN(lmatch, rmatch); + r = _compare(T, Tsize, P, Psize, SA[i + half], &match); + if(r < 0) { + i += half + 1; + half -= (size & 1) ^ 1; + lmatch = match; + } else if(r > 0) { + rmatch = match; + } else { + lsize = half, j = i, rsize = size - half - 1, k = i + half + 1; + + /* left part */ + for(llmatch = lmatch, lrmatch = match, half = lsize >> 1; + 0 < lsize; + lsize = half, half >>= 1) { + lmatch = MIN(llmatch, lrmatch); + r = _compare(T, Tsize, P, Psize, SA[j + half], &lmatch); + if(r < 0) { + j += half + 1; + half -= (lsize & 1) ^ 1; + llmatch = lmatch; + } else { + lrmatch = lmatch; + } + } + + /* right part */ + for(rlmatch = match, rrmatch = rmatch, half = rsize >> 1; + 0 < rsize; + rsize = half, half >>= 1) { + rmatch = MIN(rlmatch, rrmatch); + r = _compare(T, Tsize, P, Psize, SA[k + half], &rmatch); + if(r <= 0) { + k += half + 1; + half -= (rsize & 1) ^ 1; + rlmatch = rmatch; + } else { + rrmatch = rmatch; + } + } + + break; + } + } + + if(idx != NULL) { *idx = (0 < (k - j)) ? j : i; } + return k - j; +} + +/* Search for the character c in the string T. */ +saidx_t +sa_simplesearch(const sauchar_t *T, saidx_t Tsize, + const saidx_t *SA, saidx_t SAsize, + saint_t c, saidx_t *idx) { + saidx_t size, lsize, rsize, half; + saidx_t i, j, k, p; + saint_t r; + + if(idx != NULL) { *idx = -1; } + if((T == NULL) || (SA == NULL) || (Tsize < 0) || (SAsize < 0)) { return -1; } + if((Tsize == 0) || (SAsize == 0)) { return 0; } + + for(i = j = k = 0, size = SAsize, half = size >> 1; + 0 < size; + size = half, half >>= 1) { + p = SA[i + half]; + r = (p < Tsize) ? T[p] - c : -1; + if(r < 0) { + i += half + 1; + half -= (size & 1) ^ 1; + } else if(r == 0) { + lsize = half, j = i, rsize = size - half - 1, k = i + half + 1; + + /* left part */ + for(half = lsize >> 1; + 0 < lsize; + lsize = half, half >>= 1) { + p = SA[j + half]; + r = (p < Tsize) ? T[p] - c : -1; + if(r < 0) { + j += half + 1; + half -= (lsize & 1) ^ 1; + } + } + + /* right part */ + for(half = rsize >> 1; + 0 < rsize; + rsize = half, half >>= 1) { + p = SA[k + half]; + r = (p < Tsize) ? T[p] - c : -1; + if(r <= 0) { + k += half + 1; + half -= (rsize & 1) ^ 1; + } + } + + break; + } + } + + if(idx != NULL) { *idx = (0 < (k - j)) ? j : i; } + return k - j; +} +#endif diff --git a/Tools/unix/lzsa/src/libdivsufsort/lib/sssort.c b/Tools/unix/lzsa/src/libdivsufsort/lib/sssort.c new file mode 100644 index 00000000..4a18fd2a --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/lib/sssort.c @@ -0,0 +1,815 @@ +/* + * sssort.c for libdivsufsort + * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "divsufsort_private.h" + + +/*- Private Functions -*/ + +static const saint_t lg_table[256]= { + -1,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 +}; + +#if (SS_BLOCKSIZE == 0) || (SS_INSERTIONSORT_THRESHOLD < SS_BLOCKSIZE) + +static INLINE +saint_t +ss_ilg(saidx_t n) { +#if SS_BLOCKSIZE == 0 +# if defined(BUILD_DIVSUFSORT64) + return (n >> 32) ? + ((n >> 48) ? + ((n >> 56) ? + 56 + lg_table[(n >> 56) & 0xff] : + 48 + lg_table[(n >> 48) & 0xff]) : + ((n >> 40) ? + 40 + lg_table[(n >> 40) & 0xff] : + 32 + lg_table[(n >> 32) & 0xff])) : + ((n & 0xffff0000) ? + ((n & 0xff000000) ? + 24 + lg_table[(n >> 24) & 0xff] : + 16 + lg_table[(n >> 16) & 0xff]) : + ((n & 0x0000ff00) ? + 8 + lg_table[(n >> 8) & 0xff] : + 0 + lg_table[(n >> 0) & 0xff])); +# else + return (n & 0xffff0000) ? + ((n & 0xff000000) ? + 24 + lg_table[(n >> 24) & 0xff] : + 16 + lg_table[(n >> 16) & 0xff]) : + ((n & 0x0000ff00) ? + 8 + lg_table[(n >> 8) & 0xff] : + 0 + lg_table[(n >> 0) & 0xff]); +# endif +#elif SS_BLOCKSIZE < 256 + return lg_table[n]; +#else + return (n & 0xff00) ? + 8 + lg_table[(n >> 8) & 0xff] : + 0 + lg_table[(n >> 0) & 0xff]; +#endif +} + +#endif /* (SS_BLOCKSIZE == 0) || (SS_INSERTIONSORT_THRESHOLD < SS_BLOCKSIZE) */ + +#if SS_BLOCKSIZE != 0 + +static const saint_t sqq_table[256] = { + 0, 16, 22, 27, 32, 35, 39, 42, 45, 48, 50, 53, 55, 57, 59, 61, + 64, 65, 67, 69, 71, 73, 75, 76, 78, 80, 81, 83, 84, 86, 87, 89, + 90, 91, 93, 94, 96, 97, 98, 99, 101, 102, 103, 104, 106, 107, 108, 109, +110, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, +128, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, +143, 144, 144, 145, 146, 147, 148, 149, 150, 150, 151, 152, 153, 154, 155, 155, +156, 157, 158, 159, 160, 160, 161, 162, 163, 163, 164, 165, 166, 167, 167, 168, +169, 170, 170, 171, 172, 173, 173, 174, 175, 176, 176, 177, 178, 178, 179, 180, +181, 181, 182, 183, 183, 184, 185, 185, 186, 187, 187, 188, 189, 189, 190, 191, +192, 192, 193, 193, 194, 195, 195, 196, 197, 197, 198, 199, 199, 200, 201, 201, +202, 203, 203, 204, 204, 205, 206, 206, 207, 208, 208, 209, 209, 210, 211, 211, +212, 212, 213, 214, 214, 215, 215, 216, 217, 217, 218, 218, 219, 219, 220, 221, +221, 222, 222, 223, 224, 224, 225, 225, 226, 226, 227, 227, 228, 229, 229, 230, +230, 231, 231, 232, 232, 233, 234, 234, 235, 235, 236, 236, 237, 237, 238, 238, +239, 240, 240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 247, +247, 248, 248, 249, 249, 250, 250, 251, 251, 252, 252, 253, 253, 254, 254, 255 +}; + +static INLINE +saidx_t +ss_isqrt(saidx_t x) { + saidx_t y, e; + + if(x >= (SS_BLOCKSIZE * SS_BLOCKSIZE)) { return SS_BLOCKSIZE; } + e = (x & 0xffff0000) ? + ((x & 0xff000000) ? + 24 + lg_table[(x >> 24) & 0xff] : + 16 + lg_table[(x >> 16) & 0xff]) : + ((x & 0x0000ff00) ? + 8 + lg_table[(x >> 8) & 0xff] : + 0 + lg_table[(x >> 0) & 0xff]); + + if(e >= 16) { + y = sqq_table[x >> ((e - 6) - (e & 1))] << ((e >> 1) - 7); + if(e >= 24) { y = (y + 1 + x / y) >> 1; } + y = (y + 1 + x / y) >> 1; + } else if(e >= 8) { + y = (sqq_table[x >> ((e - 6) - (e & 1))] >> (7 - (e >> 1))) + 1; + } else { + return sqq_table[x] >> 4; + } + + return (x < (y * y)) ? y - 1 : y; +} + +#endif /* SS_BLOCKSIZE != 0 */ + + +/*---------------------------------------------------------------------------*/ + +/* Compares two suffixes. */ +static INLINE +saint_t +ss_compare(const sauchar_t *T, + const saidx_t *p1, const saidx_t *p2, + saidx_t depth) { + const sauchar_t *U1, *U2, *U1n, *U2n; + + for(U1 = T + depth + *p1, + U2 = T + depth + *p2, + U1n = T + *(p1 + 1) + 2, + U2n = T + *(p2 + 1) + 2; + (U1 < U1n) && (U2 < U2n) && (*U1 == *U2); + ++U1, ++U2) { + } + + return U1 < U1n ? + (U2 < U2n ? *U1 - *U2 : 1) : + (U2 < U2n ? -1 : 0); +} + + +/*---------------------------------------------------------------------------*/ + +#if (SS_BLOCKSIZE != 1) && (SS_INSERTIONSORT_THRESHOLD != 1) + +/* Insertionsort for small size groups */ +static +void +ss_insertionsort(const sauchar_t *T, const saidx_t *PA, + saidx_t *first, saidx_t *last, saidx_t depth) { + saidx_t *i, *j; + saidx_t t; + saint_t r; + + for(i = last - 2; first <= i; --i) { + for(t = *i, j = i + 1; 0 < (r = ss_compare(T, PA + t, PA + *j, depth));) { + do { *(j - 1) = *j; } while((++j < last) && (*j < 0)); + if(last <= j) { break; } + } + if(r == 0) { *j = ~*j; } + *(j - 1) = t; + } +} + +#endif /* (SS_BLOCKSIZE != 1) && (SS_INSERTIONSORT_THRESHOLD != 1) */ + + +/*---------------------------------------------------------------------------*/ + +#if (SS_BLOCKSIZE == 0) || (SS_INSERTIONSORT_THRESHOLD < SS_BLOCKSIZE) + +static INLINE +void +ss_fixdown(const sauchar_t *Td, const saidx_t *PA, + saidx_t *SA, saidx_t i, saidx_t size) { + saidx_t j, k; + saidx_t v; + saint_t c, d, e; + + for(v = SA[i], c = Td[PA[v]]; (j = 2 * i + 1) < size; SA[i] = SA[k], i = k) { + d = Td[PA[SA[k = j++]]]; + if(d < (e = Td[PA[SA[j]]])) { k = j; d = e; } + if(d <= c) { break; } + } + SA[i] = v; +} + +/* Simple top-down heapsort. */ +static +void +ss_heapsort(const sauchar_t *Td, const saidx_t *PA, saidx_t *SA, saidx_t size) { + saidx_t i, m; + saidx_t t; + + m = size; + if((size % 2) == 0) { + m--; + if(Td[PA[SA[m / 2]]] < Td[PA[SA[m]]]) { SWAP(SA[m], SA[m / 2]); } + } + + for(i = m / 2 - 1; 0 <= i; --i) { ss_fixdown(Td, PA, SA, i, m); } + if((size % 2) == 0) { SWAP(SA[0], SA[m]); ss_fixdown(Td, PA, SA, 0, m); } + for(i = m - 1; 0 < i; --i) { + t = SA[0], SA[0] = SA[i]; + ss_fixdown(Td, PA, SA, 0, i); + SA[i] = t; + } +} + + +/*---------------------------------------------------------------------------*/ + +/* Returns the median of three elements. */ +static INLINE +saidx_t * +ss_median3(const sauchar_t *Td, const saidx_t *PA, + saidx_t *v1, saidx_t *v2, saidx_t *v3) { + saidx_t *t; + if(Td[PA[*v1]] > Td[PA[*v2]]) { SWAP(v1, v2); } + if(Td[PA[*v2]] > Td[PA[*v3]]) { + if(Td[PA[*v1]] > Td[PA[*v3]]) { return v1; } + else { return v3; } + } + return v2; +} + +/* Returns the median of five elements. */ +static INLINE +saidx_t * +ss_median5(const sauchar_t *Td, const saidx_t *PA, + saidx_t *v1, saidx_t *v2, saidx_t *v3, saidx_t *v4, saidx_t *v5) { + saidx_t *t; + if(Td[PA[*v2]] > Td[PA[*v3]]) { SWAP(v2, v3); } + if(Td[PA[*v4]] > Td[PA[*v5]]) { SWAP(v4, v5); } + if(Td[PA[*v2]] > Td[PA[*v4]]) { SWAP(v2, v4); SWAP(v3, v5); } + if(Td[PA[*v1]] > Td[PA[*v3]]) { SWAP(v1, v3); } + if(Td[PA[*v1]] > Td[PA[*v4]]) { SWAP(v1, v4); SWAP(v3, v5); } + if(Td[PA[*v3]] > Td[PA[*v4]]) { return v4; } + return v3; +} + +/* Returns the pivot element. */ +static INLINE +saidx_t * +ss_pivot(const sauchar_t *Td, const saidx_t *PA, saidx_t *first, saidx_t *last) { + saidx_t *middle; + saidx_t t; + + t = last - first; + middle = first + t / 2; + + if(t <= 512) { + if(t <= 32) { + return ss_median3(Td, PA, first, middle, last - 1); + } else { + t >>= 2; + return ss_median5(Td, PA, first, first + t, middle, last - 1 - t, last - 1); + } + } + t >>= 3; + first = ss_median3(Td, PA, first, first + t, first + (t << 1)); + middle = ss_median3(Td, PA, middle - t, middle, middle + t); + last = ss_median3(Td, PA, last - 1 - (t << 1), last - 1 - t, last - 1); + return ss_median3(Td, PA, first, middle, last); +} + + +/*---------------------------------------------------------------------------*/ + +/* Binary partition for substrings. */ +static INLINE +saidx_t * +ss_partition(const saidx_t *PA, + saidx_t *first, saidx_t *last, saidx_t depth) { + saidx_t *a, *b; + saidx_t t; + for(a = first - 1, b = last;;) { + for(; (++a < b) && ((PA[*a] + depth) >= (PA[*a + 1] + 1));) { *a = ~*a; } + for(; (a < --b) && ((PA[*b] + depth) < (PA[*b + 1] + 1));) { } + if(b <= a) { break; } + t = ~*b; + *b = *a; + *a = t; + } + if(first < a) { *first = ~*first; } + return a; +} + +/* Multikey introsort for medium size groups. */ +static +void +ss_mintrosort(const sauchar_t *T, const saidx_t *PA, + saidx_t *first, saidx_t *last, + saidx_t depth) { +#define STACK_SIZE SS_MISORT_STACKSIZE + struct { saidx_t *a, *b, c; saint_t d; } stack[STACK_SIZE]; + const sauchar_t *Td; + saidx_t *a, *b, *c, *d, *e, *f; + saidx_t s, t; + saint_t ssize; + saint_t limit; + saint_t v, x = 0; + + for(ssize = 0, limit = ss_ilg(last - first);;) { + + if((last - first) <= SS_INSERTIONSORT_THRESHOLD) { +#if 1 < SS_INSERTIONSORT_THRESHOLD + if(1 < (last - first)) { ss_insertionsort(T, PA, first, last, depth); } +#endif + STACK_POP(first, last, depth, limit); + continue; + } + + Td = T + depth; + if(limit-- == 0) { ss_heapsort(Td, PA, first, last - first); } + if(limit < 0) { + for(a = first + 1, v = Td[PA[*first]]; a < last; ++a) { + if((x = Td[PA[*a]]) != v) { + if(1 < (a - first)) { break; } + v = x; + first = a; + } + } + if(Td[PA[*first] - 1] < v) { + first = ss_partition(PA, first, a, depth); + } + if((a - first) <= (last - a)) { + if(1 < (a - first)) { + STACK_PUSH(a, last, depth, -1); + last = a, depth += 1, limit = ss_ilg(a - first); + } else { + first = a, limit = -1; + } + } else { + if(1 < (last - a)) { + STACK_PUSH(first, a, depth + 1, ss_ilg(a - first)); + first = a, limit = -1; + } else { + last = a, depth += 1, limit = ss_ilg(a - first); + } + } + continue; + } + + /* choose pivot */ + a = ss_pivot(Td, PA, first, last); + v = Td[PA[*a]]; + SWAP(*first, *a); + + /* partition */ + for(b = first; (++b < last) && ((x = Td[PA[*b]]) == v);) { } + if(((a = b) < last) && (x < v)) { + for(; (++b < last) && ((x = Td[PA[*b]]) <= v);) { + if(x == v) { SWAP(*b, *a); ++a; } + } + } + for(c = last; (b < --c) && ((x = Td[PA[*c]]) == v);) { } + if((b < (d = c)) && (x > v)) { + for(; (b < --c) && ((x = Td[PA[*c]]) >= v);) { + if(x == v) { SWAP(*c, *d); --d; } + } + } + for(; b < c;) { + SWAP(*b, *c); + for(; (++b < c) && ((x = Td[PA[*b]]) <= v);) { + if(x == v) { SWAP(*b, *a); ++a; } + } + for(; (b < --c) && ((x = Td[PA[*c]]) >= v);) { + if(x == v) { SWAP(*c, *d); --d; } + } + } + + if(a <= d) { + c = b - 1; + + if((s = a - first) > (t = b - a)) { s = t; } + for(e = first, f = b - s; 0 < s; --s, ++e, ++f) { SWAP(*e, *f); } + if((s = d - c) > (t = last - d - 1)) { s = t; } + for(e = b, f = last - s; 0 < s; --s, ++e, ++f) { SWAP(*e, *f); } + + a = first + (b - a), c = last - (d - c); + b = (v <= Td[PA[*a] - 1]) ? a : ss_partition(PA, a, c, depth); + + if((a - first) <= (last - c)) { + if((last - c) <= (c - b)) { + STACK_PUSH(b, c, depth + 1, ss_ilg(c - b)); + STACK_PUSH(c, last, depth, limit); + last = a; + } else if((a - first) <= (c - b)) { + STACK_PUSH(c, last, depth, limit); + STACK_PUSH(b, c, depth + 1, ss_ilg(c - b)); + last = a; + } else { + STACK_PUSH(c, last, depth, limit); + STACK_PUSH(first, a, depth, limit); + first = b, last = c, depth += 1, limit = ss_ilg(c - b); + } + } else { + if((a - first) <= (c - b)) { + STACK_PUSH(b, c, depth + 1, ss_ilg(c - b)); + STACK_PUSH(first, a, depth, limit); + first = c; + } else if((last - c) <= (c - b)) { + STACK_PUSH(first, a, depth, limit); + STACK_PUSH(b, c, depth + 1, ss_ilg(c - b)); + first = c; + } else { + STACK_PUSH(first, a, depth, limit); + STACK_PUSH(c, last, depth, limit); + first = b, last = c, depth += 1, limit = ss_ilg(c - b); + } + } + } else { + limit += 1; + if(Td[PA[*first] - 1] < v) { + first = ss_partition(PA, first, last, depth); + limit = ss_ilg(last - first); + } + depth += 1; + } + } +#undef STACK_SIZE +} + +#endif /* (SS_BLOCKSIZE == 0) || (SS_INSERTIONSORT_THRESHOLD < SS_BLOCKSIZE) */ + + +/*---------------------------------------------------------------------------*/ + +#if SS_BLOCKSIZE != 0 + +static INLINE +void +ss_blockswap(saidx_t *a, saidx_t *b, saidx_t n) { + saidx_t t; + for(; 0 < n; --n, ++a, ++b) { + t = *a, *a = *b, *b = t; + } +} + +static INLINE +void +ss_rotate(saidx_t *first, saidx_t *middle, saidx_t *last) { + saidx_t *a, *b, t; + saidx_t l, r; + l = middle - first, r = last - middle; + for(; (0 < l) && (0 < r);) { + if(l == r) { ss_blockswap(first, middle, l); break; } + if(l < r) { + a = last - 1, b = middle - 1; + t = *a; + do { + *a-- = *b, *b-- = *a; + if(b < first) { + *a = t; + last = a; + if((r -= l + 1) <= l) { break; } + a -= 1, b = middle - 1; + t = *a; + } + } while(1); + } else { + a = first, b = middle; + t = *a; + do { + *a++ = *b, *b++ = *a; + if(last <= b) { + *a = t; + first = a + 1; + if((l -= r + 1) <= r) { break; } + a += 1, b = middle; + t = *a; + } + } while(1); + } + } +} + + +/*---------------------------------------------------------------------------*/ + +static +void +ss_inplacemerge(const sauchar_t *T, const saidx_t *PA, + saidx_t *first, saidx_t *middle, saidx_t *last, + saidx_t depth) { + const saidx_t *p; + saidx_t *a, *b; + saidx_t len, half; + saint_t q, r; + saint_t x; + + for(;;) { + if(*(last - 1) < 0) { x = 1; p = PA + ~*(last - 1); } + else { x = 0; p = PA + *(last - 1); } + for(a = first, len = middle - first, half = len >> 1, r = -1; + 0 < len; + len = half, half >>= 1) { + b = a + half; + q = ss_compare(T, PA + ((0 <= *b) ? *b : ~*b), p, depth); + if(q < 0) { + a = b + 1; + half -= (len & 1) ^ 1; + } else { + r = q; + } + } + if(a < middle) { + if(r == 0) { *a = ~*a; } + ss_rotate(a, middle, last); + last -= middle - a; + middle = a; + if(first == middle) { break; } + } + --last; + if(x != 0) { while(*--last < 0) { } } + if(middle == last) { break; } + } +} + + +/*---------------------------------------------------------------------------*/ + +/* Merge-forward with internal buffer. */ +static +void +ss_mergeforward(const sauchar_t *T, const saidx_t *PA, + saidx_t *first, saidx_t *middle, saidx_t *last, + saidx_t *buf, saidx_t depth) { + saidx_t *a, *b, *c, *bufend; + saidx_t t; + saint_t r; + + bufend = buf + (middle - first) - 1; + ss_blockswap(buf, first, middle - first); + + for(t = *(a = first), b = buf, c = middle;;) { + r = ss_compare(T, PA + *b, PA + *c, depth); + if(r < 0) { + do { + *a++ = *b; + if(bufend <= b) { *bufend = t; return; } + *b++ = *a; + } while(*b < 0); + } else if(r > 0) { + do { + *a++ = *c, *c++ = *a; + if(last <= c) { + while(b < bufend) { *a++ = *b, *b++ = *a; } + *a = *b, *b = t; + return; + } + } while(*c < 0); + } else { + *c = ~*c; + do { + *a++ = *b; + if(bufend <= b) { *bufend = t; return; } + *b++ = *a; + } while(*b < 0); + + do { + *a++ = *c, *c++ = *a; + if(last <= c) { + while(b < bufend) { *a++ = *b, *b++ = *a; } + *a = *b, *b = t; + return; + } + } while(*c < 0); + } + } +} + +/* Merge-backward with internal buffer. */ +static +void +ss_mergebackward(const sauchar_t *T, const saidx_t *PA, + saidx_t *first, saidx_t *middle, saidx_t *last, + saidx_t *buf, saidx_t depth) { + const saidx_t *p1, *p2; + saidx_t *a, *b, *c, *bufend; + saidx_t t; + saint_t r; + saint_t x; + + bufend = buf + (last - middle) - 1; + ss_blockswap(buf, middle, last - middle); + + x = 0; + if(*bufend < 0) { p1 = PA + ~*bufend; x |= 1; } + else { p1 = PA + *bufend; } + if(*(middle - 1) < 0) { p2 = PA + ~*(middle - 1); x |= 2; } + else { p2 = PA + *(middle - 1); } + for(t = *(a = last - 1), b = bufend, c = middle - 1;;) { + r = ss_compare(T, p1, p2, depth); + if(0 < r) { + if(x & 1) { do { *a-- = *b, *b-- = *a; } while(*b < 0); x ^= 1; } + *a-- = *b; + if(b <= buf) { *buf = t; break; } + *b-- = *a; + if(*b < 0) { p1 = PA + ~*b; x |= 1; } + else { p1 = PA + *b; } + } else if(r < 0) { + if(x & 2) { do { *a-- = *c, *c-- = *a; } while(*c < 0); x ^= 2; } + *a-- = *c, *c-- = *a; + if(c < first) { + while(buf < b) { *a-- = *b, *b-- = *a; } + *a = *b, *b = t; + break; + } + if(*c < 0) { p2 = PA + ~*c; x |= 2; } + else { p2 = PA + *c; } + } else { + if(x & 1) { do { *a-- = *b, *b-- = *a; } while(*b < 0); x ^= 1; } + *a-- = ~*b; + if(b <= buf) { *buf = t; break; } + *b-- = *a; + if(x & 2) { do { *a-- = *c, *c-- = *a; } while(*c < 0); x ^= 2; } + *a-- = *c, *c-- = *a; + if(c < first) { + while(buf < b) { *a-- = *b, *b-- = *a; } + *a = *b, *b = t; + break; + } + if(*b < 0) { p1 = PA + ~*b; x |= 1; } + else { p1 = PA + *b; } + if(*c < 0) { p2 = PA + ~*c; x |= 2; } + else { p2 = PA + *c; } + } + } +} + +/* D&C based merge. */ +static +void +ss_swapmerge(const sauchar_t *T, const saidx_t *PA, + saidx_t *first, saidx_t *middle, saidx_t *last, + saidx_t *buf, saidx_t bufsize, saidx_t depth) { +#define STACK_SIZE SS_SMERGE_STACKSIZE +#define GETIDX(a) ((0 <= (a)) ? (a) : (~(a))) +#define MERGE_CHECK(a, b, c)\ + do {\ + if(((c) & 1) ||\ + (((c) & 2) && (ss_compare(T, PA + GETIDX(*((a) - 1)), PA + *(a), depth) == 0))) {\ + *(a) = ~*(a);\ + }\ + if(((c) & 4) && ((ss_compare(T, PA + GETIDX(*((b) - 1)), PA + *(b), depth) == 0))) {\ + *(b) = ~*(b);\ + }\ + } while(0) + struct { saidx_t *a, *b, *c; saint_t d; } stack[STACK_SIZE]; + saidx_t *l, *r, *lm, *rm; + saidx_t m, len, half; + saint_t ssize; + saint_t check, next; + + for(check = 0, ssize = 0;;) { + if((last - middle) <= bufsize) { + if((first < middle) && (middle < last)) { + ss_mergebackward(T, PA, first, middle, last, buf, depth); + } + MERGE_CHECK(first, last, check); + STACK_POP(first, middle, last, check); + continue; + } + + if((middle - first) <= bufsize) { + if(first < middle) { + ss_mergeforward(T, PA, first, middle, last, buf, depth); + } + MERGE_CHECK(first, last, check); + STACK_POP(first, middle, last, check); + continue; + } + + for(m = 0, len = MIN(middle - first, last - middle), half = len >> 1; + 0 < len; + len = half, half >>= 1) { + if(ss_compare(T, PA + GETIDX(*(middle + m + half)), + PA + GETIDX(*(middle - m - half - 1)), depth) < 0) { + m += half + 1; + half -= (len & 1) ^ 1; + } + } + + if(0 < m) { + lm = middle - m, rm = middle + m; + ss_blockswap(lm, middle, m); + l = r = middle, next = 0; + if(rm < last) { + if(*rm < 0) { + *rm = ~*rm; + if(first < lm) { for(; *--l < 0;) { } next |= 4; } + next |= 1; + } else if(first < lm) { + for(; *r < 0; ++r) { } + next |= 2; + } + } + + if((l - first) <= (last - r)) { + STACK_PUSH(r, rm, last, (next & 3) | (check & 4)); + middle = lm, last = l, check = (check & 3) | (next & 4); + } else { + if((next & 2) && (r == middle)) { next ^= 6; } + STACK_PUSH(first, lm, l, (check & 3) | (next & 4)); + first = r, middle = rm, check = (next & 3) | (check & 4); + } + } else { + if(ss_compare(T, PA + GETIDX(*(middle - 1)), PA + *middle, depth) == 0) { + *middle = ~*middle; + } + MERGE_CHECK(first, last, check); + STACK_POP(first, middle, last, check); + } + } +#undef STACK_SIZE +} + +#endif /* SS_BLOCKSIZE != 0 */ + + +/*---------------------------------------------------------------------------*/ + +/*- Function -*/ + +/* Substring sort */ +void +sssort(const sauchar_t *T, const saidx_t *PA, + saidx_t *first, saidx_t *last, + saidx_t *buf, saidx_t bufsize, + saidx_t depth, saidx_t n, saint_t lastsuffix) { + saidx_t *a; +#if SS_BLOCKSIZE != 0 + saidx_t *b, *middle, *curbuf; + saidx_t j, k, curbufsize, limit; +#endif + saidx_t i; + + if(lastsuffix != 0) { ++first; } + +#if SS_BLOCKSIZE == 0 + ss_mintrosort(T, PA, first, last, depth); +#else + if((bufsize < SS_BLOCKSIZE) && + (bufsize < (last - first)) && + (bufsize < (limit = ss_isqrt(last - first)))) { + if(SS_BLOCKSIZE < limit) { limit = SS_BLOCKSIZE; } + buf = middle = last - limit, bufsize = limit; + } else { + middle = last, limit = 0; + } + for(a = first, i = 0; SS_BLOCKSIZE < (middle - a); a += SS_BLOCKSIZE, ++i) { +#if SS_INSERTIONSORT_THRESHOLD < SS_BLOCKSIZE + ss_mintrosort(T, PA, a, a + SS_BLOCKSIZE, depth); +#elif 1 < SS_BLOCKSIZE + ss_insertionsort(T, PA, a, a + SS_BLOCKSIZE, depth); +#endif + curbufsize = last - (a + SS_BLOCKSIZE); + curbuf = a + SS_BLOCKSIZE; + if(curbufsize <= bufsize) { curbufsize = bufsize, curbuf = buf; } + for(b = a, k = SS_BLOCKSIZE, j = i; j & 1; b -= k, k <<= 1, j >>= 1) { + ss_swapmerge(T, PA, b - k, b, b + k, curbuf, curbufsize, depth); + } + } +#if SS_INSERTIONSORT_THRESHOLD < SS_BLOCKSIZE + ss_mintrosort(T, PA, a, middle, depth); +#elif 1 < SS_BLOCKSIZE + ss_insertionsort(T, PA, a, middle, depth); +#endif + for(k = SS_BLOCKSIZE; i != 0; k <<= 1, i >>= 1) { + if(i & 1) { + ss_swapmerge(T, PA, a - k, a, middle, buf, bufsize, depth); + a -= k; + } + } + if(limit != 0) { +#if SS_INSERTIONSORT_THRESHOLD < SS_BLOCKSIZE + ss_mintrosort(T, PA, middle, last, depth); +#elif 1 < SS_BLOCKSIZE + ss_insertionsort(T, PA, middle, last, depth); +#endif + ss_inplacemerge(T, PA, first, middle, last, depth); + } +#endif + + if(lastsuffix != 0) { + /* Insert last type B* suffix. */ + saidx_t PAi[2]; PAi[0] = PA[*(first - 1)], PAi[1] = n - 2; + for(a = first, i = *(first - 1); + (a < last) && ((*a < 0) || (0 < ss_compare(T, &(PAi[0]), PA + *a, depth))); + ++a) { + *(a - 1) = *a; + } + *(a - 1) = i; + } +} diff --git a/Tools/unix/lzsa/src/libdivsufsort/lib/trsort.c b/Tools/unix/lzsa/src/libdivsufsort/lib/trsort.c new file mode 100644 index 00000000..6fe3e67b --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/lib/trsort.c @@ -0,0 +1,586 @@ +/* + * trsort.c for libdivsufsort + * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "divsufsort_private.h" + + +/*- Private Functions -*/ + +static const saint_t lg_table[256]= { + -1,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 +}; + +static INLINE +saint_t +tr_ilg(saidx_t n) { +#if defined(BUILD_DIVSUFSORT64) + return (n >> 32) ? + ((n >> 48) ? + ((n >> 56) ? + 56 + lg_table[(n >> 56) & 0xff] : + 48 + lg_table[(n >> 48) & 0xff]) : + ((n >> 40) ? + 40 + lg_table[(n >> 40) & 0xff] : + 32 + lg_table[(n >> 32) & 0xff])) : + ((n & 0xffff0000) ? + ((n & 0xff000000) ? + 24 + lg_table[(n >> 24) & 0xff] : + 16 + lg_table[(n >> 16) & 0xff]) : + ((n & 0x0000ff00) ? + 8 + lg_table[(n >> 8) & 0xff] : + 0 + lg_table[(n >> 0) & 0xff])); +#else + return (n & 0xffff0000) ? + ((n & 0xff000000) ? + 24 + lg_table[(n >> 24) & 0xff] : + 16 + lg_table[(n >> 16) & 0xff]) : + ((n & 0x0000ff00) ? + 8 + lg_table[(n >> 8) & 0xff] : + 0 + lg_table[(n >> 0) & 0xff]); +#endif +} + + +/*---------------------------------------------------------------------------*/ + +/* Simple insertionsort for small size groups. */ +static +void +tr_insertionsort(const saidx_t *ISAd, saidx_t *first, saidx_t *last) { + saidx_t *a, *b; + saidx_t t, r; + + for(a = first + 1; a < last; ++a) { + for(t = *a, b = a - 1; 0 > (r = ISAd[t] - ISAd[*b]);) { + do { *(b + 1) = *b; } while((first <= --b) && (*b < 0)); + if(b < first) { break; } + } + if(r == 0) { *b = ~*b; } + *(b + 1) = t; + } +} + + +/*---------------------------------------------------------------------------*/ + +static INLINE +void +tr_fixdown(const saidx_t *ISAd, saidx_t *SA, saidx_t i, saidx_t size) { + saidx_t j, k; + saidx_t v; + saidx_t c, d, e; + + for(v = SA[i], c = ISAd[v]; (j = 2 * i + 1) < size; SA[i] = SA[k], i = k) { + d = ISAd[SA[k = j++]]; + if(d < (e = ISAd[SA[j]])) { k = j; d = e; } + if(d <= c) { break; } + } + SA[i] = v; +} + +/* Simple top-down heapsort. */ +static +void +tr_heapsort(const saidx_t *ISAd, saidx_t *SA, saidx_t size) { + saidx_t i, m; + saidx_t t; + + m = size; + if((size % 2) == 0) { + m--; + if(ISAd[SA[m / 2]] < ISAd[SA[m]]) { SWAP(SA[m], SA[m / 2]); } + } + + for(i = m / 2 - 1; 0 <= i; --i) { tr_fixdown(ISAd, SA, i, m); } + if((size % 2) == 0) { SWAP(SA[0], SA[m]); tr_fixdown(ISAd, SA, 0, m); } + for(i = m - 1; 0 < i; --i) { + t = SA[0], SA[0] = SA[i]; + tr_fixdown(ISAd, SA, 0, i); + SA[i] = t; + } +} + + +/*---------------------------------------------------------------------------*/ + +/* Returns the median of three elements. */ +static INLINE +saidx_t * +tr_median3(const saidx_t *ISAd, saidx_t *v1, saidx_t *v2, saidx_t *v3) { + saidx_t *t; + if(ISAd[*v1] > ISAd[*v2]) { SWAP(v1, v2); } + if(ISAd[*v2] > ISAd[*v3]) { + if(ISAd[*v1] > ISAd[*v3]) { return v1; } + else { return v3; } + } + return v2; +} + +/* Returns the median of five elements. */ +static INLINE +saidx_t * +tr_median5(const saidx_t *ISAd, + saidx_t *v1, saidx_t *v2, saidx_t *v3, saidx_t *v4, saidx_t *v5) { + saidx_t *t; + if(ISAd[*v2] > ISAd[*v3]) { SWAP(v2, v3); } + if(ISAd[*v4] > ISAd[*v5]) { SWAP(v4, v5); } + if(ISAd[*v2] > ISAd[*v4]) { SWAP(v2, v4); SWAP(v3, v5); } + if(ISAd[*v1] > ISAd[*v3]) { SWAP(v1, v3); } + if(ISAd[*v1] > ISAd[*v4]) { SWAP(v1, v4); SWAP(v3, v5); } + if(ISAd[*v3] > ISAd[*v4]) { return v4; } + return v3; +} + +/* Returns the pivot element. */ +static INLINE +saidx_t * +tr_pivot(const saidx_t *ISAd, saidx_t *first, saidx_t *last) { + saidx_t *middle; + saidx_t t; + + t = last - first; + middle = first + t / 2; + + if(t <= 512) { + if(t <= 32) { + return tr_median3(ISAd, first, middle, last - 1); + } else { + t >>= 2; + return tr_median5(ISAd, first, first + t, middle, last - 1 - t, last - 1); + } + } + t >>= 3; + first = tr_median3(ISAd, first, first + t, first + (t << 1)); + middle = tr_median3(ISAd, middle - t, middle, middle + t); + last = tr_median3(ISAd, last - 1 - (t << 1), last - 1 - t, last - 1); + return tr_median3(ISAd, first, middle, last); +} + + +/*---------------------------------------------------------------------------*/ + +typedef struct _trbudget_t trbudget_t; +struct _trbudget_t { + saidx_t chance; + saidx_t remain; + saidx_t incval; + saidx_t count; +}; + +static INLINE +void +trbudget_init(trbudget_t *budget, saidx_t chance, saidx_t incval) { + budget->chance = chance; + budget->remain = budget->incval = incval; +} + +static INLINE +saint_t +trbudget_check(trbudget_t *budget, saidx_t size) { + if(size <= budget->remain) { budget->remain -= size; return 1; } + if(budget->chance == 0) { budget->count += size; return 0; } + budget->remain += budget->incval - size; + budget->chance -= 1; + return 1; +} + + +/*---------------------------------------------------------------------------*/ + +static INLINE +void +tr_partition(const saidx_t *ISAd, + saidx_t *first, saidx_t *middle, saidx_t *last, + saidx_t **pa, saidx_t **pb, saidx_t v) { + saidx_t *a, *b, *c, *d, *e, *f; + saidx_t t, s; + saidx_t x = 0; + + for(b = middle - 1; (++b < last) && ((x = ISAd[*b]) == v);) { } + if(((a = b) < last) && (x < v)) { + for(; (++b < last) && ((x = ISAd[*b]) <= v);) { + if(x == v) { SWAP(*b, *a); ++a; } + } + } + for(c = last; (b < --c) && ((x = ISAd[*c]) == v);) { } + if((b < (d = c)) && (x > v)) { + for(; (b < --c) && ((x = ISAd[*c]) >= v);) { + if(x == v) { SWAP(*c, *d); --d; } + } + } + for(; b < c;) { + SWAP(*b, *c); + for(; (++b < c) && ((x = ISAd[*b]) <= v);) { + if(x == v) { SWAP(*b, *a); ++a; } + } + for(; (b < --c) && ((x = ISAd[*c]) >= v);) { + if(x == v) { SWAP(*c, *d); --d; } + } + } + + if(a <= d) { + c = b - 1; + if((s = a - first) > (t = b - a)) { s = t; } + for(e = first, f = b - s; 0 < s; --s, ++e, ++f) { SWAP(*e, *f); } + if((s = d - c) > (t = last - d - 1)) { s = t; } + for(e = b, f = last - s; 0 < s; --s, ++e, ++f) { SWAP(*e, *f); } + first += (b - a), last -= (d - c); + } + *pa = first, *pb = last; +} + +static +void +tr_copy(saidx_t *ISA, const saidx_t *SA, + saidx_t *first, saidx_t *a, saidx_t *b, saidx_t *last, + saidx_t depth) { + /* sort suffixes of middle partition + by using sorted order of suffixes of left and right partition. */ + saidx_t *c, *d, *e; + saidx_t s, v; + + v = b - SA - 1; + for(c = first, d = a - 1; c <= d; ++c) { + if((0 <= (s = *c - depth)) && (ISA[s] == v)) { + *++d = s; + ISA[s] = d - SA; + } + } + for(c = last - 1, e = d + 1, d = b; e < d; --c) { + if((0 <= (s = *c - depth)) && (ISA[s] == v)) { + *--d = s; + ISA[s] = d - SA; + } + } +} + +static +void +tr_partialcopy(saidx_t *ISA, const saidx_t *SA, + saidx_t *first, saidx_t *a, saidx_t *b, saidx_t *last, + saidx_t depth) { + saidx_t *c, *d, *e; + saidx_t s, v; + saidx_t rank, lastrank, newrank = -1; + + v = b - SA - 1; + lastrank = -1; + for(c = first, d = a - 1; c <= d; ++c) { + if((0 <= (s = *c - depth)) && (ISA[s] == v)) { + *++d = s; + rank = ISA[s + depth]; + if(lastrank != rank) { lastrank = rank; newrank = d - SA; } + ISA[s] = newrank; + } + } + + lastrank = -1; + for(e = d; first <= e; --e) { + rank = ISA[*e]; + if(lastrank != rank) { lastrank = rank; newrank = e - SA; } + if(newrank != rank) { ISA[*e] = newrank; } + } + + lastrank = -1; + for(c = last - 1, e = d + 1, d = b; e < d; --c) { + if((0 <= (s = *c - depth)) && (ISA[s] == v)) { + *--d = s; + rank = ISA[s + depth]; + if(lastrank != rank) { lastrank = rank; newrank = d - SA; } + ISA[s] = newrank; + } + } +} + +static +void +tr_introsort(saidx_t *ISA, const saidx_t *ISAd, + saidx_t *SA, saidx_t *first, saidx_t *last, + trbudget_t *budget) { +#define STACK_SIZE TR_STACKSIZE + struct { const saidx_t *a; saidx_t *b, *c; saint_t d, e; }stack[STACK_SIZE]; + saidx_t *a, *b, *c; + saidx_t t; + saidx_t v, x = 0; + saidx_t incr = ISAd - ISA; + saint_t limit, next; + saint_t ssize, trlink = -1; + + for(ssize = 0, limit = tr_ilg(last - first);;) { + + if(limit < 0) { + if(limit == -1) { + /* tandem repeat partition */ + tr_partition(ISAd - incr, first, first, last, &a, &b, last - SA - 1); + + /* update ranks */ + if(a < last) { + for(c = first, v = a - SA - 1; c < a; ++c) { ISA[*c] = v; } + } + if(b < last) { + for(c = a, v = b - SA - 1; c < b; ++c) { ISA[*c] = v; } + } + + /* push */ + if(1 < (b - a)) { + STACK_PUSH5(NULL, a, b, 0, 0); + STACK_PUSH5(ISAd - incr, first, last, -2, trlink); + trlink = ssize - 2; + } + if((a - first) <= (last - b)) { + if(1 < (a - first)) { + STACK_PUSH5(ISAd, b, last, tr_ilg(last - b), trlink); + last = a, limit = tr_ilg(a - first); + } else if(1 < (last - b)) { + first = b, limit = tr_ilg(last - b); + } else { + STACK_POP5(ISAd, first, last, limit, trlink); + } + } else { + if(1 < (last - b)) { + STACK_PUSH5(ISAd, first, a, tr_ilg(a - first), trlink); + first = b, limit = tr_ilg(last - b); + } else if(1 < (a - first)) { + last = a, limit = tr_ilg(a - first); + } else { + STACK_POP5(ISAd, first, last, limit, trlink); + } + } + } else if(limit == -2) { + /* tandem repeat copy */ + a = stack[--ssize].b, b = stack[ssize].c; + if(stack[ssize].d == 0) { + tr_copy(ISA, SA, first, a, b, last, ISAd - ISA); + } else { + if(0 <= trlink) { stack[trlink].d = -1; } + tr_partialcopy(ISA, SA, first, a, b, last, ISAd - ISA); + } + STACK_POP5(ISAd, first, last, limit, trlink); + } else { + /* sorted partition */ + if(0 <= *first) { + a = first; + do { ISA[*a] = a - SA; } while((++a < last) && (0 <= *a)); + first = a; + } + if(first < last) { + a = first; do { *a = ~*a; } while(*++a < 0); + next = (ISA[*a] != ISAd[*a]) ? tr_ilg(a - first + 1) : -1; + if(++a < last) { for(b = first, v = a - SA - 1; b < a; ++b) { ISA[*b] = v; } } + + /* push */ + if(trbudget_check(budget, a - first)) { + if((a - first) <= (last - a)) { + STACK_PUSH5(ISAd, a, last, -3, trlink); + ISAd += incr, last = a, limit = next; + } else { + if(1 < (last - a)) { + STACK_PUSH5(ISAd + incr, first, a, next, trlink); + first = a, limit = -3; + } else { + ISAd += incr, last = a, limit = next; + } + } + } else { + if(0 <= trlink) { stack[trlink].d = -1; } + if(1 < (last - a)) { + first = a, limit = -3; + } else { + STACK_POP5(ISAd, first, last, limit, trlink); + } + } + } else { + STACK_POP5(ISAd, first, last, limit, trlink); + } + } + continue; + } + + if((last - first) <= TR_INSERTIONSORT_THRESHOLD) { + tr_insertionsort(ISAd, first, last); + limit = -3; + continue; + } + + if(limit-- == 0) { + tr_heapsort(ISAd, first, last - first); + for(a = last - 1; first < a; a = b) { + for(x = ISAd[*a], b = a - 1; (first <= b) && (ISAd[*b] == x); --b) { *b = ~*b; } + } + limit = -3; + continue; + } + + /* choose pivot */ + a = tr_pivot(ISAd, first, last); + SWAP(*first, *a); + v = ISAd[*first]; + + /* partition */ + tr_partition(ISAd, first, first + 1, last, &a, &b, v); + if((last - first) != (b - a)) { + next = (ISA[*a] != v) ? tr_ilg(b - a) : -1; + + /* update ranks */ + for(c = first, v = a - SA - 1; c < a; ++c) { ISA[*c] = v; } + if(b < last) { for(c = a, v = b - SA - 1; c < b; ++c) { ISA[*c] = v; } } + + /* push */ + if((1 < (b - a)) && (trbudget_check(budget, b - a))) { + if((a - first) <= (last - b)) { + if((last - b) <= (b - a)) { + if(1 < (a - first)) { + STACK_PUSH5(ISAd + incr, a, b, next, trlink); + STACK_PUSH5(ISAd, b, last, limit, trlink); + last = a; + } else if(1 < (last - b)) { + STACK_PUSH5(ISAd + incr, a, b, next, trlink); + first = b; + } else { + ISAd += incr, first = a, last = b, limit = next; + } + } else if((a - first) <= (b - a)) { + if(1 < (a - first)) { + STACK_PUSH5(ISAd, b, last, limit, trlink); + STACK_PUSH5(ISAd + incr, a, b, next, trlink); + last = a; + } else { + STACK_PUSH5(ISAd, b, last, limit, trlink); + ISAd += incr, first = a, last = b, limit = next; + } + } else { + STACK_PUSH5(ISAd, b, last, limit, trlink); + STACK_PUSH5(ISAd, first, a, limit, trlink); + ISAd += incr, first = a, last = b, limit = next; + } + } else { + if((a - first) <= (b - a)) { + if(1 < (last - b)) { + STACK_PUSH5(ISAd + incr, a, b, next, trlink); + STACK_PUSH5(ISAd, first, a, limit, trlink); + first = b; + } else if(1 < (a - first)) { + STACK_PUSH5(ISAd + incr, a, b, next, trlink); + last = a; + } else { + ISAd += incr, first = a, last = b, limit = next; + } + } else if((last - b) <= (b - a)) { + if(1 < (last - b)) { + STACK_PUSH5(ISAd, first, a, limit, trlink); + STACK_PUSH5(ISAd + incr, a, b, next, trlink); + first = b; + } else { + STACK_PUSH5(ISAd, first, a, limit, trlink); + ISAd += incr, first = a, last = b, limit = next; + } + } else { + STACK_PUSH5(ISAd, first, a, limit, trlink); + STACK_PUSH5(ISAd, b, last, limit, trlink); + ISAd += incr, first = a, last = b, limit = next; + } + } + } else { + if((1 < (b - a)) && (0 <= trlink)) { stack[trlink].d = -1; } + if((a - first) <= (last - b)) { + if(1 < (a - first)) { + STACK_PUSH5(ISAd, b, last, limit, trlink); + last = a; + } else if(1 < (last - b)) { + first = b; + } else { + STACK_POP5(ISAd, first, last, limit, trlink); + } + } else { + if(1 < (last - b)) { + STACK_PUSH5(ISAd, first, a, limit, trlink); + first = b; + } else if(1 < (a - first)) { + last = a; + } else { + STACK_POP5(ISAd, first, last, limit, trlink); + } + } + } + } else { + if(trbudget_check(budget, last - first)) { + limit = tr_ilg(last - first), ISAd += incr; + } else { + if(0 <= trlink) { stack[trlink].d = -1; } + STACK_POP5(ISAd, first, last, limit, trlink); + } + } + } +#undef STACK_SIZE +} + + + +/*---------------------------------------------------------------------------*/ + +/*- Function -*/ + +/* Tandem repeat sort */ +void +trsort(saidx_t *ISA, saidx_t *SA, saidx_t n, saidx_t depth) { + saidx_t *ISAd; + saidx_t *first, *last; + trbudget_t budget; + saidx_t t, skip, unsorted; + + trbudget_init(&budget, tr_ilg(n) * 2 / 3, n); +/* trbudget_init(&budget, tr_ilg(n) * 3 / 4, n); */ + for(ISAd = ISA + depth; -n < *SA; ISAd += ISAd - ISA) { + first = SA; + skip = 0; + unsorted = 0; + do { + if((t = *first) < 0) { first -= t; skip += t; } + else { + if(skip != 0) { *(first + skip) = skip; skip = 0; } + last = SA + ISA[t] + 1; + if(1 < (last - first)) { + budget.count = 0; + tr_introsort(ISA, ISAd, SA, first, last, &budget); + if(budget.count != 0) { unsorted += budget.count; } + else { skip = first - last; } + } else if((last - first) == 1) { + skip = -1; + } + first = last; + } + } while(first < (SA + n)); + if(skip != 0) { *(first + skip) = skip; } + if(unsorted == 0) { break; } + } +} diff --git a/Tools/unix/lzsa/src/libdivsufsort/pkgconfig/CMakeLists.txt b/Tools/unix/lzsa/src/libdivsufsort/pkgconfig/CMakeLists.txt new file mode 100644 index 00000000..ee7063c9 --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/pkgconfig/CMakeLists.txt @@ -0,0 +1,9 @@ +## generate libdivsufsort.pc ## +set(W64BIT "") +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/libdivsufsort.pc.cmake" "${CMAKE_CURRENT_BINARY_DIR}/libdivsufsort.pc" @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libdivsufsort.pc" DESTINATION ${CMAKE_INSTALL_PKGCONFIGDIR}) +if(BUILD_DIVSUFSORT64) + set(W64BIT "64") + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/libdivsufsort.pc.cmake" "${CMAKE_CURRENT_BINARY_DIR}/libdivsufsort64.pc" @ONLY) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libdivsufsort64.pc" DESTINATION ${CMAKE_INSTALL_PKGCONFIGDIR}) +endif(BUILD_DIVSUFSORT64) diff --git a/Tools/unix/lzsa/src/libdivsufsort/pkgconfig/libdivsufsort.pc.cmake b/Tools/unix/lzsa/src/libdivsufsort/pkgconfig/libdivsufsort.pc.cmake new file mode 100644 index 00000000..6419d1ea --- /dev/null +++ b/Tools/unix/lzsa/src/libdivsufsort/pkgconfig/libdivsufsort.pc.cmake @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=@CMAKE_INSTALL_LIBDIR@ +includedir=@CMAKE_INSTALL_INCLUDEDIR@ + +Name: @PROJECT_NAME@@W64BIT@ +Description: @PROJECT_DESCRIPTION@ +Version: @PROJECT_VERSION_FULL@ +URL: @PROJECT_URL@ +Libs: -L${libdir} -ldivsufsort@W64BIT@ +Cflags: -I${includedir} diff --git a/Tools/unix/lzsa/src/lzsa.c b/Tools/unix/lzsa/src/lzsa.c new file mode 100644 index 00000000..4cce4041 --- /dev/null +++ b/Tools/unix/lzsa/src/lzsa.c @@ -0,0 +1,1110 @@ +/* + * lzsa.c - command line compression utility for the LZSA format + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#include +#else +#include +#endif +#include "lib.h" + +#define OPT_VERBOSE 1 +#define OPT_RAW 2 +#define OPT_FAVOR_RATIO 4 +#define OPT_RAW_BACKWARD 8 +#define OPT_STATS 16 + +#define TOOL_VERSION "1.2.0" + +/*---------------------------------------------------------------------------*/ + +#ifdef _WIN32 +LARGE_INTEGER hpc_frequency; +BOOL hpc_available = FALSE; +#endif + +static void do_init_time() { +#ifdef _WIN32 + hpc_frequency.QuadPart = 0; + hpc_available = QueryPerformanceFrequency(&hpc_frequency); +#endif +} + +static long long do_get_time() { + long long nTime; + +#ifdef _WIN32 + if (hpc_available) { + LARGE_INTEGER nCurTime; + + /* Use HPC hardware for best precision */ + QueryPerformanceCounter(&nCurTime); + nTime = (long long)(nCurTime.QuadPart * 1000000LL / hpc_frequency.QuadPart); + } + else { + struct _timeb tb; + _ftime(&tb); + + nTime = ((long long)tb.time * 1000LL + (long long)tb.millitm) * 1000LL; + } +#else + struct timeval tm; + gettimeofday(&tm, NULL); + + nTime = (long long)tm.tv_sec * 1000000LL + (long long)tm.tv_usec; +#endif + return nTime; +} + +/*---------------------------------------------------------------------------*/ + +static void compression_progress(long long nOriginalSize, long long nCompressedSize) { + if (nOriginalSize >= 1024 * 1024) { + fprintf(stdout, "\r%lld => %lld (%g %%) \b\b\b\b\b", nOriginalSize, nCompressedSize, (double)(nCompressedSize * 100.0 / nOriginalSize)); + fflush(stdout); + } +} + +static int do_compress(const char *pszInFilename, const char *pszOutFilename, const char *pszDictionaryFilename, const unsigned int nOptions, const int nMinMatchSize, const int nFormatVersion) { + long long nStartTime = 0LL, nEndTime = 0LL; + long long nOriginalSize = 0LL, nCompressedSize = 0LL; + int nCommandCount = 0, nSafeDist = 0; + int nFlags; + lzsa_status_t nStatus; + lzsa_stats stats; + + nFlags = 0; + if (nOptions & OPT_FAVOR_RATIO) + nFlags |= LZSA_FLAG_FAVOR_RATIO; + if (nOptions & OPT_RAW) + nFlags |= LZSA_FLAG_RAW_BLOCK; + if (nOptions & OPT_RAW_BACKWARD) + nFlags |= LZSA_FLAG_RAW_BACKWARD; + + if (nOptions & OPT_VERBOSE) { + nStartTime = do_get_time(); + } + + nStatus = lzsa_compress_file(pszInFilename, pszOutFilename, pszDictionaryFilename, nFlags, nMinMatchSize, nFormatVersion, compression_progress, &nOriginalSize, &nCompressedSize, &nCommandCount, &nSafeDist, &stats); + + if ((nOptions & OPT_VERBOSE)) { + nEndTime = do_get_time(); + } + + switch (nStatus) { + case LZSA_ERROR_SRC: fprintf(stderr, "error reading '%s'\n", pszInFilename); break; + case LZSA_ERROR_DST: fprintf(stderr, "error writing '%s'\n", pszOutFilename); break; + case LZSA_ERROR_DICTIONARY: fprintf(stderr, "error reading dictionary '%s'\n", pszDictionaryFilename); break; + case LZSA_ERROR_MEMORY: fprintf(stderr, "out of memory\n"); break; + case LZSA_ERROR_COMPRESSION: fprintf(stderr, "internal compression error\n"); break; + case LZSA_ERROR_RAW_TOOLARGE: fprintf(stderr, "error: raw blocks can only be used with files <= 64 Kb\n"); break; + case LZSA_ERROR_RAW_UNCOMPRESSED: fprintf(stderr, "error: incompressible data needs to be <= 64 Kb in raw blocks\n"); break; + case LZSA_OK: break; + default: fprintf(stderr, "unknown compression error %d\n", nStatus); break; + } + + if (nStatus) + return 100; + + if ((nOptions & OPT_VERBOSE)) { + double fDelta = ((double)(nEndTime - nStartTime)) / 1000000.0; + double fSpeed = ((double)nOriginalSize / 1048576.0) / fDelta; + fprintf(stdout, "\rCompressed '%s' in %g seconds, %.02g Mb/s, %d tokens (%g bytes/token), %lld into %lld bytes ==> %g %%\n", + pszInFilename, fDelta, fSpeed, nCommandCount, (double)nOriginalSize / (double)nCommandCount, + nOriginalSize, nCompressedSize, (double)(nCompressedSize * 100.0 / nOriginalSize)); + if (nOptions & OPT_RAW) { + fprintf(stdout, "Safe distance: %d (0x%X)\n", nSafeDist, nSafeDist); + } + } + + if (nOptions & OPT_STATS) { + if (stats.literals_divisor > 0) + fprintf(stdout, "Literals: min: %d avg: %d max: %d count: %d\n", stats.min_literals, stats.total_literals / stats.literals_divisor, stats.max_literals, stats.literals_divisor); + else + fprintf(stdout, "Literals: none\n"); + if (stats.match_divisor > 0) { + fprintf(stdout, "Offsets: min: %d avg: %d max: %d reps: %d count: %d\n", stats.min_offset, stats.total_offsets / stats.match_divisor, stats.max_offset, stats.num_rep_offsets, stats.match_divisor); + fprintf(stdout, "Match lens: min: %d avg: %d max: %d count: %d\n", stats.min_match_len, stats.total_match_lens / stats.match_divisor, stats.max_match_len, stats.match_divisor); + } + else { + fprintf(stdout, "Offsets: none\n"); + fprintf(stdout, "Match lens: none\n"); + } + if (stats.rle1_divisor > 0) { + fprintf(stdout, "RLE1 lens: min: %d avg: %d max: %d count: %d\n", stats.min_rle1_len, stats.total_rle1_lens / stats.rle1_divisor, stats.max_rle1_len, stats.rle1_divisor); + } + else { + fprintf(stdout, "RLE1 lens: none\n"); + } + if (stats.rle2_divisor > 0) { + fprintf(stdout, "RLE2 lens: min: %d avg: %d max: %d count: %d\n", stats.min_rle2_len, stats.total_rle2_lens / stats.rle2_divisor, stats.max_rle2_len, stats.rle2_divisor); + } + else { + fprintf(stdout, "RLE2 lens: none\n"); + } + } + return 0; +} + +/*---------------------------------------------------------------------------*/ + +static int do_decompress(const char *pszInFilename, const char *pszOutFilename, const char *pszDictionaryFilename, const unsigned int nOptions, int nFormatVersion) { + long long nStartTime = 0LL, nEndTime = 0LL; + long long nOriginalSize = 0LL, nCompressedSize = 0LL; + lzsa_status_t nStatus; + int nFlags; + + nFlags = 0; + if (nOptions & OPT_RAW) + nFlags |= LZSA_FLAG_RAW_BLOCK; + if (nOptions & OPT_RAW_BACKWARD) + nFlags |= LZSA_FLAG_RAW_BACKWARD; + + if (nOptions & OPT_VERBOSE) { + nStartTime = do_get_time(); + } + + nStatus = lzsa_decompress_file(pszInFilename, pszOutFilename, pszDictionaryFilename, nFlags, nFormatVersion, &nOriginalSize, &nCompressedSize); + + switch (nStatus) { + case LZSA_ERROR_SRC: fprintf(stderr, "error reading '%s'\n", pszInFilename); break; + case LZSA_ERROR_DST: fprintf(stderr, "error writing '%s'\n", pszOutFilename); break; + case LZSA_ERROR_DICTIONARY: fprintf(stderr, "error reading dictionary '%s'\n", pszDictionaryFilename); break; + case LZSA_ERROR_MEMORY: fprintf(stderr, "out of memory\n"); break; + case LZSA_ERROR_DECOMPRESSION: fprintf(stderr, "internal decompression error\n"); break; + case LZSA_ERROR_FORMAT: fprintf(stderr, "invalid magic number or format version in input file\n"); break; + case LZSA_OK: break; + default: fprintf(stderr, "unknown decompression error %d\n", nStatus); break; + } + + if (nStatus) { + fprintf(stderr, "decompression error for '%s'\n", pszInFilename); + return 100; + } + else { + if (nOptions & OPT_VERBOSE) { + nEndTime = do_get_time(); + double fDelta = ((double)(nEndTime - nStartTime)) / 1000000.0; + double fSpeed = ((double)nOriginalSize / 1048576.0) / fDelta; + fprintf(stdout, "Decompressed '%s' in %g seconds, %g Mb/s\n", + pszInFilename, fDelta, fSpeed); + } + + return 0; + } +} + +/*---------------------------------------------------------------------------*/ + +typedef struct { + FILE *f; + void *pCompareDataBuf; + size_t nCompareDataSize; +} compare_stream_t; + +void comparestream_close(lzsa_stream_t *stream) { + if (stream->obj) { + compare_stream_t *pCompareStream = (compare_stream_t *)stream->obj; + if (pCompareStream->pCompareDataBuf) { + free(pCompareStream->pCompareDataBuf); + pCompareStream->pCompareDataBuf = NULL; + } + + fclose(pCompareStream->f); + free(pCompareStream); + + stream->obj = NULL; + stream->read = NULL; + stream->write = NULL; + stream->eof = NULL; + stream->close = NULL; + } +} + +size_t comparestream_read(lzsa_stream_t *stream, void *ptr, size_t size) { + return 0; +} + +size_t comparestream_write(lzsa_stream_t *stream, void *ptr, size_t size) { + compare_stream_t *pCompareStream = (compare_stream_t *)stream->obj; + + if (!pCompareStream->pCompareDataBuf || pCompareStream->nCompareDataSize < size) { + pCompareStream->nCompareDataSize = size; + pCompareStream->pCompareDataBuf = realloc(pCompareStream->pCompareDataBuf, pCompareStream->nCompareDataSize); + if (!pCompareStream->pCompareDataBuf) + return 0; + } + + size_t nReadBytes = fread(pCompareStream->pCompareDataBuf, 1, size, pCompareStream->f); + if (nReadBytes != size) { + return 0; + } + + if (memcmp(ptr, pCompareStream->pCompareDataBuf, size)) { + return 0; + } + + return size; +} + +int comparestream_eof(lzsa_stream_t *stream) { + compare_stream_t *pCompareStream = (compare_stream_t *)stream->obj; + return feof(pCompareStream->f); +} + +int comparestream_open(lzsa_stream_t *stream, const char *pszCompareFilename, const char *pszMode) { + compare_stream_t *pCompareStream; + + pCompareStream = (compare_stream_t*)malloc(sizeof(compare_stream_t)); + if (!pCompareStream) + return -1; + + pCompareStream->pCompareDataBuf = NULL; + pCompareStream->nCompareDataSize = 0; + pCompareStream->f = (void*)fopen(pszCompareFilename, pszMode); + + if (pCompareStream->f) { + stream->obj = pCompareStream; + stream->read = comparestream_read; + stream->write = comparestream_write; + stream->eof = comparestream_eof; + stream->close = comparestream_close; + return 0; + } + else { + free(pCompareStream); + return -1; + } +} + +static int do_compare(const char *pszInFilename, const char *pszOutFilename, const char *pszDictionaryFilename, const unsigned int nOptions, int nFormatVersion) { + lzsa_stream_t inStream, compareStream; + long long nStartTime = 0LL, nEndTime = 0LL; + long long nOriginalSize = 0LL; + long long nCompressedSize = 0LL; + void *pDictionaryData = NULL; + int nDictionaryDataSize = 0; + lzsa_status_t nStatus; + int nFlags; + + if (lzsa_filestream_open(&inStream, pszInFilename, "rb") < 0) { + fprintf(stderr, "error opening compressed input file\n"); + return 100; + } + + if (comparestream_open(&compareStream, pszOutFilename, "rb") < 0) { + fprintf(stderr, "error opening original uncompressed file\n"); + inStream.close(&inStream); + return 100; + } + + nStatus = lzsa_dictionary_load(pszDictionaryFilename, &pDictionaryData, &nDictionaryDataSize); + if (nStatus) { + compareStream.close(&compareStream); + inStream.close(&inStream); + fprintf(stderr, "error reading dictionary '%s'\n", pszDictionaryFilename); + return 100; + } + + nFlags = 0; + if (nOptions & OPT_RAW) + nFlags |= LZSA_FLAG_RAW_BLOCK; + if (nOptions & OPT_RAW_BACKWARD) + nFlags |= LZSA_FLAG_RAW_BACKWARD; + + if (nOptions & OPT_VERBOSE) { + nStartTime = do_get_time(); + } + + nStatus = lzsa_decompress_stream(&inStream, &compareStream, pDictionaryData, nDictionaryDataSize, nFlags, nFormatVersion, &nOriginalSize, &nCompressedSize); + + switch (nStatus) { + case LZSA_ERROR_SRC: fprintf(stderr, "error reading '%s'\n", pszInFilename); break; + case LZSA_ERROR_DST: fprintf(stderr, "error comparing compressed file '%s' with original '%s'\n", pszInFilename, pszOutFilename); break; + case LZSA_ERROR_MEMORY: fprintf(stderr, "out of memory\n"); break; + case LZSA_ERROR_DECOMPRESSION: fprintf(stderr, "internal decompression error\n"); break; + case LZSA_ERROR_FORMAT: fprintf(stderr, "invalid magic number or format version in input file\n"); break; + case LZSA_OK: break; + default: fprintf(stderr, "unknown decompression error %d\n", nStatus); break; + } + + lzsa_dictionary_free(&pDictionaryData); + compareStream.close(&compareStream); + inStream.close(&inStream); + + if (nStatus) { + return 100; + } + else { + if (nOptions & OPT_VERBOSE) { + nEndTime = do_get_time(); + double fDelta = ((double)(nEndTime - nStartTime)) / 1000000.0; + double fSpeed = ((double)nOriginalSize / 1048576.0) / fDelta; + fprintf(stdout, "Compared '%s' in %g seconds, %g Mb/s\n", + pszInFilename, fDelta, fSpeed); + } + + return 0; + } +} + +/*---------------------------------------------------------------------------*/ + +static void generate_compressible_data(unsigned char *pBuffer, size_t nBufferSize, int nMinMatchSize, unsigned int nSeed, int nNumLiteralValues, float fMatchProbability) { + size_t nIndex = 0; + int nMatchProbability = (int)(fMatchProbability * 1023.0f); + + srand(nSeed); + + if (nIndex >= nBufferSize) return; + pBuffer[nIndex++] = rand() % nNumLiteralValues; + + while (nIndex < nBufferSize) { + if ((rand() & 1023) >= nMatchProbability) { + size_t nLiteralCount = rand() & 127; + if (nLiteralCount > (nBufferSize - nIndex)) + nLiteralCount = nBufferSize - nIndex; + + while (nLiteralCount--) + pBuffer[nIndex++] = rand() % nNumLiteralValues; + } + else { + size_t nMatchLength = nMinMatchSize + (rand() & 1023); + size_t nMatchOffset; + + if (nMatchLength > (nBufferSize - nIndex)) + nMatchLength = nBufferSize - nIndex; + if (nMatchLength > nIndex) + nMatchLength = nIndex; + + if (nMatchLength < nIndex) + nMatchOffset = rand() % (nIndex - nMatchLength); + else + nMatchOffset = 0; + + while (nMatchLength--) { + pBuffer[nIndex] = pBuffer[nIndex - nMatchOffset]; + nIndex++; + } + } + } +} + +static void xor_data(unsigned char *pBuffer, size_t nBufferSize, unsigned int nSeed, float fXorProbability) { + size_t nIndex = 0; + int nXorProbability = (int)(fXorProbability * 1023.0f); + + srand(nSeed); + + if (nIndex >= nBufferSize) return; + + while (nIndex < nBufferSize) { + if ((rand() & 1023) < nXorProbability) { + pBuffer[nIndex] ^= 0xff; + } + nIndex++; + } +} + +static int do_self_test(const unsigned int nOptions, const int nMinMatchSize, int nFormatVersion) { + unsigned char *pGeneratedData; + unsigned char *pCompressedData; + unsigned char *pTmpCompressedData; + unsigned char *pTmpDecompressedData; + size_t nGeneratedDataSize; + size_t nMaxCompressedDataSize; + unsigned int nSeed = 123; + int nFlags; + int i; + + nFlags = 0; + if (nOptions & OPT_FAVOR_RATIO) + nFlags |= LZSA_FLAG_FAVOR_RATIO; + if (nOptions & OPT_RAW) + nFlags |= LZSA_FLAG_RAW_BLOCK; + if (nOptions & OPT_RAW_BACKWARD) + nFlags |= LZSA_FLAG_RAW_BACKWARD; + + pGeneratedData = (unsigned char*)malloc(4 * BLOCK_SIZE); + if (!pGeneratedData) { + fprintf(stderr, "out of memory, %d bytes needed\n", 4 * BLOCK_SIZE); + return 100; + } + + nMaxCompressedDataSize = lzsa_get_max_compressed_size_inmem(4 * BLOCK_SIZE); + pCompressedData = (unsigned char*)malloc(nMaxCompressedDataSize); + if (!pCompressedData) { + free(pGeneratedData); + pGeneratedData = NULL; + + fprintf(stderr, "out of memory, %zd bytes needed\n", nMaxCompressedDataSize); + return 100; + } + + pTmpCompressedData = (unsigned char*)malloc(nMaxCompressedDataSize); + if (!pTmpCompressedData) { + free(pCompressedData); + pCompressedData = NULL; + free(pGeneratedData); + pGeneratedData = NULL; + + fprintf(stderr, "out of memory, %zd bytes needed\n", nMaxCompressedDataSize); + return 100; + } + + pTmpDecompressedData = (unsigned char*)malloc(4 * BLOCK_SIZE); + if (!pTmpDecompressedData) { + free(pTmpCompressedData); + pTmpCompressedData = NULL; + free(pCompressedData); + pCompressedData = NULL; + free(pGeneratedData); + pGeneratedData = NULL; + + fprintf(stderr, "out of memory, %d bytes needed\n", 4 * BLOCK_SIZE); + return 100; + } + + memset(pGeneratedData, 0, 4 * BLOCK_SIZE); + memset(pCompressedData, 0, nMaxCompressedDataSize); + memset(pTmpCompressedData, 0, nMaxCompressedDataSize); + + /* Test compressing with a too small buffer to do anything, expect to fail cleanly */ + for (i = 0; i < 12; i++) { + generate_compressible_data(pGeneratedData, i, nMinMatchSize, nSeed, 256, 0.5f); + lzsa_compress_inmem(pGeneratedData, pCompressedData, i, i, nFlags, nMinMatchSize, nFormatVersion); + } + + size_t nDataSizeStep = 128; + float fProbabilitySizeStep = 0.0005f; + + for (nGeneratedDataSize = 1024; nGeneratedDataSize <= ((size_t)((nOptions & OPT_RAW) ? BLOCK_SIZE : (4 * BLOCK_SIZE))); nGeneratedDataSize += nDataSizeStep) { + float fMatchProbability; + + fprintf(stdout, "size %zd", nGeneratedDataSize); + for (fMatchProbability = 0; fMatchProbability <= 0.995f; fMatchProbability += fProbabilitySizeStep) { + int nNumLiteralValues[12] = { 1, 2, 3, 15, 30, 56, 96, 137, 178, 191, 255, 256 }; + float fXorProbability; + + fputc('.', stdout); + fflush(stdout); + + for (i = 0; i < 12; i++) { + /* Generate data to compress */ + generate_compressible_data(pGeneratedData, nGeneratedDataSize, nMinMatchSize, nSeed, nNumLiteralValues[i], fMatchProbability); + + /* Try to compress it, expected to succeed */ + size_t nActualCompressedSize = lzsa_compress_inmem(pGeneratedData, pCompressedData, nGeneratedDataSize, lzsa_get_max_compressed_size_inmem(nGeneratedDataSize), + nFlags, nMinMatchSize, nFormatVersion); + if (nActualCompressedSize == -1 || (int)nActualCompressedSize < (lzsa_get_header_size() + lzsa_get_frame_size() + lzsa_get_frame_size() /* footer */)) { + free(pTmpDecompressedData); + pTmpDecompressedData = NULL; + free(pTmpCompressedData); + pTmpCompressedData = NULL; + free(pCompressedData); + pCompressedData = NULL; + free(pGeneratedData); + pGeneratedData = NULL; + + fprintf(stderr, "\nself-test: error compressing size %zd, seed %d, match probability %f, literals range %d\n", nGeneratedDataSize, nSeed, fMatchProbability, nNumLiteralValues[i]); + return 100; + } + + /* Try to decompress it, expected to succeed */ + size_t nActualDecompressedSize; + int nDecFormatVersion = nFormatVersion; + nActualDecompressedSize = lzsa_decompress_inmem(pCompressedData, pTmpDecompressedData, nActualCompressedSize, nGeneratedDataSize, nFlags, &nDecFormatVersion); + if (nActualDecompressedSize == -1) { + free(pTmpDecompressedData); + pTmpDecompressedData = NULL; + free(pTmpCompressedData); + pTmpCompressedData = NULL; + free(pCompressedData); + pCompressedData = NULL; + free(pGeneratedData); + pGeneratedData = NULL; + + fprintf(stderr, "\nself-test: error decompressing size %zd, seed %d, match probability %f, literals range %d\n", nGeneratedDataSize, nSeed, fMatchProbability, nNumLiteralValues[i]); + return 100; + } + + if (memcmp(pGeneratedData, pTmpDecompressedData, nGeneratedDataSize)) { + free(pTmpDecompressedData); + pTmpDecompressedData = NULL; + free(pTmpCompressedData); + pTmpCompressedData = NULL; + free(pCompressedData); + pCompressedData = NULL; + free(pGeneratedData); + pGeneratedData = NULL; + + fprintf(stderr, "\nself-test: error comparing decompressed and original data, size %zd, seed %d, match probability %f, literals range %d\n", nGeneratedDataSize, nSeed, fMatchProbability, nNumLiteralValues[i]); + return 100; + } + + /* Try to decompress corrupted data, expected to fail cleanly, without crashing or corrupting memory outside the output buffer */ + for (fXorProbability = 0.05f; fXorProbability <= 0.5f; fXorProbability += 0.05f) { + memcpy(pTmpCompressedData, pCompressedData, nActualCompressedSize); + xor_data(pTmpCompressedData + lzsa_get_header_size() + lzsa_get_frame_size(), nActualCompressedSize - lzsa_get_header_size() - lzsa_get_frame_size() - lzsa_get_frame_size() /* footer */, nSeed, fXorProbability); + nDecFormatVersion = nFormatVersion; + lzsa_decompress_inmem(pTmpCompressedData, pGeneratedData, nActualCompressedSize, nGeneratedDataSize, nFlags, &nDecFormatVersion); + } + } + + nSeed++; + } + + fputc(10, stdout); + fflush(stdout); + + nDataSizeStep <<= 1; + if (nDataSizeStep > (128 * 4096)) + nDataSizeStep = 128 * 4096; + fProbabilitySizeStep *= 1.25; + if (fProbabilitySizeStep > (0.0005f * 4096)) + fProbabilitySizeStep = 0.0005f * 4096; + } + + free(pTmpDecompressedData); + pTmpDecompressedData = NULL; + + free(pTmpCompressedData); + pTmpCompressedData = NULL; + + free(pCompressedData); + pCompressedData = NULL; + + free(pGeneratedData); + pGeneratedData = NULL; + + fprintf(stdout, "All tests passed.\n"); + return 0; +} + +/*---------------------------------------------------------------------------*/ + +static int do_compr_benchmark(const char *pszInFilename, const char *pszOutFilename, const char *pszDictionaryFilename, const unsigned int nOptions, const int nMinMatchSize, int nFormatVersion) { + size_t nFileSize, nMaxCompressedSize; + unsigned char *pFileData; + unsigned char *pCompressedData; + int nFlags; + int i; + + nFlags = 0; + if (nOptions & OPT_FAVOR_RATIO) + nFlags |= LZSA_FLAG_FAVOR_RATIO; + if (nOptions & OPT_RAW) + nFlags |= LZSA_FLAG_RAW_BLOCK; + if (nOptions & OPT_RAW_BACKWARD) + nFlags |= LZSA_FLAG_RAW_BACKWARD; + + if (pszDictionaryFilename) { + fprintf(stderr, "in-memory benchmarking does not support dictionaries\n"); + return 100; + } + + /* Read the whole original file in memory */ + + FILE *f_in = fopen(pszInFilename, "rb"); + if (!f_in) { + fprintf(stderr, "error opening '%s' for reading\n", pszInFilename); + return 100; + } + + fseek(f_in, 0, SEEK_END); + nFileSize = (size_t)ftell(f_in); + fseek(f_in, 0, SEEK_SET); + + pFileData = (unsigned char*)malloc(nFileSize); + if (!pFileData) { + fclose(f_in); + fprintf(stderr, "out of memory for reading '%s', %zd bytes needed\n", pszInFilename, nFileSize); + return 100; + } + + if (fread(pFileData, 1, nFileSize, f_in) != nFileSize) { + free(pFileData); + fclose(f_in); + fprintf(stderr, "I/O error while reading '%s'\n", pszInFilename); + return 100; + } + + fclose(f_in); + + /* Allocate max compressed size */ + + nMaxCompressedSize = lzsa_get_max_compressed_size_inmem(nFileSize); + + pCompressedData = (unsigned char*)malloc(nMaxCompressedSize + 2048); + if (!pCompressedData) { + free(pFileData); + fprintf(stderr, "out of memory for compressing '%s', %zd bytes needed\n", pszInFilename, nMaxCompressedSize); + return 100; + } + + memset(pCompressedData + 1024, 0, nMaxCompressedSize); + + long long nBestCompTime = -1; + + size_t nActualCompressedSize = 0; + size_t nRightGuardPos = nMaxCompressedSize; + + for (i = 0; i < 5; i++) { + unsigned char nGuard = 0x33 + i; + int j; + + /* Write guard bytes around the output buffer, to help check for writes outside of it by the compressor */ + memset(pCompressedData, nGuard, 1024); + memset(pCompressedData + 1024 + nRightGuardPos, nGuard, 1024); + + long long t0 = do_get_time(); + nActualCompressedSize = lzsa_compress_inmem(pFileData, pCompressedData + 1024, nFileSize, nRightGuardPos, nFlags, nMinMatchSize, nFormatVersion); + long long t1 = do_get_time(); + if (nActualCompressedSize == -1) { + free(pCompressedData); + free(pFileData); + fprintf(stderr, "compression error\n"); + return 100; + } + + long long nCurDecTime = t1 - t0; + if (nBestCompTime == -1 || nBestCompTime > nCurDecTime) + nBestCompTime = nCurDecTime; + + /* Check guard bytes before the output buffer */ + for (j = 0; j < 1024; j++) { + if (pCompressedData[j] != nGuard) { + free(pCompressedData); + free(pFileData); + fprintf(stderr, "error, wrote outside of output buffer at %d!\n", j - 1024); + return 100; + } + } + + /* Check guard bytes after the output buffer */ + for (j = 0; j < 1024; j++) { + if (pCompressedData[1024 + nRightGuardPos + j] != nGuard) { + free(pCompressedData); + free(pFileData); + fprintf(stderr, "error, wrote outside of output buffer at %d!\n", j); + return 100; + } + } + + nRightGuardPos = nActualCompressedSize; + } + + if (pszOutFilename) { + FILE *f_out; + + /* Write whole compressed file out */ + + f_out = fopen(pszOutFilename, "wb"); + if (f_out) { + fwrite(pCompressedData + 1024, 1, nActualCompressedSize, f_out); + fclose(f_out); + } + } + + free(pCompressedData); + free(pFileData); + + fprintf(stdout, "compressed size: %zd bytes\n", nActualCompressedSize); + fprintf(stdout, "compression time: %lld microseconds (%g Mb/s)\n", nBestCompTime, ((double)nActualCompressedSize / 1024.0) / ((double)nBestCompTime / 1000.0)); + + return 0; +} + +/*---------------------------------------------------------------------------*/ + +static int do_dec_benchmark(const char *pszInFilename, const char *pszOutFilename, const char *pszDictionaryFilename, const unsigned int nOptions, int nFormatVersion) { + size_t nFileSize, nMaxDecompressedSize; + unsigned char *pFileData; + unsigned char *pDecompressedData; + int nFlags; + int i; + + nFlags = 0; + if (nOptions & OPT_RAW) + nFlags |= LZSA_FLAG_RAW_BLOCK; + if (nOptions & OPT_RAW_BACKWARD) + nFlags |= LZSA_FLAG_RAW_BACKWARD; + + if (pszDictionaryFilename) { + fprintf(stderr, "in-memory benchmarking does not support dictionaries\n"); + return 100; + } + + /* Read the whole compressed file in memory */ + + FILE *f_in = fopen(pszInFilename, "rb"); + if (!f_in) { + fprintf(stderr, "error opening '%s' for reading\n", pszInFilename); + return 100; + } + + fseek(f_in, 0, SEEK_END); + nFileSize = (size_t)ftell(f_in); + fseek(f_in, 0, SEEK_SET); + + pFileData = (unsigned char*)malloc(nFileSize); + if (!pFileData) { + fclose(f_in); + fprintf(stderr, "out of memory for reading '%s', %zd bytes needed\n", pszInFilename, nFileSize); + return 100; + } + + if (fread(pFileData, 1, nFileSize, f_in) != nFileSize) { + free(pFileData); + fclose(f_in); + fprintf(stderr, "I/O error while reading '%s'\n", pszInFilename); + return 100; + } + + fclose(f_in); + + /* Allocate max decompressed size */ + + if (nOptions & OPT_RAW) + nMaxDecompressedSize = 65536; + else + nMaxDecompressedSize = lzsa_get_max_decompressed_size_inmem(pFileData, nFileSize); + if (nMaxDecompressedSize == -1) { + free(pFileData); + fprintf(stderr, "invalid compressed format for file '%s'\n", pszInFilename); + return 100; + } + + pDecompressedData = (unsigned char*)malloc(nMaxDecompressedSize); + if (!pDecompressedData) { + free(pFileData); + fprintf(stderr, "out of memory for decompressing '%s', %zd bytes needed\n", pszInFilename, nMaxDecompressedSize); + return 100; + } + + memset(pDecompressedData, 0, nMaxDecompressedSize); + + long long nBestDecTime = -1; + + size_t nActualDecompressedSize = 0; + for (i = 0; i < 50; i++) { + long long t0 = do_get_time(); + nActualDecompressedSize = lzsa_decompress_inmem(pFileData, pDecompressedData, nFileSize, nMaxDecompressedSize, nFlags, &nFormatVersion); + long long t1 = do_get_time(); + if (nActualDecompressedSize == -1) { + free(pDecompressedData); + free(pFileData); + fprintf(stderr, "decompression error\n"); + return 100; + } + + long long nCurDecTime = t1 - t0; + if (nBestDecTime == -1 || nBestDecTime > nCurDecTime) + nBestDecTime = nCurDecTime; + } + + if (pszOutFilename) { + FILE *f_out; + + /* Write whole decompressed file out */ + + f_out = fopen(pszOutFilename, "wb"); + if (f_out) { + fwrite(pDecompressedData, 1, nActualDecompressedSize, f_out); + fclose(f_out); + } + } + + free(pDecompressedData); + free(pFileData); + + fprintf(stdout, "format: LZSA%d\n", nFormatVersion); + fprintf(stdout, "decompressed size: %zd bytes\n", nActualDecompressedSize); + fprintf(stdout, "decompression time: %lld microseconds (%g Mb/s)\n", nBestDecTime, ((double)nActualDecompressedSize / 1024.0) / ((double)nBestDecTime / 1000.0)); + + return 0; +} + +/*---------------------------------------------------------------------------*/ + +int main(int argc, char **argv) { + int i; + const char *pszInFilename = NULL; + const char *pszOutFilename = NULL; + const char *pszDictionaryFilename = NULL; + bool bArgsError = false; + bool bCommandDefined = false; + bool bVerifyCompression = false; + bool bMinMatchDefined = false; + bool bFormatVersionDefined = false; + char cCommand = 'z'; + int nMinMatchSize = 0; + unsigned int nOptions = OPT_FAVOR_RATIO; + int nFormatVersion = 1; + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-d")) { + if (!bCommandDefined) { + bCommandDefined = true; + cCommand = 'd'; + } + else + bArgsError = true; + } + else if (!strcmp(argv[i], "-z")) { + if (!bCommandDefined) { + bCommandDefined = true; + cCommand = 'z'; + } + else + bArgsError = true; + } + else if (!strcmp(argv[i], "-c")) { + if (!bVerifyCompression) { + bVerifyCompression = true; + } + else + bArgsError = true; + } + else if (!strcmp(argv[i], "-cbench")) { + if (!bCommandDefined) { + bCommandDefined = true; + cCommand = 'B'; + } + else + bArgsError = true; + } + else if (!strcmp(argv[i], "-dbench")) { + if (!bCommandDefined) { + bCommandDefined = true; + cCommand = 'b'; + } + else + bArgsError = true; + } + else if (!strcmp(argv[i], "-test")) { + if (!bCommandDefined) { + bCommandDefined = true; + cCommand = 't'; + } + else + bArgsError = true; + } + else if (!strcmp(argv[i], "-D")) { + if (!pszDictionaryFilename && (i + 1) < argc) { + pszDictionaryFilename = argv[i + 1]; + i++; + } + else + bArgsError = true; + } + else if (!strncmp(argv[i], "-D", 2)) { + if (!pszDictionaryFilename) { + pszDictionaryFilename = argv[i] + 2; + } + else + bArgsError = true; + } + else if (!strcmp(argv[i], "-m")) { + if (!bMinMatchDefined && (i + 1) < argc) { + char *pEnd = NULL; + nMinMatchSize = (int)strtol(argv[i + 1], &pEnd, 10); + if (pEnd && pEnd != argv[i + 1] && (nMinMatchSize >= 2 && nMinMatchSize <= 5)) { + i++; + bMinMatchDefined = true; + nOptions &= (~OPT_FAVOR_RATIO); + } + else { + bArgsError = true; + } + } + else + bArgsError = true; + } + else if (!strncmp(argv[i], "-m", 2)) { + if (!bMinMatchDefined) { + char *pEnd = NULL; + nMinMatchSize = (int)strtol(argv[i] + 2, &pEnd, 10); + if (pEnd && pEnd != (argv[i]+2) && (nMinMatchSize >= 2 && nMinMatchSize <= 5)) { + bMinMatchDefined = true; + nOptions &= (~OPT_FAVOR_RATIO); + } + else { + bArgsError = true; + } + } + else + bArgsError = true; + } + else if (!strcmp(argv[i], "--prefer-ratio")) { + if (!bMinMatchDefined) { + nMinMatchSize = 0; + bMinMatchDefined = true; + } + else + bArgsError = true; + } + else if (!strcmp(argv[i], "--prefer-speed")) { + if (!bMinMatchDefined) { + nMinMatchSize = 3; + nOptions &= (~OPT_FAVOR_RATIO); + bMinMatchDefined = true; + } + else + bArgsError = true; + } + else if (!strcmp(argv[i], "-f")) { + if (!bFormatVersionDefined && (i + 1) < argc) { + char *pEnd = NULL; + nFormatVersion = (int)strtol(argv[i + 1], &pEnd, 10); + if (pEnd && pEnd != argv[i + 1] && (nFormatVersion >= 1 && nFormatVersion <= 2)) { + i++; + bFormatVersionDefined = true; + } + else { + bArgsError = true; + } + } + else + bArgsError = true; + } + else if (!strncmp(argv[i], "-f", 2)) { + if (!bFormatVersionDefined) { + char *pEnd = NULL; + nFormatVersion = (int)strtol(argv[i] + 2, &pEnd, 10); + if (pEnd && pEnd != (argv[i] + 2) && (nFormatVersion >= 1 && nFormatVersion <= 2)) { + bFormatVersionDefined = true; + } + else { + bArgsError = true; + } + } + else + bArgsError = true; + } + else if (!strcmp(argv[i], "-v")) { + if ((nOptions & OPT_VERBOSE) == 0) { + nOptions |= OPT_VERBOSE; + } + else + bArgsError = true; + } + else if (!strcmp(argv[i], "-r")) { + if ((nOptions & OPT_RAW) == 0) { + nOptions |= OPT_RAW; + } + else + bArgsError = true; + } + else if (!strcmp(argv[i], "-b")) { + if ((nOptions & OPT_RAW_BACKWARD) == 0) { + nOptions |= OPT_RAW_BACKWARD; + } + else + bArgsError = true; + } + else if (!strcmp(argv[i], "-stats")) { + if ((nOptions & OPT_STATS) == 0) { + nOptions |= OPT_STATS; + } + else + bArgsError = true; + } + else { + if (!pszInFilename) + pszInFilename = argv[i]; + else { + if (!pszOutFilename) + pszOutFilename = argv[i]; + else + bArgsError = true; + } + } + } + + if (!bArgsError && (nOptions & OPT_RAW_BACKWARD) && !(nOptions & OPT_RAW)) { + fprintf(stderr, "error: -b (compress backwards) requires -r (raw block format)\n"); + return 100; + } + + if (!bArgsError && cCommand == 't') { + return do_self_test(nOptions, nMinMatchSize, nFormatVersion); + } + + if (bArgsError || !pszInFilename || !pszOutFilename) { + fprintf(stderr, "lzsa command-line tool v" TOOL_VERSION " by Emmanuel Marty and spke\n"); + fprintf(stderr, "usage: %s [-c] [-d] [-v] [-r] \n", argv[0]); + fprintf(stderr, " -c: check resulting stream after compressing\n"); + fprintf(stderr, " -d: decompress (default: compress)\n"); + fprintf(stderr, " -cbench: benchmark in-memory compression\n"); + fprintf(stderr, " -dbench: benchmark in-memory decompression\n"); + fprintf(stderr, " -test: run automated self-tests\n"); + fprintf(stderr, " -stats: show compressed data stats\n"); + fprintf(stderr, " -v: be verbose\n"); + fprintf(stderr, " -f : LZSA compression format (1-2)\n"); + fprintf(stderr, " -r: raw block format (max. 64 Kb files)\n"); + fprintf(stderr, " -b: compress backward (requires -r and a backward decompressor)\n"); + fprintf(stderr, " -D : use dictionary file\n"); + fprintf(stderr, " -m : minimum match size (3-5) (default: 3)\n"); + fprintf(stderr, " --prefer-ratio: favor compression ratio (default)\n"); + fprintf(stderr, " --prefer-speed: favor decompression speed (same as -m3)\n"); + return 100; + } + + do_init_time(); + + if (cCommand == 'z') { + int nResult = do_compress(pszInFilename, pszOutFilename, pszDictionaryFilename, nOptions, nMinMatchSize, nFormatVersion); + if (nResult == 0 && bVerifyCompression) { + return do_compare(pszOutFilename, pszInFilename, pszDictionaryFilename, nOptions, nFormatVersion); + } else { + return nResult; + } + } + else if (cCommand == 'd') { + return do_decompress(pszInFilename, pszOutFilename, pszDictionaryFilename, nOptions, nFormatVersion); + } + else if (cCommand == 'B') { + return do_compr_benchmark(pszInFilename, pszOutFilename, pszDictionaryFilename, nOptions, nMinMatchSize, nFormatVersion); + } + else if (cCommand == 'b') { + return do_dec_benchmark(pszInFilename, pszOutFilename, pszDictionaryFilename, nOptions, nFormatVersion); + } + else { + return 100; + } +} diff --git a/Tools/unix/lzsa/src/matchfinder.c b/Tools/unix/lzsa/src/matchfinder.c new file mode 100644 index 00000000..fbdc5ca3 --- /dev/null +++ b/Tools/unix/lzsa/src/matchfinder.c @@ -0,0 +1,361 @@ +/* + * matchfinder.c - LZ match finder implementation + * + * The following copying information applies to this specific source code file: + * + * Written in 2019 by Emmanuel Marty + * Portions written in 2014-2015 by Eric Biggers + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide via the Creative Commons Zero 1.0 Universal Public Domain + * Dedication (the "CC0"). + * + * This software is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the CC0 for more details. + * + * You should have received a copy of the CC0 along with this software; if not + * see . + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#include +#include +#include "matchfinder.h" +#include "format.h" +#include "lib.h" + +/** + * Hash index into TAG_BITS + * + * @param nIndex index value + * + * @return hash + */ +static inline int lzsa_get_index_tag(unsigned int nIndex) { + return (int)(((unsigned long long)nIndex * 11400714819323198485ULL) >> (64ULL - TAG_BITS)); +} + +/** + * Parse input data, build suffix array and overlaid data structures to speed up match finding + * + * @param pCompressor compression context + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param nInWindowSize total input size in bytes (previously compressed bytes + bytes to compress) + * + * @return 0 for success, non-zero for failure + */ +int lzsa_build_suffix_array(lzsa_compressor *pCompressor, const unsigned char *pInWindow, const int nInWindowSize) { + unsigned int *intervals = pCompressor->intervals; + + /* Build suffix array from input data */ + if (divsufsort_build_array(&pCompressor->divsufsort_context, pInWindow, (saidx_t*)intervals, nInWindowSize) != 0) { + return 100; + } + + int *PLCP = (int*)pCompressor->pos_data; /* Use temporarily */ + int *Phi = PLCP; + int nCurLen = 0; + int i; + + /* Compute the permuted LCP first (Kärkkäinen method) */ + Phi[intervals[0]] = -1; + for (i = 1; i < nInWindowSize; i++) + Phi[intervals[i]] = intervals[i - 1]; + for (i = 0; i < nInWindowSize; i++) { + if (Phi[i] == -1) { + PLCP[i] = 0; + continue; + } + int nMaxLen = (i > Phi[i]) ? (nInWindowSize - i) : (nInWindowSize - Phi[i]); + while (nCurLen < nMaxLen && pInWindow[i + nCurLen] == pInWindow[Phi[i] + nCurLen]) nCurLen++; + PLCP[i] = nCurLen; + if (nCurLen > 0) + nCurLen--; + } + + /* Rotate permuted LCP into the LCP. This has better cache locality than the direct Kasai LCP method. This also + * saves us from having to build the inverse suffix array index, as the LCP is calculated without it using this method, + * and the interval builder below doesn't need it either. */ + intervals[0] &= POS_MASK; + int nMinMatchSize = pCompressor->min_match_size; + + if (pCompressor->format_version >= 2) { + for (i = 1; i < nInWindowSize; i++) { + int nIndex = (int)(intervals[i] & POS_MASK); + int nLen = PLCP[nIndex]; + if (nLen < nMinMatchSize) + nLen = 0; + if (nLen > LCP_MAX) + nLen = LCP_MAX; + int nTaggedLen = 0; + if (nLen) + nTaggedLen = (nLen << TAG_BITS) | (lzsa_get_index_tag((unsigned int)nIndex) & ((1 << TAG_BITS) - 1)); + intervals[i] = ((unsigned int)nIndex) | (((unsigned int)nTaggedLen) << LCP_SHIFT); + } + } + else { + for (i = 1; i < nInWindowSize; i++) { + int nIndex = (int)(intervals[i] & POS_MASK); + int nLen = PLCP[nIndex]; + if (nLen < nMinMatchSize) + nLen = 0; + if (nLen > LCP_AND_TAG_MAX) + nLen = LCP_AND_TAG_MAX; + intervals[i] = ((unsigned int)nIndex) | (((unsigned int)nLen) << LCP_SHIFT); + } + } + + /** + * Build intervals for finding matches + * + * Methodology and code fragment taken from wimlib (CC0 license): + * https://wimlib.net/git/?p=wimlib;a=blob_plain;f=src/lcpit_matchfinder.c;h=a2d6a1e0cd95200d1f3a5464d8359d5736b14cbe;hb=HEAD + */ + unsigned int * const SA_and_LCP = intervals; + unsigned int *pos_data = pCompressor->pos_data; + unsigned int next_interval_idx; + unsigned int *top = pCompressor->open_intervals; + unsigned int prev_pos = SA_and_LCP[0] & POS_MASK; + + *top = 0; + intervals[0] = 0; + next_interval_idx = 1; + + for (int r = 1; r < nInWindowSize; r++) { + const unsigned int next_pos = SA_and_LCP[r] & POS_MASK; + const unsigned int next_lcp = SA_and_LCP[r] & LCP_MASK; + const unsigned int top_lcp = *top & LCP_MASK; + + if (next_lcp == top_lcp) { + /* Continuing the deepest open interval */ + pos_data[prev_pos] = *top; + } + else if (next_lcp > top_lcp) { + /* Opening a new interval */ + *++top = next_lcp | next_interval_idx++; + pos_data[prev_pos] = *top; + } + else { + /* Closing the deepest open interval */ + pos_data[prev_pos] = *top; + for (;;) { + const unsigned int closed_interval_idx = *top-- & POS_MASK; + const unsigned int superinterval_lcp = *top & LCP_MASK; + + if (next_lcp == superinterval_lcp) { + /* Continuing the superinterval */ + intervals[closed_interval_idx] = *top; + break; + } + else if (next_lcp > superinterval_lcp) { + /* Creating a new interval that is a + * superinterval of the one being + * closed, but still a subinterval of + * its superinterval */ + *++top = next_lcp | next_interval_idx++; + intervals[closed_interval_idx] = *top; + break; + } + else { + /* Also closing the superinterval */ + intervals[closed_interval_idx] = *top; + } + } + } + prev_pos = next_pos; + } + + /* Close any still-open intervals. */ + pos_data[prev_pos] = *top; + for (; top > pCompressor->open_intervals; top--) + intervals[*top & POS_MASK] = *(top - 1); + + /* Success */ + return 0; +} + +/** + * Find matches at the specified offset in the input window + * + * @param pCompressor compression context + * @param nOffset offset to find matches at, in the input window + * @param pMatches pointer to returned matches + * @param nMaxMatches maximum number of matches to return (0 for none) + * @param nInWindowSize total input size in bytes (previously compressed bytes + bytes to compress) + * + * @return number of matches + */ +int lzsa_find_matches_at(lzsa_compressor *pCompressor, const int nOffset, lzsa_match *pMatches, const int nMaxMatches, const int nInWindowSize) { + unsigned int *intervals = pCompressor->intervals; + unsigned int *pos_data = pCompressor->pos_data; + unsigned int ref; + unsigned int super_ref; + unsigned int match_pos; + lzsa_match *matchptr; + int nPrevOffset = 0; + + /** + * Find matches using intervals + * + * Taken from wimlib (CC0 license): + * https://wimlib.net/git/?p=wimlib;a=blob_plain;f=src/lcpit_matchfinder.c;h=a2d6a1e0cd95200d1f3a5464d8359d5736b14cbe;hb=HEAD + */ + + /* Get the deepest lcp-interval containing the current suffix. */ + ref = pos_data[nOffset]; + + pos_data[nOffset] = 0; + + /* Ascend until we reach a visited interval, the root, or a child of the + * root. Link unvisited intervals to the current suffix as we go. */ + while ((super_ref = intervals[ref & POS_MASK]) & LCP_MASK) { + intervals[ref & POS_MASK] = nOffset | VISITED_FLAG; + ref = super_ref; + } + + if (super_ref == 0) { + /* In this case, the current interval may be any of: + * (1) the root; + * (2) an unvisited child of the root */ + + if (ref != 0) /* Not the root? */ + intervals[ref & POS_MASK] = nOffset | VISITED_FLAG; + return 0; + } + + /* Ascend indirectly via pos_data[] links. */ + match_pos = super_ref & EXCL_VISITED_MASK; + matchptr = pMatches; + + if (pCompressor->format_version >= 2 && nInWindowSize < 65536) { + if ((matchptr - pMatches) < nMaxMatches) { + int nMatchOffset = (int)(nOffset - match_pos); + + if (nMatchOffset <= MAX_OFFSET) { + matchptr->length = (unsigned short)(ref >> (LCP_SHIFT + TAG_BITS)); + matchptr->offset = (unsigned short)nMatchOffset; + matchptr++; + + nPrevOffset = nMatchOffset; + } + } + } + + for (;;) { + if ((super_ref = pos_data[match_pos]) > ref) { + match_pos = intervals[super_ref & POS_MASK] & EXCL_VISITED_MASK; + + if (pCompressor->format_version >= 2 && nInWindowSize < 65536) { + if ((matchptr - pMatches) < nMaxMatches) { + int nMatchOffset = (int)(nOffset - match_pos); + + if (nMatchOffset <= MAX_OFFSET && nMatchOffset != nPrevOffset) { + matchptr->length = ((unsigned short)(ref >> (LCP_SHIFT + TAG_BITS))) | 0x8000; + matchptr->offset = (unsigned short)nMatchOffset; + matchptr++; + + nPrevOffset = nMatchOffset; + } + } + } + } + + while ((super_ref = pos_data[match_pos]) > ref) + match_pos = intervals[super_ref & POS_MASK] & EXCL_VISITED_MASK; + intervals[ref & POS_MASK] = nOffset | VISITED_FLAG; + pos_data[match_pos] = ref; + + if ((matchptr - pMatches) < nMaxMatches) { + int nMatchOffset = (int)(nOffset - match_pos); + + if (nMatchOffset <= MAX_OFFSET && nMatchOffset != nPrevOffset) { + if (pCompressor->format_version >= 2) { + matchptr->length = (unsigned short)(ref >> (LCP_SHIFT + TAG_BITS)); + } + else { + matchptr->length = (unsigned short)(ref >> LCP_SHIFT); + } + matchptr->offset = (unsigned short)nMatchOffset; + matchptr++; + } + } + + if (super_ref == 0) + break; + ref = super_ref; + match_pos = intervals[ref & POS_MASK] & EXCL_VISITED_MASK; + + if (pCompressor->format_version >= 2 && nInWindowSize < 65536) { + if ((matchptr - pMatches) < nMaxMatches) { + int nMatchOffset = (int)(nOffset - match_pos); + + if (nMatchOffset <= MAX_OFFSET && nMatchOffset != nPrevOffset) { + matchptr->length = ((unsigned short)(ref >> (LCP_SHIFT + TAG_BITS))) | 0x8000; + matchptr->offset = (unsigned short)nMatchOffset; + + if ((matchptr->length & 0x7fff) > 2) { + matchptr++; + + nPrevOffset = nMatchOffset; + } + } + } + } + } + + return (int)(matchptr - pMatches); +} + +/** + * Skip previously compressed bytes + * + * @param pCompressor compression context + * @param nStartOffset current offset in input window (typically 0) + * @param nEndOffset offset to skip to in input window (typically the number of previously compressed bytes) + */ +void lzsa_skip_matches(lzsa_compressor *pCompressor, const int nStartOffset, const int nEndOffset) { + lzsa_match match; + int i; + + /* Skipping still requires scanning for matches, as this also performs a lazy update of the intervals. However, + * we don't store the matches. */ + for (i = nStartOffset; i < nEndOffset; i++) { + lzsa_find_matches_at(pCompressor, i, &match, 0, 0); + } +} + +/** + * Find all matches for the data to be compressed + * + * @param pCompressor compression context + * @param nMatchesPerOffset maximum number of matches to store for each offset + * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) + * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes + */ +void lzsa_find_all_matches(lzsa_compressor *pCompressor, const int nMatchesPerOffset, const int nStartOffset, const int nEndOffset) { + lzsa_match *pMatch = pCompressor->match; + int i; + + for (i = nStartOffset; i < nEndOffset; i++) { + int nMatches = lzsa_find_matches_at(pCompressor, i, pMatch, nMatchesPerOffset, nEndOffset - nStartOffset); + + while (nMatches < nMatchesPerOffset) { + pMatch[nMatches].length = 0; + pMatch[nMatches].offset = 0; + nMatches++; + } + + pMatch += nMatchesPerOffset; + } +} diff --git a/Tools/unix/lzsa/src/matchfinder.h b/Tools/unix/lzsa/src/matchfinder.h new file mode 100644 index 00000000..cc3b5abd --- /dev/null +++ b/Tools/unix/lzsa/src/matchfinder.h @@ -0,0 +1,91 @@ +/* + * matchfinder.h - LZ match finder definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _MATCHFINDER_H +#define _MATCHFINDER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declarations */ +typedef struct _lzsa_match lzsa_match; +typedef struct _lzsa_compressor lzsa_compressor; + +/** + * Parse input data, build suffix array and overlaid data structures to speed up match finding + * + * @param pCompressor compression context + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param nInWindowSize total input size in bytes (previously compressed bytes + bytes to compress) + * + * @return 0 for success, non-zero for failure + */ +int lzsa_build_suffix_array(lzsa_compressor *pCompressor, const unsigned char *pInWindow, const int nInWindowSize); + +/** + * Find matches at the specified offset in the input window + * + * @param pCompressor compression context + * @param nOffset offset to find matches at, in the input window + * @param pMatches pointer to returned matches + * @param nMaxMatches maximum number of matches to return (0 for none) + * @param nInWindowSize total input size in bytes (previously compressed bytes + bytes to compress) + * + * @return number of matches + */ +int lzsa_find_matches_at(lzsa_compressor *pCompressor, const int nOffset, lzsa_match *pMatches, const int nMaxMatches, const int nInWindowSize); + +/** + * Skip previously compressed bytes + * + * @param pCompressor compression context + * @param nStartOffset current offset in input window (typically 0) + * @param nEndOffset offset to skip to in input window (typically the number of previously compressed bytes) + */ +void lzsa_skip_matches(lzsa_compressor *pCompressor, const int nStartOffset, const int nEndOffset); + +/** + * Find all matches for the data to be compressed + * + * @param pCompressor compression context + * @param nMatchesPerOffset maximum number of matches to store for each offset + * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) + * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes + */ +void lzsa_find_all_matches(lzsa_compressor *pCompressor, const int nMatchesPerOffset, const int nStartOffset, const int nEndOffset); + +#ifdef __cplusplus +} +#endif + +#endif /* _MATCHFINDER_H */ diff --git a/Tools/unix/lzsa/src/shrink_block_v1.c b/Tools/unix/lzsa/src/shrink_block_v1.c new file mode 100644 index 00000000..c30e4a9f --- /dev/null +++ b/Tools/unix/lzsa/src/shrink_block_v1.c @@ -0,0 +1,702 @@ +/* + * shrink_block_v1.c - LZSA1 block compressor implementation + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#include +#include +#include "lib.h" +#include "shrink_block_v1.h" +#include "format.h" + +/** + * Get the number of extra bits required to represent a literals length + * + * @param nLength literals length + * + * @return number of extra bits required + */ +static inline int lzsa_get_literals_varlen_size_v1(const int nLength) { + if (nLength < LITERALS_RUN_LEN_V1) { + return 0; + } + else { + if (nLength < 256) + return 8; + else { + if (nLength < 512) + return 16; + else + return 24; + } + } +} + +/** + * Write extra literals length bytes to output (compressed) buffer. The caller must first check that there is enough + * room to write the bytes. + * + * @param pOutData pointer to output buffer + * @param nOutOffset current write index into output buffer + * @param nLength literals length + */ +static inline int lzsa_write_literals_varlen_v1(unsigned char *pOutData, int nOutOffset, int nLength) { + if (nLength >= LITERALS_RUN_LEN_V1) { + if (nLength < 256) + pOutData[nOutOffset++] = nLength - LITERALS_RUN_LEN_V1; + else { + if (nLength < 512) { + pOutData[nOutOffset++] = 250; + pOutData[nOutOffset++] = nLength - 256; + } + else { + pOutData[nOutOffset++] = 249; + pOutData[nOutOffset++] = nLength & 0xff; + pOutData[nOutOffset++] = (nLength >> 8) & 0xff; + } + } + } + + return nOutOffset; +} + +/** + * Get the number of extra bits required to represent an encoded match length + * + * @param nLength encoded match length (actual match length - MIN_MATCH_SIZE_V1) + * + * @return number of extra bits required + */ +static inline int lzsa_get_match_varlen_size_v1(const int nLength) { + if (nLength < MATCH_RUN_LEN_V1) { + return 0; + } + else { + if ((nLength + MIN_MATCH_SIZE_V1) < 256) + return 8; + else { + if ((nLength + MIN_MATCH_SIZE_V1) < 512) + return 16; + else + return 24; + } + } +} + +/** + * Write extra encoded match length bytes to output (compressed) buffer. The caller must first check that there is enough + * room to write the bytes. + * + * @param pOutData pointer to output buffer + * @param nOutOffset current write index into output buffer + * @param nLength encoded match length (actual match length - MIN_MATCH_SIZE_V1) + */ +static inline int lzsa_write_match_varlen_v1(unsigned char *pOutData, int nOutOffset, int nLength) { + if (nLength >= MATCH_RUN_LEN_V1) { + if ((nLength + MIN_MATCH_SIZE_V1) < 256) + pOutData[nOutOffset++] = nLength - MATCH_RUN_LEN_V1; + else { + if ((nLength + MIN_MATCH_SIZE_V1) < 512) { + pOutData[nOutOffset++] = 239; + pOutData[nOutOffset++] = nLength + MIN_MATCH_SIZE_V1 - 256; + } + else { + pOutData[nOutOffset++] = 238; + pOutData[nOutOffset++] = (nLength + MIN_MATCH_SIZE_V1) & 0xff; + pOutData[nOutOffset++] = ((nLength + MIN_MATCH_SIZE_V1) >> 8) & 0xff; + } + } + } + + return nOutOffset; +} + +/** + * Get offset encoding cost in bits + * + * @param nMatchOffset offset to get cost of + * + * @return cost in bits + */ +static inline int lzsa_get_offset_cost_v1(const unsigned int nMatchOffset) { + return (nMatchOffset <= 256) ? 8 : 16; +} + +/** + * Attempt to pick optimal matches using a forward arrivals parser, so as to produce the smallest possible output that decompresses to the same input + * + * @param pCompressor compression context + * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) + * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes + */ +static void lzsa_optimize_forward_v1(lzsa_compressor *pCompressor, lzsa_match *pBestMatch, const int nStartOffset, const int nEndOffset, const int nReduce) { + lzsa_arrival *arrival = pCompressor->arrival - (nStartOffset << MATCHES_PER_ARRIVAL_SHIFT); + const int nMinMatchSize = pCompressor->min_match_size; + const int nFavorRatio = (pCompressor->flags & LZSA_FLAG_FAVOR_RATIO) ? 1 : 0; + const int nDisableScore = nReduce ? 0 : (2 * BLOCK_SIZE); + int i, j, n; + + if ((nEndOffset - nStartOffset) > BLOCK_SIZE) return; + + memset(arrival + (nStartOffset << MATCHES_PER_ARRIVAL_SHIFT), 0, sizeof(lzsa_arrival) * ((nEndOffset - nStartOffset + 1) << MATCHES_PER_ARRIVAL_SHIFT)); + + arrival[nStartOffset << MATCHES_PER_ARRIVAL_SHIFT].from_slot = -1; + + for (i = nStartOffset; i != nEndOffset; i++) { + int m; + + for (j = 0; j < NMATCHES_PER_ARRIVAL_V1 && arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].from_slot; j++) { + int nPrevCost = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].cost; + int nCodingChoiceCost = nPrevCost + 8 /* literal */; + int nScore = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].score + 1; + int nNumLiterals = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].num_literals + 1; + + if (nNumLiterals == LITERALS_RUN_LEN_V1 || nNumLiterals == 256 || nNumLiterals == 512) { + nCodingChoiceCost += 8; + } + + if (!nFavorRatio && nNumLiterals == 1) + nCodingChoiceCost += MODESWITCH_PENALTY; + + for (n = 0; n < NMATCHES_PER_ARRIVAL_V1 /* we only need the literals + short match cost + long match cost cases */; n++) { + lzsa_arrival *pDestArrival = &arrival[((i + 1) << MATCHES_PER_ARRIVAL_SHIFT) + n]; + + if (pDestArrival->from_slot == 0 || + nCodingChoiceCost < pDestArrival->cost || + (nCodingChoiceCost == pDestArrival->cost && nScore < (pDestArrival->score + nDisableScore))) { + memmove(&arrival[((i + 1) << MATCHES_PER_ARRIVAL_SHIFT) + n + 1], + &arrival[((i + 1) << MATCHES_PER_ARRIVAL_SHIFT) + n], + sizeof(lzsa_arrival) * (NMATCHES_PER_ARRIVAL_V1 - n - 1)); + + pDestArrival->cost = nCodingChoiceCost; + pDestArrival->from_pos = i; + pDestArrival->from_slot = j + 1; + pDestArrival->match_offset = 0; + pDestArrival->match_len = 0; + pDestArrival->num_literals = nNumLiterals; + pDestArrival->score = nScore; + pDestArrival->rep_offset = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].rep_offset; + break; + } + } + } + + const lzsa_match *match = pCompressor->match + ((i - nStartOffset) << MATCHES_PER_INDEX_SHIFT_V1); + + for (m = 0; m < NMATCHES_PER_INDEX_V1 && match[m].length; m++) { + int nMatchLen = match[m].length; + int nMatchOffsetCost = lzsa_get_offset_cost_v1(match[m].offset); + int nStartingMatchLen, k; + + if ((i + nMatchLen) > (nEndOffset - LAST_LITERALS)) + nMatchLen = nEndOffset - LAST_LITERALS - i; + + if (nMatchLen >= LEAVE_ALONE_MATCH_SIZE) + nStartingMatchLen = nMatchLen; + else + nStartingMatchLen = nMinMatchSize; + for (k = nStartingMatchLen; k <= nMatchLen; k++) { + int nMatchLenCost = lzsa_get_match_varlen_size_v1(k - MIN_MATCH_SIZE_V1); + + for (j = 0; j < NMATCHES_PER_ARRIVAL_V1 && arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].from_slot; j++) { + int nPrevCost = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].cost; + int nCodingChoiceCost = nPrevCost + 8 /* token */ /* the actual cost of the literals themselves accumulates up the chain */ + nMatchOffsetCost + nMatchLenCost; + int nScore = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].score + 5; + int exists = 0; + + if (!nFavorRatio && !arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].num_literals) + nCodingChoiceCost += MODESWITCH_PENALTY; + + for (n = 0; + n < NMATCHES_PER_ARRIVAL_V1 && arrival[((i + k) << MATCHES_PER_ARRIVAL_SHIFT) + n].from_slot && arrival[((i + k) << MATCHES_PER_ARRIVAL_SHIFT) + n].cost <= nCodingChoiceCost; + n++) { + if (lzsa_get_offset_cost_v1(arrival[((i + k) << MATCHES_PER_ARRIVAL_SHIFT) + n].rep_offset) == lzsa_get_offset_cost_v1(match[m].offset)) { + exists = 1; + break; + } + } + + for (n = 0; !exists && n < NMATCHES_PER_ARRIVAL_V1 /* we only need the literals + short match cost + long match cost cases */; n++) { + lzsa_arrival *pDestArrival = &arrival[((i + k) << MATCHES_PER_ARRIVAL_SHIFT) + n]; + + if (pDestArrival->from_slot == 0 || + nCodingChoiceCost < pDestArrival->cost || + (nCodingChoiceCost == pDestArrival->cost && nScore < (pDestArrival->score + nDisableScore))) { + memmove(&arrival[((i + k) << MATCHES_PER_ARRIVAL_SHIFT) + n + 1], + &arrival[((i + k) << MATCHES_PER_ARRIVAL_SHIFT) + n], + sizeof(lzsa_arrival) * (NMATCHES_PER_ARRIVAL_V1 - n - 1)); + + pDestArrival->cost = nCodingChoiceCost; + pDestArrival->from_pos = i; + pDestArrival->from_slot = j + 1; + pDestArrival->match_offset = match[m].offset; + pDestArrival->match_len = k; + pDestArrival->num_literals = 0; + pDestArrival->score = nScore; + pDestArrival->rep_offset = match[m].offset; + break; + } + } + } + } + } + } + + lzsa_arrival *end_arrival = &arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + 0]; + + while (end_arrival->from_slot > 0 && end_arrival->from_pos >= 0) { + if (end_arrival->from_pos >= nEndOffset) return; + pBestMatch[end_arrival->from_pos].length = end_arrival->match_len; + pBestMatch[end_arrival->from_pos].offset = end_arrival->match_offset; + + end_arrival = &arrival[(end_arrival->from_pos << MATCHES_PER_ARRIVAL_SHIFT) + (end_arrival->from_slot - 1)]; + } +} + +/** + * Attempt to minimize the number of commands issued in the compressed data block, in order to speed up decompression without + * impacting the compression ratio + * + * @param pCompressor compression context + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param pBestMatch optimal matches to emit + * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) + * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes + * + * @return non-zero if the number of tokens was reduced, 0 if it wasn't + */ +static int lzsa_optimize_command_count_v1(lzsa_compressor *pCompressor, const unsigned char *pInWindow, lzsa_match *pBestMatch, const int nStartOffset, const int nEndOffset) { + int i; + int nNumLiterals = 0; + int nDidReduce = 0; + + for (i = nStartOffset; i < nEndOffset; ) { + lzsa_match *pMatch = pBestMatch + i; + + if (pMatch->length == 0 && + (i + 1) < (nEndOffset - LAST_LITERALS) && + pBestMatch[i + 1].length >= MIN_MATCH_SIZE_V1 && + pBestMatch[i + 1].length < MAX_VARLEN && + pBestMatch[i + 1].offset && + i >= pBestMatch[i + 1].offset && + (i + pBestMatch[i + 1].length + 1) <= (nEndOffset - LAST_LITERALS) && + !memcmp(pInWindow + i - (pBestMatch[i + 1].offset), pInWindow + i, pBestMatch[i + 1].length + 1)) { + int nCurLenSize = lzsa_get_match_varlen_size_v1(pBestMatch[i + 1].length - MIN_MATCH_SIZE_V1); + int nReducedLenSize = lzsa_get_match_varlen_size_v1(pBestMatch[i + 1].length + 1 - MIN_MATCH_SIZE_V1); + + if ((nReducedLenSize - nCurLenSize) <= 8) { + /* Merge */ + pBestMatch[i].length = pBestMatch[i + 1].length + 1; + pBestMatch[i].offset = pBestMatch[i + 1].offset; + pBestMatch[i + 1].length = 0; + pBestMatch[i + 1].offset = 0; + nDidReduce = 1; + continue; + } + } + + if (pMatch->length >= MIN_MATCH_SIZE_V1) { + if (pMatch->length <= 9 /* Don't waste time considering large matches, they will always win over literals */ && + (i + pMatch->length) < nEndOffset /* Don't consider the last token in the block, we can only reduce a match inbetween other tokens */) { + int nNextIndex = i + pMatch->length; + int nNextLiterals = 0; + + while (nNextIndex < nEndOffset && pBestMatch[nNextIndex].length < MIN_MATCH_SIZE_V1) { + nNextLiterals++; + nNextIndex++; + } + + /* This command is a match, is followed by 'nNextLiterals' literals and then by another match, or the end of the input. Calculate this command's current cost (excluding 'nNumLiterals' bytes) */ + if ((8 /* token */ + lzsa_get_literals_varlen_size_v1(nNumLiterals) + ((pMatch->offset <= 256) ? 8 : 16) /* match offset */ + lzsa_get_match_varlen_size_v1(pMatch->length - MIN_MATCH_SIZE_V1) + + 8 /* token */ + lzsa_get_literals_varlen_size_v1(nNextLiterals)) >= + (8 /* token */ + (pMatch->length << 3) + lzsa_get_literals_varlen_size_v1(nNumLiterals + pMatch->length + nNextLiterals))) { + /* Reduce */ + int nMatchLen = pMatch->length; + int j; + + for (j = 0; j < nMatchLen; j++) { + pBestMatch[i + j].length = 0; + } + + nDidReduce = 1; + continue; + } + } + + if ((i + pMatch->length) <= nEndOffset && pMatch->offset > 0 && pMatch->length >= MIN_MATCH_SIZE_V1 && + pBestMatch[i + pMatch->length].offset > 0 && + pBestMatch[i + pMatch->length].length >= MIN_MATCH_SIZE_V1 && + (pMatch->length + pBestMatch[i + pMatch->length].length) >= LEAVE_ALONE_MATCH_SIZE && + (pMatch->length + pBestMatch[i + pMatch->length].length) <= MAX_VARLEN && + (i + pMatch->length) > pMatch->offset && + (i + pMatch->length) > pBestMatch[i + pMatch->length].offset && + (i + pMatch->length + pBestMatch[i + pMatch->length].length) <= nEndOffset && + !memcmp(pInWindow + i - pMatch->offset + pMatch->length, + pInWindow + i + pMatch->length - pBestMatch[i + pMatch->length].offset, + pBestMatch[i + pMatch->length].length)) { + + int nCurPartialSize = lzsa_get_match_varlen_size_v1(pMatch->length - MIN_MATCH_SIZE_V1); + nCurPartialSize += 8 /* token */ + lzsa_get_literals_varlen_size_v1(0) + ((pBestMatch[i + pMatch->length].offset <= 256) ? 8 : 16) /* match offset */ + lzsa_get_match_varlen_size_v1(pBestMatch[i + pMatch->length].length - MIN_MATCH_SIZE_V1); + + int nReducedPartialSize = lzsa_get_match_varlen_size_v1(pMatch->length + pBestMatch[i + pMatch->length].length - MIN_MATCH_SIZE_V1); + + if (nCurPartialSize >= nReducedPartialSize) { + int nMatchLen = pMatch->length; + + /* Join */ + + pMatch->length += pBestMatch[i + nMatchLen].length; + pBestMatch[i + nMatchLen].offset = 0; + pBestMatch[i + nMatchLen].length = -1; + continue; + } + } + + i += pMatch->length; + nNumLiterals = 0; + } + else { + nNumLiterals++; + i++; + } + } + + return nDidReduce; +} + +/** + * Get compressed data block size + * + * @param pCompressor compression context + * @param pBestMatch optimal matches to emit + * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) + * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes + * + * @return size of compressed data that will be written to output buffer + */ +static int lzsa_get_compressed_size_v1(lzsa_compressor *pCompressor, lzsa_match *pBestMatch, const int nStartOffset, const int nEndOffset) { + int i; + int nNumLiterals = 0; + int nCompressedSize = 0; + + for (i = nStartOffset; i < nEndOffset; ) { + const lzsa_match *pMatch = pBestMatch + i; + + if (pMatch->length >= MIN_MATCH_SIZE_V1) { + int nMatchOffset = pMatch->offset; + int nMatchLen = pMatch->length; + int nEncodedMatchLen = nMatchLen - MIN_MATCH_SIZE_V1; + int nTokenLiteralsLen = (nNumLiterals >= LITERALS_RUN_LEN_V1) ? LITERALS_RUN_LEN_V1 : nNumLiterals; + int nTokenMatchLen = (nEncodedMatchLen >= MATCH_RUN_LEN_V1) ? MATCH_RUN_LEN_V1 : nEncodedMatchLen; + int nTokenLongOffset = (nMatchOffset <= 256) ? 0x00 : 0x80; + int nCommandSize = 8 /* token */ + lzsa_get_literals_varlen_size_v1(nNumLiterals) + (nNumLiterals << 3) + (nTokenLongOffset ? 16 : 8) /* match offset */ + lzsa_get_match_varlen_size_v1(nEncodedMatchLen); + + nCompressedSize += nCommandSize; + nNumLiterals = 0; + i += nMatchLen; + } + else { + nNumLiterals++; + i++; + } + } + + { + int nTokenLiteralsLen = (nNumLiterals >= LITERALS_RUN_LEN_V1) ? LITERALS_RUN_LEN_V1 : nNumLiterals; + int nCommandSize = 8 /* token */ + lzsa_get_literals_varlen_size_v1(nNumLiterals) + (nNumLiterals << 3); + + nCompressedSize += nCommandSize; + nNumLiterals = 0; + } + + if (pCompressor->flags & LZSA_FLAG_RAW_BLOCK) { + nCompressedSize += 8 * 4; + } + + return nCompressedSize; +} + +/** + * Emit block of compressed data + * + * @param pCompressor compression context + * @param pBestMatch optimal matches to emit + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) + * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes + * @param pOutData pointer to output buffer + * @param nMaxOutDataSize maximum size of output buffer, in bytes + * + * @return size of compressed data in output buffer, or -1 if the data is uncompressible + */ +static int lzsa_write_block_v1(lzsa_compressor *pCompressor, lzsa_match *pBestMatch, const unsigned char *pInWindow, const int nStartOffset, const int nEndOffset, unsigned char *pOutData, const int nMaxOutDataSize) { + int i; + int nNumLiterals = 0; + int nInFirstLiteralOffset = 0; + int nOutOffset = 0; + + for (i = nStartOffset; i < nEndOffset; ) { + const lzsa_match *pMatch = pBestMatch + i; + + if (pMatch->length >= MIN_MATCH_SIZE_V1) { + int nMatchOffset = pMatch->offset; + int nMatchLen = pMatch->length; + int nEncodedMatchLen = nMatchLen - MIN_MATCH_SIZE_V1; + int nTokenLiteralsLen = (nNumLiterals >= LITERALS_RUN_LEN_V1) ? LITERALS_RUN_LEN_V1 : nNumLiterals; + int nTokenMatchLen = (nEncodedMatchLen >= MATCH_RUN_LEN_V1) ? MATCH_RUN_LEN_V1 : nEncodedMatchLen; + int nTokenLongOffset = (nMatchOffset <= 256) ? 0x00 : 0x80; + int nCommandSize = 8 /* token */ + lzsa_get_literals_varlen_size_v1(nNumLiterals) + (nNumLiterals << 3) + (nTokenLongOffset ? 16 : 8) /* match offset */ + lzsa_get_match_varlen_size_v1(nEncodedMatchLen); + + if ((nOutOffset + (nCommandSize >> 3)) > nMaxOutDataSize) + return -1; + if (nMatchOffset < MIN_OFFSET || nMatchOffset > MAX_OFFSET) + return -1; + + pOutData[nOutOffset++] = nTokenLongOffset | (nTokenLiteralsLen << 4) | nTokenMatchLen; + nOutOffset = lzsa_write_literals_varlen_v1(pOutData, nOutOffset, nNumLiterals); + + if (nNumLiterals < pCompressor->stats.min_literals || pCompressor->stats.min_literals == -1) + pCompressor->stats.min_literals = nNumLiterals; + if (nNumLiterals > pCompressor->stats.max_literals) + pCompressor->stats.max_literals = nNumLiterals; + pCompressor->stats.total_literals += nNumLiterals; + pCompressor->stats.literals_divisor++; + + if (nNumLiterals != 0) { + memcpy(pOutData + nOutOffset, pInWindow + nInFirstLiteralOffset, nNumLiterals); + nOutOffset += nNumLiterals; + nNumLiterals = 0; + } + + pOutData[nOutOffset++] = (-nMatchOffset) & 0xff; + if (nTokenLongOffset) { + pOutData[nOutOffset++] = (-nMatchOffset) >> 8; + } + nOutOffset = lzsa_write_match_varlen_v1(pOutData, nOutOffset, nEncodedMatchLen); + + if (nMatchOffset < pCompressor->stats.min_offset || pCompressor->stats.min_offset == -1) + pCompressor->stats.min_offset = nMatchOffset; + if (nMatchOffset > pCompressor->stats.max_offset) + pCompressor->stats.max_offset = nMatchOffset; + pCompressor->stats.total_offsets += nMatchOffset; + + if (nMatchLen < pCompressor->stats.min_match_len || pCompressor->stats.min_match_len == -1) + pCompressor->stats.min_match_len = nMatchLen; + if (nMatchLen > pCompressor->stats.max_match_len) + pCompressor->stats.max_match_len = nMatchLen; + pCompressor->stats.total_match_lens += nMatchLen; + pCompressor->stats.match_divisor++; + + if (nMatchOffset == 1) { + if (nMatchLen < pCompressor->stats.min_rle1_len || pCompressor->stats.min_rle1_len == -1) + pCompressor->stats.min_rle1_len = nMatchLen; + if (nMatchLen > pCompressor->stats.max_rle1_len) + pCompressor->stats.max_rle1_len = nMatchLen; + pCompressor->stats.total_rle1_lens += nMatchLen; + pCompressor->stats.rle1_divisor++; + } + else if (nMatchOffset == 2) { + if (nMatchLen < pCompressor->stats.min_rle2_len || pCompressor->stats.min_rle2_len == -1) + pCompressor->stats.min_rle2_len = nMatchLen; + if (nMatchLen > pCompressor->stats.max_rle2_len) + pCompressor->stats.max_rle2_len = nMatchLen; + pCompressor->stats.total_rle2_lens += nMatchLen; + pCompressor->stats.rle2_divisor++; + } + + i += nMatchLen; + + if (pCompressor->flags & LZSA_FLAG_RAW_BLOCK) { + int nCurSafeDist = (i - nStartOffset) - nOutOffset; + if (nCurSafeDist >= 0 && pCompressor->safe_dist < nCurSafeDist) + pCompressor->safe_dist = nCurSafeDist; + } + + pCompressor->num_commands++; + } + else { + if (nNumLiterals == 0) + nInFirstLiteralOffset = i; + nNumLiterals++; + i++; + } + } + + { + int nTokenLiteralsLen = (nNumLiterals >= LITERALS_RUN_LEN_V1) ? LITERALS_RUN_LEN_V1 : nNumLiterals; + int nCommandSize = 8 /* token */ + lzsa_get_literals_varlen_size_v1(nNumLiterals) + (nNumLiterals << 3); + + if ((nOutOffset + (nCommandSize >> 3)) > nMaxOutDataSize) + return -1; + + if (pCompressor->flags & LZSA_FLAG_RAW_BLOCK) + pOutData[nOutOffset++] = (nTokenLiteralsLen << 4) | 0x0f; + else + pOutData[nOutOffset++] = (nTokenLiteralsLen << 4) | 0x00; + nOutOffset = lzsa_write_literals_varlen_v1(pOutData, nOutOffset, nNumLiterals); + + if (nNumLiterals < pCompressor->stats.min_literals || pCompressor->stats.min_literals == -1) + pCompressor->stats.min_literals = nNumLiterals; + if (nNumLiterals > pCompressor->stats.max_literals) + pCompressor->stats.max_literals = nNumLiterals; + pCompressor->stats.total_literals += nNumLiterals; + pCompressor->stats.literals_divisor++; + + if (nNumLiterals != 0) { + memcpy(pOutData + nOutOffset, pInWindow + nInFirstLiteralOffset, nNumLiterals); + nOutOffset += nNumLiterals; + nNumLiterals = 0; + } + + if (pCompressor->flags & LZSA_FLAG_RAW_BLOCK) { + int nCurSafeDist = (i - nStartOffset) - nOutOffset; + if (nCurSafeDist >= 0 && pCompressor->safe_dist < nCurSafeDist) + pCompressor->safe_dist = nCurSafeDist; + } + + pCompressor->num_commands++; + } + + if (pCompressor->flags & LZSA_FLAG_RAW_BLOCK) { + /* Emit EOD marker for raw block */ + + if ((nOutOffset + 4) > nMaxOutDataSize) + return -1; + + pOutData[nOutOffset++] = 0; + pOutData[nOutOffset++] = 238; + pOutData[nOutOffset++] = 0; + pOutData[nOutOffset++] = 0; + } + + return nOutOffset; +} + +/** + * Emit raw block of uncompressible data + * + * @param pCompressor compression context + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) + * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes + * @param pOutData pointer to output buffer + * @param nMaxOutDataSize maximum size of output buffer, in bytes + * + * @return size of compressed data in output buffer, or -1 if the data is uncompressible + */ +static int lzsa_write_raw_uncompressed_block_v1(lzsa_compressor *pCompressor, const unsigned char *pInWindow, const int nStartOffset, const int nEndOffset, unsigned char *pOutData, const int nMaxOutDataSize) { + int nNumLiterals = nEndOffset - nStartOffset; + int nTokenLiteralsLen = (nNumLiterals >= LITERALS_RUN_LEN_V1) ? LITERALS_RUN_LEN_V1 : nNumLiterals; + int nOutOffset = 0; + + int nCommandSize = 8 /* token */ + lzsa_get_literals_varlen_size_v1(nNumLiterals) + (nNumLiterals << 3) + 4; + if ((nOutOffset + (nCommandSize >> 3)) > nMaxOutDataSize) + return -1; + + pCompressor->num_commands = 0; + pOutData[nOutOffset++] = (nTokenLiteralsLen << 4) | 0x0f; + + nOutOffset = lzsa_write_literals_varlen_v1(pOutData, nOutOffset, nNumLiterals); + + if (nNumLiterals != 0) { + memcpy(pOutData + nOutOffset, pInWindow + nStartOffset, nNumLiterals); + nOutOffset += nNumLiterals; + nNumLiterals = 0; + } + + pCompressor->num_commands++; + + /* Emit EOD marker for raw block */ + + pOutData[nOutOffset++] = 0; + pOutData[nOutOffset++] = 238; + pOutData[nOutOffset++] = 0; + pOutData[nOutOffset++] = 0; + + return nOutOffset; +} + +/** + * Select the most optimal matches, reduce the token count if possible, and then emit a block of compressed LZSA1 data + * + * @param pCompressor compression context + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param nPreviousBlockSize number of previously compressed bytes (or 0 for none) + * @param nInDataSize number of input bytes to compress + * @param pOutData pointer to output buffer + * @param nMaxOutDataSize maximum size of output buffer, in bytes + * + * @return size of compressed data in output buffer, or -1 if the data is uncompressible + */ +int lzsa_optimize_and_write_block_v1(lzsa_compressor *pCompressor, const unsigned char *pInWindow, const int nPreviousBlockSize, const int nInDataSize, unsigned char *pOutData, const int nMaxOutDataSize) { + int nResult, nBaseCompressedSize; + + /* Compress optimally without breaking ties in favor of less tokens */ + + memset(pCompressor->best_match, 0, BLOCK_SIZE * sizeof(lzsa_match)); + lzsa_optimize_forward_v1(pCompressor, pCompressor->best_match - nPreviousBlockSize, nPreviousBlockSize, nPreviousBlockSize + nInDataSize, 0 /* reduce */); + + int nDidReduce; + int nPasses = 0; + do { + nDidReduce = lzsa_optimize_command_count_v1(pCompressor, pInWindow, pCompressor->best_match - nPreviousBlockSize, nPreviousBlockSize, nPreviousBlockSize + nInDataSize); + nPasses++; + } while (nDidReduce && nPasses < 20); + + nBaseCompressedSize = lzsa_get_compressed_size_v1(pCompressor, pCompressor->best_match - nPreviousBlockSize, nPreviousBlockSize, nPreviousBlockSize + nInDataSize); + lzsa_match *pBestMatch = pCompressor->best_match - nPreviousBlockSize; + + if (nBaseCompressedSize > 0 && nInDataSize < 65536) { + int nReducedCompressedSize; + + /* Compress optimally and do break ties in favor of less tokens */ + memset(pCompressor->improved_match, 0, BLOCK_SIZE * sizeof(lzsa_match)); + lzsa_optimize_forward_v1(pCompressor, pCompressor->improved_match - nPreviousBlockSize, nPreviousBlockSize, nPreviousBlockSize + nInDataSize, 1 /* reduce */); + + nPasses = 0; + do { + nDidReduce = lzsa_optimize_command_count_v1(pCompressor, pInWindow, pCompressor->improved_match - nPreviousBlockSize, nPreviousBlockSize, nPreviousBlockSize + nInDataSize); + nPasses++; + } while (nDidReduce && nPasses < 20); + + nReducedCompressedSize = lzsa_get_compressed_size_v1(pCompressor, pCompressor->improved_match - nPreviousBlockSize, nPreviousBlockSize, nPreviousBlockSize + nInDataSize); + if (nReducedCompressedSize > 0 && nReducedCompressedSize <= nBaseCompressedSize) { + /* Pick the parse with the reduced number of tokens as it didn't negatively affect the size */ + pBestMatch = pCompressor->improved_match - nPreviousBlockSize; + } + } + + nResult = lzsa_write_block_v1(pCompressor, pBestMatch, pInWindow, nPreviousBlockSize, nPreviousBlockSize + nInDataSize, pOutData, nMaxOutDataSize); + if (nResult < 0 && pCompressor->flags & LZSA_FLAG_RAW_BLOCK) { + nResult = lzsa_write_raw_uncompressed_block_v1(pCompressor, pInWindow, nPreviousBlockSize, nPreviousBlockSize + nInDataSize, pOutData, nMaxOutDataSize); + } + + return nResult; +} diff --git a/Tools/unix/lzsa/src/shrink_block_v1.h b/Tools/unix/lzsa/src/shrink_block_v1.h new file mode 100644 index 00000000..cc89cde0 --- /dev/null +++ b/Tools/unix/lzsa/src/shrink_block_v1.h @@ -0,0 +1,53 @@ +/* + * shrink_block_v1.h - LZSA1 block compressor definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _SHRINK_BLOCK_V1_H +#define _SHRINK_BLOCK_V1_H + +/* Forward declarations */ +typedef struct _lzsa_compressor lzsa_compressor; + +/** + * Select the most optimal matches, reduce the token count if possible, and then emit a block of compressed LZSA1 data + * + * @param pCompressor compression context + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param nPreviousBlockSize number of previously compressed bytes (or 0 for none) + * @param nInDataSize number of input bytes to compress + * @param pOutData pointer to output buffer + * @param nMaxOutDataSize maximum size of output buffer, in bytes + * + * @return size of compressed data in output buffer, or -1 if the data is uncompressible + */ +int lzsa_optimize_and_write_block_v1(lzsa_compressor *pCompressor, const unsigned char *pInWindow, const int nPreviousBlockSize, const int nInDataSize, unsigned char *pOutData, const int nMaxOutDataSize); + +#endif /* _SHRINK_BLOCK_V1_H */ diff --git a/Tools/unix/lzsa/src/shrink_block_v2.c b/Tools/unix/lzsa/src/shrink_block_v2.c new file mode 100644 index 00000000..eb4a16ed --- /dev/null +++ b/Tools/unix/lzsa/src/shrink_block_v2.c @@ -0,0 +1,1183 @@ +/* + * shrink_block_v2.c - LZSA2 block compressor implementation + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#include +#include +#include "lib.h" +#include "shrink_block_v2.h" +#include "format.h" + +/** + * Write 4-bit nibble to output (compressed) buffer + * + * @param pOutData pointer to output buffer + * @param nOutOffset current write index into output buffer + * @param nMaxOutDataSize maximum size of output buffer, in bytes + * @param nCurNibbleOffset write index into output buffer, of current byte being filled with nibbles + * @param nCurFreeNibbles current number of free nibbles in byte + * @param nNibbleValue value to write (0..15) + */ +static int lzsa_write_nibble_v2(unsigned char *pOutData, int nOutOffset, const int nMaxOutDataSize, int *nCurNibbleOffset, int *nCurFreeNibbles, int nNibbleValue) { + if (nOutOffset < 0) return -1; + + if ((*nCurNibbleOffset) == -1) { + if (nOutOffset >= nMaxOutDataSize) return -1; + (*nCurNibbleOffset) = nOutOffset; + (*nCurFreeNibbles) = 2; + pOutData[nOutOffset++] = 0; + } + + pOutData[*nCurNibbleOffset] = (pOutData[*nCurNibbleOffset] << 4) | (nNibbleValue & 0x0f); + (*nCurFreeNibbles)--; + if ((*nCurFreeNibbles) == 0) { + (*nCurNibbleOffset) = -1; + } + + return nOutOffset; +} + +/** + * Get the number of extra bits required to represent a literals length + * + * @param nLength literals length + * + * @return number of extra bits required + */ +static inline int lzsa_get_literals_varlen_size_v2(const int nLength) { + if (nLength < LITERALS_RUN_LEN_V2) { + return 0; + } + else { + if (nLength < (LITERALS_RUN_LEN_V2 + 15)) { + return 4; + } + else { + if (nLength < 256) + return 4+8; + else { + return 4+24; + } + } + } +} + +/** + * Write extra literals length bytes to output (compressed) buffer. The caller must first check that there is enough + * room to write the bytes. + * + * @param pOutData pointer to output buffer + * @param nOutOffset current write index into output buffer + * @param nLength literals length + */ +static inline int lzsa_write_literals_varlen_v2(unsigned char *pOutData, int nOutOffset, const int nMaxOutDataSize, int *nCurNibbleOffset, int *nCurFreeNibbles, int nLength) { + if (nLength >= LITERALS_RUN_LEN_V2) { + if (nLength < (LITERALS_RUN_LEN_V2 + 15)) { + nOutOffset = lzsa_write_nibble_v2(pOutData, nOutOffset, nMaxOutDataSize, nCurNibbleOffset, nCurFreeNibbles, nLength - LITERALS_RUN_LEN_V2); + } + else { + nOutOffset = lzsa_write_nibble_v2(pOutData, nOutOffset, nMaxOutDataSize, nCurNibbleOffset, nCurFreeNibbles, 15); + if (nOutOffset < 0) return -1; + + if (nLength < 256) + pOutData[nOutOffset++] = nLength - 18; + else { + pOutData[nOutOffset++] = 239; + pOutData[nOutOffset++] = nLength & 0xff; + pOutData[nOutOffset++] = (nLength >> 8) & 0xff; + } + } + } + + return nOutOffset; +} + +/** + * Get the number of extra bits required to represent an encoded match length + * + * @param nLength encoded match length (actual match length - MIN_MATCH_SIZE_V2) + * + * @return number of extra bits required + */ +static inline int lzsa_get_match_varlen_size_v2(const int nLength) { + if (nLength < MATCH_RUN_LEN_V2) { + return 0; + } + else { + if (nLength < (MATCH_RUN_LEN_V2 + 15)) + return 4; + else { + if ((nLength + MIN_MATCH_SIZE_V2) < 256) + return 4+8; + else { + return 4 + 24; + } + } + } +} + +/** + * Write extra encoded match length bytes to output (compressed) buffer. The caller must first check that there is enough + * room to write the bytes. + * + * @param pOutData pointer to output buffer + * @param nOutOffset current write index into output buffer + * @param nLength encoded match length (actual match length - MIN_MATCH_SIZE_V2) + */ +static inline int lzsa_write_match_varlen_v2(unsigned char *pOutData, int nOutOffset, const int nMaxOutDataSize, int *nCurNibbleOffset, int *nCurFreeNibbles, int nLength) { + if (nLength >= MATCH_RUN_LEN_V2) { + if (nLength < (MATCH_RUN_LEN_V2 + 15)) { + nOutOffset = lzsa_write_nibble_v2(pOutData, nOutOffset, nMaxOutDataSize, nCurNibbleOffset, nCurFreeNibbles, nLength - MATCH_RUN_LEN_V2); + } + else { + nOutOffset = lzsa_write_nibble_v2(pOutData, nOutOffset, nMaxOutDataSize, nCurNibbleOffset, nCurFreeNibbles, 15); + if (nOutOffset < 0) return -1; + + if ((nLength + MIN_MATCH_SIZE_V2) < 256) + pOutData[nOutOffset++] = nLength + MIN_MATCH_SIZE_V2 - 24; + else { + pOutData[nOutOffset++] = 233; + pOutData[nOutOffset++] = (nLength + MIN_MATCH_SIZE_V2) & 0xff; + pOutData[nOutOffset++] = ((nLength + MIN_MATCH_SIZE_V2) >> 8) & 0xff; + } + } + } + + return nOutOffset; +} + +/** + * Insert forward rep candidate + * + * @param pCompressor compression context + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param i input data window position whose matches are being considered + * @param nMatchOffset match offset to use as rep candidate + * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) + * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes + * @param nMatchesPerArrival number of arrivals to record per input buffer position + * @param nDepth current insertion depth + */ +static void lzsa_insert_forward_match_v2(lzsa_compressor *pCompressor, const unsigned char *pInWindow, const int i, const int nMatchOffset, const int nStartOffset, const int nEndOffset, const int nMatchesPerArrival, int nDepth) { + lzsa_arrival *arrival = pCompressor->arrival - (nStartOffset << MATCHES_PER_ARRIVAL_SHIFT); + int j; + + if (nDepth >= 10) return; + + for (j = 0; j < nMatchesPerArrival && arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].from_slot; j++) { + int nRepOffset = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].rep_offset; + + if (nMatchOffset != nRepOffset && nRepOffset && arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].rep_len >= MIN_MATCH_SIZE_V2) { + int nRepPos = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].rep_pos; + int nRepLen = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].rep_len; + + if (nRepPos > nMatchOffset && + (nRepPos - nMatchOffset + nRepLen) <= (nEndOffset - LAST_LITERALS) && + !memcmp(pInWindow + nRepPos - nRepOffset, pInWindow + nRepPos - nMatchOffset, nRepLen)) { + int nCurRepLen = nRepLen; + + int nMaxRepLen = nEndOffset - nRepPos; + if (nMaxRepLen > LCP_MAX) + nMaxRepLen = LCP_MAX; + while ((nCurRepLen + 8) < nMaxRepLen && !memcmp(pInWindow + nRepPos + nCurRepLen, pInWindow + nRepPos - nMatchOffset + nCurRepLen, 8)) + nCurRepLen += 8; + while ((nCurRepLen + 4) < nMaxRepLen && !memcmp(pInWindow + nRepPos + nCurRepLen, pInWindow + nRepPos - nMatchOffset + nCurRepLen, 4)) + nCurRepLen += 4; + while (nCurRepLen < nMaxRepLen && pInWindow[nRepPos + nCurRepLen] == pInWindow[nRepPos - nMatchOffset + nCurRepLen]) + nCurRepLen++; + + lzsa_match *fwd_match = pCompressor->match + ((nRepPos - nStartOffset) << MATCHES_PER_INDEX_SHIFT_V2); + int exists = 0; + int r; + + for (r = 0; r < NMATCHES_PER_INDEX_V2 && fwd_match[r].length >= MIN_MATCH_SIZE_V2; r++) { + if (fwd_match[r].offset == nMatchOffset) { + exists = 1; + + if (fwd_match[r].length < nCurRepLen) { + fwd_match[r].length = nCurRepLen; + lzsa_insert_forward_match_v2(pCompressor, pInWindow, nRepPos, nMatchOffset, nStartOffset, nEndOffset, nMatchesPerArrival, nDepth + 1); + } + break; + } + } + + if (!exists && r < NMATCHES_PER_INDEX_V2) { + fwd_match[r].offset = nMatchOffset; + fwd_match[r].length = nCurRepLen; + + lzsa_insert_forward_match_v2(pCompressor, pInWindow, nRepPos, nMatchOffset, nStartOffset, nEndOffset, nMatchesPerArrival, nDepth + 1); + } + } + } + } +} + +/** + * Attempt to pick optimal matches using a forward arrivals parser, so as to produce the smallest possible output that decompresses to the same input + * + * @param pCompressor compression context + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param pBestMatch pointer to buffer for outputting optimal matches + * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) + * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes + * @param nReduce non-zero to reduce the number of tokens when the path costs are equal, zero not to + * @param nInsertForwardReps non-zero to insert forward repmatch candidates, zero to use the previously inserted candidates + * @param nMatchesPerArrival number of arrivals to record per input buffer position + */ +static void lzsa_optimize_forward_v2(lzsa_compressor *pCompressor, const unsigned char *pInWindow, lzsa_match *pBestMatch, const int nStartOffset, const int nEndOffset, const int nReduce, const int nInsertForwardReps, const int nMatchesPerArrival) { + lzsa_arrival *arrival = pCompressor->arrival - (nStartOffset << MATCHES_PER_ARRIVAL_SHIFT); + const int nFavorRatio = (pCompressor->flags & LZSA_FLAG_FAVOR_RATIO) ? 1 : 0; + const int nMinMatchSize = pCompressor->min_match_size; + const int nDisableScore = nReduce ? 0 : (2 * BLOCK_SIZE); + const int nLeaveAloneMatchSize = (nMatchesPerArrival == NMATCHES_PER_ARRIVAL_V2_SMALL) ? LEAVE_ALONE_MATCH_SIZE_SMALL : LEAVE_ALONE_MATCH_SIZE; + int i, j, n; + + if ((nEndOffset - nStartOffset) > BLOCK_SIZE) return; + + memset(arrival + (nStartOffset << MATCHES_PER_ARRIVAL_SHIFT), 0, sizeof(lzsa_arrival) * ((nEndOffset - nStartOffset + 1) << MATCHES_PER_ARRIVAL_SHIFT)); + + for (i = (nStartOffset << MATCHES_PER_ARRIVAL_SHIFT); i != ((nEndOffset + 1) << MATCHES_PER_ARRIVAL_SHIFT); i++) { + arrival[i].cost = 0x40000000; + } + + arrival[nStartOffset << MATCHES_PER_ARRIVAL_SHIFT].from_slot = -1; + + for (i = nStartOffset; i != nEndOffset; i++) { + int m; + + for (j = 0; j < nMatchesPerArrival && arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].from_slot; j++) { + const int nPrevCost = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].cost & 0x3fffffff; + int nCodingChoiceCost = nPrevCost + 8 /* literal */; + int nNumLiterals = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].num_literals + 1; + + if (nNumLiterals == LITERALS_RUN_LEN_V2) { + nCodingChoiceCost += 4; + } + else if (nNumLiterals == (LITERALS_RUN_LEN_V2 + 15)) { + nCodingChoiceCost += 8; + } + else if (nNumLiterals == 256) { + nCodingChoiceCost += 16; + } + + if (!nFavorRatio && nNumLiterals == 1) + nCodingChoiceCost += MODESWITCH_PENALTY; + + lzsa_arrival *pDestSlots = &arrival[(i + 1) << MATCHES_PER_ARRIVAL_SHIFT]; + if (nCodingChoiceCost <= pDestSlots[nMatchesPerArrival - 1].cost) { + int exists = 0; + for (n = 0; + n < nMatchesPerArrival && pDestSlots[n].cost <= nCodingChoiceCost; + n++) { + if (pDestSlots[n].rep_offset == arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].rep_offset) { + exists = 1; + break; + } + } + + if (!exists) { + int nScore = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].score + 1; + for (n = 0; n < nMatchesPerArrival; n++) { + lzsa_arrival *pDestArrival = &pDestSlots[n]; + if (nCodingChoiceCost < pDestArrival->cost || + (nCodingChoiceCost == pDestArrival->cost && nScore < (pDestArrival->score + nDisableScore))) { + + if (pDestArrival->from_slot) { + int z; + + for (z = n; z < nMatchesPerArrival - 1; z++) { + if (pDestSlots[z].rep_offset == arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].rep_offset) + break; + } + + memmove(&pDestSlots[n + 1], + &pDestSlots[n], + sizeof(lzsa_arrival) * (z - n)); + } + + pDestArrival->cost = nCodingChoiceCost; + pDestArrival->from_pos = i; + pDestArrival->from_slot = j + 1; + pDestArrival->match_offset = 0; + pDestArrival->match_len = 0; + pDestArrival->num_literals = nNumLiterals; + pDestArrival->score = nScore; + pDestArrival->rep_offset = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].rep_offset; + pDestArrival->rep_pos = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].rep_pos; + pDestArrival->rep_len = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].rep_len; + break; + } + } + } + } + } + + lzsa_match *match = pCompressor->match + ((i - nStartOffset) << MATCHES_PER_INDEX_SHIFT_V2); + + int nMinRepLen[NMATCHES_PER_ARRIVAL_V2_BIG]; + memset(nMinRepLen, 0, nMatchesPerArrival * sizeof(int)); + + for (m = 0; m < NMATCHES_PER_INDEX_V2 && match[m].length; m++) { + int nMatchLen = match[m].length & 0x7fff; + int nMatchOffset = match[m].offset; + int nScorePenalty = ((match[m].length & 0x8000) >> 15); + int nNoRepmatchOffsetCost = (nMatchOffset <= 32) ? 4 : ((nMatchOffset <= 512) ? 8 : ((nMatchOffset <= (8192 + 512)) ? 12 : 16)); + int nStartingMatchLen, k; + int nMaxRepLen[NMATCHES_PER_ARRIVAL_V2_BIG]; + + if ((i + nMatchLen) > (nEndOffset - LAST_LITERALS)) + nMatchLen = nEndOffset - LAST_LITERALS - i; + + for (j = 0; j < nMatchesPerArrival && arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].from_slot; j++) { + int nRepOffset = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].rep_offset; + int nCurMaxRepLen = 0; + + if (nRepOffset) { + if (nMatchOffset == nRepOffset) + nCurMaxRepLen = nMatchLen; + else { + if (i > nRepOffset && + (i - nRepOffset + nMatchLen) <= (nEndOffset - LAST_LITERALS)) { + nCurMaxRepLen = nMinRepLen[j]; + while ((nCurMaxRepLen + 8) < nMatchLen && !memcmp(pInWindow + i - nRepOffset + nCurMaxRepLen, pInWindow + i + nCurMaxRepLen, 8)) + nCurMaxRepLen += 8; + while ((nCurMaxRepLen + 4) < nMatchLen && !memcmp(pInWindow + i - nRepOffset + nCurMaxRepLen, pInWindow + i + nCurMaxRepLen, 4)) + nCurMaxRepLen += 4; + while (nCurMaxRepLen < nMatchLen && pInWindow[i - nRepOffset + nCurMaxRepLen] == pInWindow[i + nCurMaxRepLen]) + nCurMaxRepLen++; + nMinRepLen[j] = nCurMaxRepLen; + } + } + } + + nMaxRepLen[j] = nCurMaxRepLen; + } + while (j < nMatchesPerArrival) + nMaxRepLen[j++] = 0; + + if (nInsertForwardReps) + lzsa_insert_forward_match_v2(pCompressor, pInWindow, i, nMatchOffset, nStartOffset, nEndOffset, nMatchesPerArrival, 0); + + int nMatchLenCost = 0; + if (nMatchLen >= nLeaveAloneMatchSize) { + nStartingMatchLen = nMatchLen; + nMatchLenCost = 4 + 24; + } + else { + nStartingMatchLen = nMinMatchSize; + nMatchLenCost = 0; + } + + for (k = nStartingMatchLen; k <= nMatchLen; k++) { + if (k == (MATCH_RUN_LEN_V2 + MIN_MATCH_SIZE_V2)) { + nMatchLenCost = 4; + } + else { + if (k == (MATCH_RUN_LEN_V2 + 15 + MIN_MATCH_SIZE_V2)) + nMatchLenCost = 4 + 8; + else { + if (k == 256) + nMatchLenCost = 4 + 24; + } + } + + lzsa_arrival *pDestSlots = &arrival[(i + k) << MATCHES_PER_ARRIVAL_SHIFT]; + int nInsertedNoRepMatchCandidate = 0; + + for (j = 0; j < nMatchesPerArrival && arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].from_slot; j++) { + const int nPrevCost = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].cost & 0x3fffffff; + int nRepCodingChoiceCost = nPrevCost + 8 /* token */ /* the actual cost of the literals themselves accumulates up the chain */ + nMatchLenCost; + + if (nRepCodingChoiceCost <= pDestSlots[nMatchesPerArrival - 1].cost) { + int nRepOffset = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].rep_offset; + + if (nMatchOffset != nRepOffset && !nInsertedNoRepMatchCandidate) { + int nCodingChoiceCost = nRepCodingChoiceCost + nNoRepmatchOffsetCost; + + if (!nFavorRatio && !arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].num_literals) + nCodingChoiceCost += MODESWITCH_PENALTY; + + if (nCodingChoiceCost <= pDestSlots[nMatchesPerArrival - 1].cost) { + int exists = 0; + int nScore = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].score + 3 + nScorePenalty; + + for (n = 0; + n < nMatchesPerArrival && pDestSlots[n].cost <= nCodingChoiceCost; + n++) { + if (pDestSlots[n].rep_offset == nMatchOffset && + (!nInsertForwardReps || pDestSlots[n].cost != nCodingChoiceCost || pDestSlots[n].rep_pos >= i || nScore >= (pDestSlots[n].score + nDisableScore) || + pDestSlots[nMatchesPerArrival - 1].from_slot)) { + exists = 1; + break; + } + } + + if (!exists) { + for (n = 0; n < nMatchesPerArrival - 1; n++) { + lzsa_arrival *pDestArrival = &pDestSlots[n]; + + if (nCodingChoiceCost < pDestArrival->cost || + (nCodingChoiceCost == pDestArrival->cost && nScore < (pDestArrival->score + nDisableScore))) { + if (pDestArrival->from_slot) { + int z; + + for (z = n; z < nMatchesPerArrival - 1; z++) { + if (pDestSlots[z].rep_offset == nMatchOffset) + break; + } + + if (z == (nMatchesPerArrival - 1) && pDestSlots[z].from_slot && pDestSlots[z].match_len < MIN_MATCH_SIZE_V2) + z--; + + memmove(&pDestSlots[n + 1], + &pDestSlots[n], + sizeof(lzsa_arrival) * (z - n)); + } + + pDestArrival->cost = nCodingChoiceCost; + pDestArrival->from_pos = i; + pDestArrival->from_slot = j + 1; + pDestArrival->match_offset = nMatchOffset; + pDestArrival->match_len = k; + pDestArrival->num_literals = 0; + pDestArrival->score = nScore; + pDestArrival->rep_offset = nMatchOffset; + pDestArrival->rep_pos = i; + pDestArrival->rep_len = k; + nInsertedNoRepMatchCandidate = 1; + break; + } + } + } + } + } + + /* If this coding choice doesn't rep-match, see if we still get a match by using the current repmatch offset for this arrival. This can occur (and not have the + * matchfinder offer the offset in the first place, or have too many choices with the same cost to retain the repmatchable offset) when compressing regions + * of identical bytes, for instance. Checking for this provides a big compression win on some files. */ + + if (nMaxRepLen[j] >= k) { + int exists = 0; + + /* A match is possible at the rep offset; insert the extra coding choice. */ + + for (n = 0; + n < nMatchesPerArrival && pDestSlots[n].cost <= nRepCodingChoiceCost; + n++) { + if (pDestSlots[n].rep_offset == nRepOffset) { + exists = 1; + break; + } + } + + if (!exists) { + int nScore = arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + j].score + 2; + + for (n = 0; n < nMatchesPerArrival; n++) { + lzsa_arrival *pDestArrival = &pDestSlots[n]; + + if (nRepCodingChoiceCost < pDestArrival->cost || + (nRepCodingChoiceCost == pDestArrival->cost && nScore < (pDestArrival->score + nDisableScore))) { + if (pDestArrival->from_slot) { + int z; + + for (z = n; z < nMatchesPerArrival - 1; z++) { + if (pDestSlots[z].rep_offset == nRepOffset) + break; + } + + memmove(&pDestSlots[n + 1], + &pDestSlots[n], + sizeof(lzsa_arrival) * (z - n)); + } + + pDestArrival->cost = nRepCodingChoiceCost; + pDestArrival->from_pos = i; + pDestArrival->from_slot = j + 1; + pDestArrival->match_offset = nRepOffset; + pDestArrival->match_len = k; + pDestArrival->num_literals = 0; + pDestArrival->score = nScore; + pDestArrival->rep_offset = nRepOffset; + pDestArrival->rep_pos = i; + pDestArrival->rep_len = k; + break; + } + } + } + } + } + else { + break; + } + } + } + + if (nMatchLen >= LCP_MAX && ((m + 1) >= NMATCHES_PER_INDEX_V2 || match[m + 1].length < LCP_MAX)) + break; + } + } + + lzsa_arrival *end_arrival = &arrival[(i << MATCHES_PER_ARRIVAL_SHIFT) + 0]; + + while (end_arrival->from_slot > 0 && end_arrival->from_pos >= 0) { + if (end_arrival->from_pos >= nEndOffset) return; + pBestMatch[end_arrival->from_pos].length = end_arrival->match_len; + pBestMatch[end_arrival->from_pos].offset = end_arrival->match_offset; + end_arrival = &arrival[(end_arrival->from_pos << MATCHES_PER_ARRIVAL_SHIFT) + (end_arrival->from_slot - 1)]; + } +} + +/** + * Attempt to minimize the number of commands issued in the compressed data block, in order to speed up decompression without + * impacting the compression ratio + * + * @param pCompressor compression context + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param pBestMatch optimal matches to evaluate and update + * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) + * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes + * + * @return non-zero if the number of tokens was reduced, 0 if it wasn't + */ +static int lzsa_optimize_command_count_v2(lzsa_compressor *pCompressor, const unsigned char *pInWindow, lzsa_match *pBestMatch, const int nStartOffset, const int nEndOffset) { + int i; + int nNumLiterals = 0; + int nPrevRepMatchOffset = 0; + int nRepMatchOffset = 0; + int nRepMatchLen = 0; + int nRepIndex = 0; + int nDidReduce = 0; + + for (i = nStartOffset; i < nEndOffset; ) { + lzsa_match *pMatch = pBestMatch + i; + + if (pMatch->length == 0 && + (i + 1) < (nEndOffset - LAST_LITERALS) && + pBestMatch[i + 1].length >= MIN_MATCH_SIZE_V2 && + pBestMatch[i + 1].length < MAX_VARLEN && + pBestMatch[i + 1].offset && + i >= pBestMatch[i + 1].offset && + (i + pBestMatch[i + 1].length + 1) <= (nEndOffset - LAST_LITERALS) && + !memcmp(pInWindow + i - (pBestMatch[i + 1].offset), pInWindow + i, pBestMatch[i + 1].length + 1)) { + int nCurLenSize = lzsa_get_match_varlen_size_v2(pBestMatch[i + 1].length - MIN_MATCH_SIZE_V2); + int nReducedLenSize = lzsa_get_match_varlen_size_v2(pBestMatch[i + 1].length + 1 - MIN_MATCH_SIZE_V2); + + if ((nReducedLenSize - nCurLenSize) <= 8) { + /* Merge */ + pBestMatch[i].length = pBestMatch[i + 1].length + 1; + pBestMatch[i].offset = pBestMatch[i + 1].offset; + pBestMatch[i + 1].length = 0; + pBestMatch[i + 1].offset = 0; + nDidReduce = 1; + continue; + } + } + + if (pMatch->length >= MIN_MATCH_SIZE_V2) { + if ((i + pMatch->length) < nEndOffset /* Don't consider the last match in the block, we can only reduce a match inbetween other tokens */) { + int nNextIndex = i + pMatch->length; + int nNextLiterals = 0; + + while (nNextIndex < nEndOffset && pBestMatch[nNextIndex].length < MIN_MATCH_SIZE_V2) { + nNextLiterals++; + nNextIndex++; + } + + if (nNextIndex < nEndOffset && pBestMatch[nNextIndex].length >= MIN_MATCH_SIZE_V2) { + /* This command is a match, is followed by 'nNextLiterals' literals and then by another match */ + + if (nRepMatchOffset && pMatch->offset != nRepMatchOffset && (pBestMatch[nNextIndex].offset != pMatch->offset || pBestMatch[nNextIndex].offset == nRepMatchOffset || + ((pMatch->offset <= 32) ? 4 : ((pMatch->offset <= 512) ? 8 : ((pMatch->offset <= (8192 + 512)) ? 12 : 16))) > + ((pBestMatch[nNextIndex].offset <= 32) ? 4 : ((pBestMatch[nNextIndex].offset <= 512) ? 8 : ((pBestMatch[nNextIndex].offset <= (8192 + 512)) ? 12 : 16))))) { + /* Check if we can change the current match's offset to be the same as the previous match's offset, and get an extra repmatch. This will occur when + * matching large regions of identical bytes for instance, where there are too many offsets to be considered by the parser, and when not compressing to favor the + * ratio (the forward arrivals parser already has this covered). */ + if (i > nRepMatchOffset && + (i - nRepMatchOffset + pMatch->length) <= (nEndOffset - LAST_LITERALS) && + !memcmp(pInWindow + i - nRepMatchOffset, pInWindow + i - pMatch->offset, pMatch->length)) { + pMatch->offset = nRepMatchOffset; + nDidReduce = 1; + } + } + + if (pBestMatch[nNextIndex].offset && pMatch->offset != pBestMatch[nNextIndex].offset && nRepMatchOffset != pBestMatch[nNextIndex].offset) { + /* Otherwise, try to gain a match forward as well */ + if (i > pBestMatch[nNextIndex].offset && (i - pBestMatch[nNextIndex].offset + pMatch->length) <= (nEndOffset - LAST_LITERALS)) { + int nMaxLen = 0; + while (nMaxLen < pMatch->length && pInWindow[i - pBestMatch[nNextIndex].offset + nMaxLen] == pInWindow[i - pMatch->offset + nMaxLen]) + nMaxLen++; + if (nMaxLen >= pMatch->length) { + /* Replace */ + pMatch->offset = pBestMatch[nNextIndex].offset; + nDidReduce = 1; + } + else if (nMaxLen >= 2 && pMatch->offset != nRepMatchOffset) { + int nPartialSizeBefore, nPartialSizeAfter; + + nPartialSizeBefore = lzsa_get_match_varlen_size_v2(pMatch->length - MIN_MATCH_SIZE_V2); + nPartialSizeBefore += (pMatch->offset <= 32) ? 4 : ((pMatch->offset <= 512) ? 8 : ((pMatch->offset <= (8192 + 512)) ? 12 : 16)); + nPartialSizeBefore += lzsa_get_literals_varlen_size_v2(nNextLiterals); + + nPartialSizeAfter = lzsa_get_match_varlen_size_v2(nMaxLen - MIN_MATCH_SIZE_V2); + nPartialSizeAfter += lzsa_get_literals_varlen_size_v2(nNextLiterals + (pMatch->length - nMaxLen)) + ((pMatch->length - nMaxLen) << 3); + + if (nPartialSizeAfter < nPartialSizeBefore) { + int j; + + /* We gain a repmatch that is shorter than the original match as this is the best we can do, so it is followed by extra literals, but + * we have calculated that this is shorter */ + pMatch->offset = pBestMatch[nNextIndex].offset; + for (j = nMaxLen; j < pMatch->length; j++) { + pBestMatch[i + j].length = 0; + } + pMatch->length = nMaxLen; + nDidReduce = 1; + } + } + } + } + + if (pMatch->length < 9 /* Don't waste time considering large matches, they will always win over literals */) { + /* Calculate this command's current cost (excluding 'nNumLiterals' bytes) */ + + int nCurCommandSize = 8 /* token */ + lzsa_get_literals_varlen_size_v2(nNumLiterals) + lzsa_get_match_varlen_size_v2(pMatch->length - MIN_MATCH_SIZE_V2); + if (pMatch->offset != nRepMatchOffset) + nCurCommandSize += (pMatch->offset <= 32) ? 4 : ((pMatch->offset <= 512) ? 8 : ((pMatch->offset <= (8192 + 512)) ? 12 : 16)); + + /* Calculate the next command's current cost */ + int nNextCommandSize = 8 /* token */ + lzsa_get_literals_varlen_size_v2(nNextLiterals) + (nNextLiterals << 3) + lzsa_get_match_varlen_size_v2(pBestMatch[nNextIndex].length - MIN_MATCH_SIZE_V2); + if (pBestMatch[nNextIndex].offset != pMatch->offset) + nNextCommandSize += (pBestMatch[nNextIndex].offset <= 32) ? 4 : ((pBestMatch[nNextIndex].offset <= 512) ? 8 : ((pBestMatch[nNextIndex].offset <= (8192 + 512)) ? 12 : 16)); + + int nOriginalCombinedCommandSize = nCurCommandSize + nNextCommandSize; + + /* Calculate the cost of replacing this match command by literals + the next command with the cost of encoding these literals (excluding 'nNumLiterals' bytes) */ + int nReducedCommandSize = (pMatch->length << 3) + 8 /* token */ + lzsa_get_literals_varlen_size_v2(nNumLiterals + pMatch->length + nNextLiterals) + (nNextLiterals << 3) + lzsa_get_match_varlen_size_v2(pBestMatch[nNextIndex].length - MIN_MATCH_SIZE_V2); + if (pBestMatch[nNextIndex].offset != nRepMatchOffset) + nReducedCommandSize += (pBestMatch[nNextIndex].offset <= 32) ? 4 : ((pBestMatch[nNextIndex].offset <= 512) ? 8 : ((pBestMatch[nNextIndex].offset <= (8192 + 512)) ? 12 : 16)); + + int nReplaceRepOffset = 0; + if (nRepMatchOffset && nRepMatchOffset != nPrevRepMatchOffset && nRepMatchLen >= MIN_MATCH_SIZE_V2 && nRepMatchOffset != pBestMatch[nNextIndex].offset && nRepIndex > pBestMatch[nNextIndex].offset && + (nRepIndex - pBestMatch[nNextIndex].offset + nRepMatchLen) <= (nEndOffset - LAST_LITERALS) && + !memcmp(pInWindow + nRepIndex - nRepMatchOffset, pInWindow + nRepIndex - pBestMatch[nNextIndex].offset, nRepMatchLen)) { + /* Replacing this match command by literals would let us create a repmatch */ + nReplaceRepOffset = 1; + nReducedCommandSize -= (nRepMatchOffset <= 32) ? 4 : ((nRepMatchOffset <= 512) ? 8 : ((nRepMatchOffset <= (8192 + 512)) ? 12 : 16)); + } + + if (nOriginalCombinedCommandSize >= nReducedCommandSize) { + /* Reduce */ + int nMatchLen = pMatch->length; + int j; + + for (j = 0; j < nMatchLen; j++) { + pBestMatch[i + j].length = 0; + } + + nDidReduce = 1; + + if (nReplaceRepOffset) { + pBestMatch[nRepIndex].offset = pBestMatch[nNextIndex].offset; + nRepMatchOffset = pBestMatch[nNextIndex].offset; + } + continue; + } + } + } + } + + if ((i + pMatch->length) <= nEndOffset && pMatch->offset > 0 && pMatch->length >= MIN_MATCH_SIZE_V2 && + pBestMatch[i + pMatch->length].offset > 0 && + pBestMatch[i + pMatch->length].length >= MIN_MATCH_SIZE_V2 && + (pMatch->length + pBestMatch[i + pMatch->length].length) >= LEAVE_ALONE_MATCH_SIZE && + (pMatch->length + pBestMatch[i + pMatch->length].length) <= MAX_VARLEN && + (i + pMatch->length) > pMatch->offset && + (i + pMatch->length) > pBestMatch[i + pMatch->length].offset && + (i + pMatch->length + pBestMatch[i + pMatch->length].length) <= nEndOffset && + !memcmp(pInWindow + i - pMatch->offset + pMatch->length, + pInWindow + i + pMatch->length - pBestMatch[i + pMatch->length].offset, + pBestMatch[i + pMatch->length].length)) { + + int nNextIndex = i + pMatch->length; + int nNextLiterals = 0; + + while (nNextIndex < nEndOffset && pBestMatch[nNextIndex].length < MIN_MATCH_SIZE_V2) { + nNextLiterals++; + nNextIndex++; + } + + int nCurPartialSize = lzsa_get_match_varlen_size_v2(pMatch->length - MIN_MATCH_SIZE_V2); + + nCurPartialSize += 8 /* token */ + lzsa_get_literals_varlen_size_v2(0) + lzsa_get_match_varlen_size_v2(pBestMatch[i + pMatch->length].length - MIN_MATCH_SIZE_V2); + if (pBestMatch[i + pMatch->length].offset != pMatch->offset) + nCurPartialSize += (pBestMatch[i + pMatch->length].offset <= 32) ? 4 : ((pBestMatch[i + pMatch->length].offset <= 512) ? 8 : ((pBestMatch[i + pMatch->length].offset <= (8192 + 512)) ? 12 : 16)); + + if (pBestMatch[nNextIndex].offset != pBestMatch[i + pMatch->length].offset) + nCurPartialSize += (pBestMatch[nNextIndex].offset <= 32) ? 4 : ((pBestMatch[nNextIndex].offset <= 512) ? 8 : ((pBestMatch[nNextIndex].offset <= (8192 + 512)) ? 12 : 16)); + + int nReducedPartialSize = lzsa_get_match_varlen_size_v2(pMatch->length + pBestMatch[i + pMatch->length].length - MIN_MATCH_SIZE_V2); + + if (pBestMatch[nNextIndex].offset != pMatch->offset) + nReducedPartialSize += (pBestMatch[nNextIndex].offset <= 32) ? 4 : ((pBestMatch[nNextIndex].offset <= 512) ? 8 : ((pBestMatch[nNextIndex].offset <= (8192 + 512)) ? 12 : 16)); + + if (nCurPartialSize >= nReducedPartialSize) { + int nMatchLen = pMatch->length; + + /* Join */ + + pMatch->length += pBestMatch[i + nMatchLen].length; + pBestMatch[i + nMatchLen].offset = 0; + pBestMatch[i + nMatchLen].length = -1; + nDidReduce = 1; + continue; + } + } + + nPrevRepMatchOffset = nRepMatchOffset; + nRepMatchOffset = pMatch->offset; + nRepMatchLen = pMatch->length; + nRepIndex = i; + + i += pMatch->length; + nNumLiterals = 0; + } + else { + nNumLiterals++; + i++; + } + } + + return nDidReduce; +} + +/** + * Get compressed data block size + * + * @param pCompressor compression context + * @param pBestMatch optimal matches to emit + * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) + * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes + * + * @return size of compressed data that will be written to output buffer + */ +static int lzsa_get_compressed_size_v2(lzsa_compressor *pCompressor, lzsa_match *pBestMatch, const int nStartOffset, const int nEndOffset) { + int i; + int nNumLiterals = 0; + int nOutOffset = 0; + int nRepMatchOffset = 0; + int nCompressedSize = 0; + + for (i = nStartOffset; i < nEndOffset; ) { + const lzsa_match *pMatch = pBestMatch + i; + + if (pMatch->length >= MIN_MATCH_SIZE_V2) { + int nMatchOffset = pMatch->offset; + int nMatchLen = pMatch->length; + int nEncodedMatchLen = nMatchLen - MIN_MATCH_SIZE_V2; + int nOffsetSize; + + if (nMatchOffset == nRepMatchOffset) { + nOffsetSize = 0; + } + else { + if (nMatchOffset <= 32) { + nOffsetSize = 4; + } + else if (nMatchOffset <= 512) { + nOffsetSize = 8; + } + else if (nMatchOffset <= (8192 + 512)) { + nOffsetSize = 12; + } + else { + nOffsetSize = 16; + } + } + + int nCommandSize = 8 /* token */ + lzsa_get_literals_varlen_size_v2(nNumLiterals) + (nNumLiterals << 3) + nOffsetSize /* match offset */ + lzsa_get_match_varlen_size_v2(nEncodedMatchLen); + nCompressedSize += nCommandSize; + + nNumLiterals = 0; + nRepMatchOffset = nMatchOffset; + i += nMatchLen; + } + else { + nNumLiterals++; + i++; + } + } + + { + int nTokenLiteralsLen = (nNumLiterals >= LITERALS_RUN_LEN_V2) ? LITERALS_RUN_LEN_V2 : nNumLiterals; + int nCommandSize = 8 /* token */ + lzsa_get_literals_varlen_size_v2(nNumLiterals) + (nNumLiterals << 3); + + nCompressedSize += nCommandSize; + nNumLiterals = 0; + } + + if (pCompressor->flags & LZSA_FLAG_RAW_BLOCK) { + nCompressedSize += (8 + 4 + 8); + } + + return nCompressedSize; +} + +/** + * Emit block of compressed data + * + * @param pCompressor compression context + * @param pBestMatch optimal matches to emit + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) + * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes + * @param pOutData pointer to output buffer + * @param nMaxOutDataSize maximum size of output buffer, in bytes + * + * @return size of compressed data in output buffer, or -1 if the data is uncompressible + */ +static int lzsa_write_block_v2(lzsa_compressor *pCompressor, lzsa_match *pBestMatch, const unsigned char *pInWindow, const int nStartOffset, const int nEndOffset, unsigned char *pOutData, const int nMaxOutDataSize) { + int i; + int nNumLiterals = 0; + int nInFirstLiteralOffset = 0; + int nOutOffset = 0; + int nCurNibbleOffset = -1, nCurFreeNibbles = 0; + int nRepMatchOffset = 0; + + for (i = nStartOffset; i < nEndOffset; ) { + const lzsa_match *pMatch = pBestMatch + i; + + if (pMatch->length >= MIN_MATCH_SIZE_V2) { + int nMatchOffset = pMatch->offset; + int nMatchLen = pMatch->length; + int nEncodedMatchLen = nMatchLen - MIN_MATCH_SIZE_V2; + int nTokenLiteralsLen = (nNumLiterals >= LITERALS_RUN_LEN_V2) ? LITERALS_RUN_LEN_V2 : nNumLiterals; + int nTokenMatchLen = (nEncodedMatchLen >= MATCH_RUN_LEN_V2) ? MATCH_RUN_LEN_V2 : nEncodedMatchLen; + int nTokenOffsetMode; + int nOffsetSize; + + if (nMatchOffset == nRepMatchOffset) { + nTokenOffsetMode = 0xe0; + nOffsetSize = 0; + } + else { + if (nMatchOffset <= 32) { + nTokenOffsetMode = 0x00 | ((((-nMatchOffset) & 0x01) << 5) ^ 0x20); + nOffsetSize = 4; + } + else if (nMatchOffset <= 512) { + nTokenOffsetMode = 0x40 | ((((-nMatchOffset) & 0x100) >> 3) ^ 0x20); + nOffsetSize = 8; + } + else if (nMatchOffset <= (8192 + 512)) { + nTokenOffsetMode = 0x80 | ((((-(nMatchOffset - 512)) & 0x0100) >> 3) ^ 0x20); + nOffsetSize = 12; + } + else { + nTokenOffsetMode = 0xc0; + nOffsetSize = 16; + } + } + + int nCommandSize = 8 /* token */ + lzsa_get_literals_varlen_size_v2(nNumLiterals) + (nNumLiterals << 3) + nOffsetSize /* match offset */ + lzsa_get_match_varlen_size_v2(nEncodedMatchLen); + + if ((nOutOffset + ((nCommandSize + 7) >> 3)) > nMaxOutDataSize) + return -1; + if (nMatchOffset < MIN_OFFSET || nMatchOffset > MAX_OFFSET) + return -1; + + pOutData[nOutOffset++] = nTokenOffsetMode | (nTokenLiteralsLen << 3) | nTokenMatchLen; + nOutOffset = lzsa_write_literals_varlen_v2(pOutData, nOutOffset, nMaxOutDataSize, &nCurNibbleOffset, &nCurFreeNibbles, nNumLiterals); + if (nOutOffset < 0) return -1; + + if (nNumLiterals < pCompressor->stats.min_literals || pCompressor->stats.min_literals == -1) + pCompressor->stats.min_literals = nNumLiterals; + if (nNumLiterals > pCompressor->stats.max_literals) + pCompressor->stats.max_literals = nNumLiterals; + pCompressor->stats.total_literals += nNumLiterals; + pCompressor->stats.literals_divisor++; + + if (nNumLiterals != 0) { + memcpy(pOutData + nOutOffset, pInWindow + nInFirstLiteralOffset, nNumLiterals); + nOutOffset += nNumLiterals; + nNumLiterals = 0; + } + + if (nTokenOffsetMode == 0x00 || nTokenOffsetMode == 0x20) { + nOutOffset = lzsa_write_nibble_v2(pOutData, nOutOffset, nMaxOutDataSize, &nCurNibbleOffset, &nCurFreeNibbles, ((-nMatchOffset) & 0x1e) >> 1); + if (nOutOffset < 0) return -1; + } + else if (nTokenOffsetMode == 0x40 || nTokenOffsetMode == 0x60) { + pOutData[nOutOffset++] = (-nMatchOffset) & 0xff; + } + else if (nTokenOffsetMode == 0x80 || nTokenOffsetMode == 0xa0) { + nOutOffset = lzsa_write_nibble_v2(pOutData, nOutOffset, nMaxOutDataSize, &nCurNibbleOffset, &nCurFreeNibbles, ((-(nMatchOffset - 512)) >> 9) & 0x0f); + if (nOutOffset < 0) return -1; + pOutData[nOutOffset++] = (-(nMatchOffset - 512)) & 0xff; + } + else if (nTokenOffsetMode == 0xc0) { + pOutData[nOutOffset++] = (-nMatchOffset) >> 8; + pOutData[nOutOffset++] = (-nMatchOffset) & 0xff; + } + + if (nMatchOffset == nRepMatchOffset) + pCompressor->stats.num_rep_offsets++; + + nRepMatchOffset = nMatchOffset; + + nOutOffset = lzsa_write_match_varlen_v2(pOutData, nOutOffset, nMaxOutDataSize, &nCurNibbleOffset, &nCurFreeNibbles, nEncodedMatchLen); + if (nOutOffset < 0) return -1; + + if (nMatchOffset < pCompressor->stats.min_offset || pCompressor->stats.min_offset == -1) + pCompressor->stats.min_offset = nMatchOffset; + if (nMatchOffset > pCompressor->stats.max_offset) + pCompressor->stats.max_offset = nMatchOffset; + pCompressor->stats.total_offsets += nMatchOffset; + + if (nMatchLen < pCompressor->stats.min_match_len || pCompressor->stats.min_match_len == -1) + pCompressor->stats.min_match_len = nMatchLen; + if (nMatchLen > pCompressor->stats.max_match_len) + pCompressor->stats.max_match_len = nMatchLen; + pCompressor->stats.total_match_lens += nMatchLen; + pCompressor->stats.match_divisor++; + + if (nMatchOffset == 1) { + if (nMatchLen < pCompressor->stats.min_rle1_len || pCompressor->stats.min_rle1_len == -1) + pCompressor->stats.min_rle1_len = nMatchLen; + if (nMatchLen > pCompressor->stats.max_rle1_len) + pCompressor->stats.max_rle1_len = nMatchLen; + pCompressor->stats.total_rle1_lens += nMatchLen; + pCompressor->stats.rle1_divisor++; + } + else if (nMatchOffset == 2) { + if (nMatchLen < pCompressor->stats.min_rle2_len || pCompressor->stats.min_rle2_len == -1) + pCompressor->stats.min_rle2_len = nMatchLen; + if (nMatchLen > pCompressor->stats.max_rle2_len) + pCompressor->stats.max_rle2_len = nMatchLen; + pCompressor->stats.total_rle2_lens += nMatchLen; + pCompressor->stats.rle2_divisor++; + } + + i += nMatchLen; + + if (pCompressor->flags & LZSA_FLAG_RAW_BLOCK) { + int nCurSafeDist = (i - nStartOffset) - nOutOffset; + if (nCurSafeDist >= 0 && pCompressor->safe_dist < nCurSafeDist) + pCompressor->safe_dist = nCurSafeDist; + } + + pCompressor->num_commands++; + } + else { + if (nNumLiterals == 0) + nInFirstLiteralOffset = i; + nNumLiterals++; + i++; + } + } + + { + int nTokenLiteralsLen = (nNumLiterals >= LITERALS_RUN_LEN_V2) ? LITERALS_RUN_LEN_V2 : nNumLiterals; + int nCommandSize = 8 /* token */ + lzsa_get_literals_varlen_size_v2(nNumLiterals) + (nNumLiterals << 3); + + if ((nOutOffset + ((nCommandSize + 7) >> 3)) > nMaxOutDataSize) + return -1; + + if (pCompressor->flags & LZSA_FLAG_RAW_BLOCK) + pOutData[nOutOffset++] = (nTokenLiteralsLen << 3) | 0x47; + else + pOutData[nOutOffset++] = (nTokenLiteralsLen << 3) | 0x00; + nOutOffset = lzsa_write_literals_varlen_v2(pOutData, nOutOffset, nMaxOutDataSize, &nCurNibbleOffset, &nCurFreeNibbles, nNumLiterals); + if (nOutOffset < 0) return -1; + + if (nNumLiterals < pCompressor->stats.min_literals || pCompressor->stats.min_literals == -1) + pCompressor->stats.min_literals = nNumLiterals; + if (nNumLiterals > pCompressor->stats.max_literals) + pCompressor->stats.max_literals = nNumLiterals; + pCompressor->stats.total_literals += nNumLiterals; + pCompressor->stats.literals_divisor++; + + if (nNumLiterals != 0) { + memcpy(pOutData + nOutOffset, pInWindow + nInFirstLiteralOffset, nNumLiterals); + nOutOffset += nNumLiterals; + nNumLiterals = 0; + } + + if (pCompressor->flags & LZSA_FLAG_RAW_BLOCK) { + int nCurSafeDist = (i - nStartOffset) - nOutOffset; + if (nCurSafeDist >= 0 && pCompressor->safe_dist < nCurSafeDist) + pCompressor->safe_dist = nCurSafeDist; + } + + pCompressor->num_commands++; + } + + if (pCompressor->flags & LZSA_FLAG_RAW_BLOCK) { + /* Emit EOD marker for raw block */ + + if (nOutOffset >= nMaxOutDataSize) + return -1; + pOutData[nOutOffset++] = 0; /* Match offset */ + + nOutOffset = lzsa_write_nibble_v2(pOutData, nOutOffset, nMaxOutDataSize, &nCurNibbleOffset, &nCurFreeNibbles, 15); /* Extended match length nibble */ + if (nOutOffset < 0) return -1; + + if ((nOutOffset + 1) > nMaxOutDataSize) + return -1; + + pOutData[nOutOffset++] = 232; /* EOD match length byte */ + } + + if (nCurNibbleOffset != -1) { + nOutOffset = lzsa_write_nibble_v2(pOutData, nOutOffset, nMaxOutDataSize, &nCurNibbleOffset, &nCurFreeNibbles, 0); + if (nOutOffset < 0 || nCurNibbleOffset != -1) + return -1; + } + + return nOutOffset; +} + +/** + * Emit raw block of uncompressible data + * + * @param pCompressor compression context + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param nStartOffset current offset in input window (typically the number of previously compressed bytes) + * @param nEndOffset offset to end finding matches at (typically the size of the total input window in bytes + * @param pOutData pointer to output buffer + * @param nMaxOutDataSize maximum size of output buffer, in bytes + * + * @return size of compressed data in output buffer, or -1 if the data is uncompressible + */ +static int lzsa_write_raw_uncompressed_block_v2(lzsa_compressor *pCompressor, const unsigned char *pInWindow, const int nStartOffset, const int nEndOffset, unsigned char *pOutData, const int nMaxOutDataSize) { + int nCurNibbleOffset = -1, nCurFreeNibbles = 0; + int nNumLiterals = nEndOffset - nStartOffset; + int nTokenLiteralsLen = (nNumLiterals >= LITERALS_RUN_LEN_V2) ? LITERALS_RUN_LEN_V2 : nNumLiterals; + int nOutOffset = 0; + + int nCommandSize = 8 /* token */ + lzsa_get_literals_varlen_size_v2(nNumLiterals) + (nNumLiterals << 3) + 8 + 4 + 8; + if ((nOutOffset + ((nCommandSize + 7) >> 3)) > nMaxOutDataSize) + return -1; + + pCompressor->num_commands = 0; + pOutData[nOutOffset++] = (nTokenLiteralsLen << 3) | 0x47; + + nOutOffset = lzsa_write_literals_varlen_v2(pOutData, nOutOffset, nMaxOutDataSize, &nCurNibbleOffset, &nCurFreeNibbles, nNumLiterals); + if (nOutOffset < 0) return -1; + + if (nNumLiterals != 0) { + memcpy(pOutData + nOutOffset, pInWindow + nStartOffset, nNumLiterals); + nOutOffset += nNumLiterals; + nNumLiterals = 0; + } + + /* Emit EOD marker for raw block */ + + pOutData[nOutOffset++] = 0; /* Match offset */ + + nOutOffset = lzsa_write_nibble_v2(pOutData, nOutOffset, nMaxOutDataSize, &nCurNibbleOffset, &nCurFreeNibbles, 15); /* Extended match length nibble */ + if (nOutOffset < 0) return -1; + + if ((nOutOffset + 1) > nMaxOutDataSize) + return -1; + + pOutData[nOutOffset++] = 232; /* EOD match length byte */ + + pCompressor->num_commands++; + + if (nCurNibbleOffset != -1) { + nOutOffset = lzsa_write_nibble_v2(pOutData, nOutOffset, nMaxOutDataSize, &nCurNibbleOffset, &nCurFreeNibbles, 0); + if (nOutOffset < 0 || nCurNibbleOffset != -1) + return -1; + } + + return nOutOffset; +} + +/** + * Select the most optimal matches, reduce the token count if possible, and then emit a block of compressed LZSA2 data + * + * @param pCompressor compression context + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param nPreviousBlockSize number of previously compressed bytes (or 0 for none) + * @param nInDataSize number of input bytes to compress + * @param pOutData pointer to output buffer + * @param nMaxOutDataSize maximum size of output buffer, in bytes + * + * @return size of compressed data in output buffer, or -1 if the data is uncompressible + */ +int lzsa_optimize_and_write_block_v2(lzsa_compressor *pCompressor, const unsigned char *pInWindow, const int nPreviousBlockSize, const int nInDataSize, unsigned char *pOutData, const int nMaxOutDataSize) { + int nResult, nBaseCompressedSize; + int nMatchesPerArrival = (nInDataSize < 65536) ? NMATCHES_PER_ARRIVAL_V2_BIG : NMATCHES_PER_ARRIVAL_V2_SMALL; + + /* Compress optimally without breaking ties in favor of less tokens */ + + memset(pCompressor->best_match, 0, BLOCK_SIZE * sizeof(lzsa_match)); + lzsa_optimize_forward_v2(pCompressor, pInWindow, pCompressor->best_match - nPreviousBlockSize, nPreviousBlockSize, nPreviousBlockSize + nInDataSize, 0 /* reduce */, (nInDataSize < 65536) ? 1 : 0 /* insert forward reps */, nMatchesPerArrival); + + int nDidReduce; + int nPasses = 0; + do { + nDidReduce = lzsa_optimize_command_count_v2(pCompressor, pInWindow, pCompressor->best_match - nPreviousBlockSize, nPreviousBlockSize, nPreviousBlockSize + nInDataSize); + nPasses++; + } while (nDidReduce && nPasses < 20); + + nBaseCompressedSize = lzsa_get_compressed_size_v2(pCompressor, pCompressor->best_match - nPreviousBlockSize, nPreviousBlockSize, nPreviousBlockSize + nInDataSize); + lzsa_match *pBestMatch = pCompressor->best_match - nPreviousBlockSize; + + if (nBaseCompressedSize > 0 && nInDataSize < 65536) { + int nReducedCompressedSize; + + /* Compress optimally and do break ties in favor of less tokens */ + memset(pCompressor->improved_match, 0, BLOCK_SIZE * sizeof(lzsa_match)); + lzsa_optimize_forward_v2(pCompressor, pInWindow, pCompressor->improved_match - nPreviousBlockSize, nPreviousBlockSize, nPreviousBlockSize + nInDataSize, 1 /* reduce */, 0 /* use forward reps */, nMatchesPerArrival); + + nPasses = 0; + do { + nDidReduce = lzsa_optimize_command_count_v2(pCompressor, pInWindow, pCompressor->improved_match - nPreviousBlockSize, nPreviousBlockSize, nPreviousBlockSize + nInDataSize); + nPasses++; + } while (nDidReduce && nPasses < 20); + + nReducedCompressedSize = lzsa_get_compressed_size_v2(pCompressor, pCompressor->improved_match - nPreviousBlockSize, nPreviousBlockSize, nPreviousBlockSize + nInDataSize); + if (nReducedCompressedSize > 0 && nReducedCompressedSize <= nBaseCompressedSize) { + /* Pick the parse with the reduced number of tokens as it didn't negatively affect the size */ + pBestMatch = pCompressor->improved_match - nPreviousBlockSize; + } + } + + nResult = lzsa_write_block_v2(pCompressor, pBestMatch, pInWindow, nPreviousBlockSize, nPreviousBlockSize + nInDataSize, pOutData, nMaxOutDataSize); + if (nResult < 0 && pCompressor->flags & LZSA_FLAG_RAW_BLOCK) { + nResult = lzsa_write_raw_uncompressed_block_v2(pCompressor, pInWindow, nPreviousBlockSize, nPreviousBlockSize + nInDataSize, pOutData, nMaxOutDataSize); + } + + return nResult; +} diff --git a/Tools/unix/lzsa/src/shrink_block_v2.h b/Tools/unix/lzsa/src/shrink_block_v2.h new file mode 100644 index 00000000..4a836087 --- /dev/null +++ b/Tools/unix/lzsa/src/shrink_block_v2.h @@ -0,0 +1,53 @@ +/* + * shrink_block_v2.h - LZSA2 block compressor definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _SHRINK_BLOCK_V2_H +#define _SHRINK_BLOCK_V2_H + +/* Forward declarations */ +typedef struct _lzsa_compressor lzsa_compressor; + +/** + * Select the most optimal matches, reduce the token count if possible, and then emit a block of compressed LZSA2 data + * + * @param pCompressor compression context + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param nPreviousBlockSize number of previously compressed bytes (or 0 for none) + * @param nInDataSize number of input bytes to compress + * @param pOutData pointer to output buffer + * @param nMaxOutDataSize maximum size of output buffer, in bytes + * + * @return size of compressed data in output buffer, or -1 if the data is uncompressible + */ +int lzsa_optimize_and_write_block_v2(lzsa_compressor *pCompressor, const unsigned char *pInWindow, const int nPreviousBlockSize, const int nInDataSize, unsigned char *pOutData, const int nMaxOutDataSize); + +#endif /* _SHRINK_BLOCK_V2_H */ diff --git a/Tools/unix/lzsa/src/shrink_context.c b/Tools/unix/lzsa/src/shrink_context.c new file mode 100644 index 00000000..c1e7ab3d --- /dev/null +++ b/Tools/unix/lzsa/src/shrink_context.c @@ -0,0 +1,221 @@ +/* + * shrink_context.c - compression context implementation + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#include +#include +#include "shrink_context.h" +#include "shrink_block_v1.h" +#include "shrink_block_v2.h" +#include "format.h" +#include "matchfinder.h" +#include "lib.h" + +/** + * Initialize compression context + * + * @param pCompressor compression context to initialize + * @param nMaxWindowSize maximum size of input data window (previously compressed bytes + bytes to compress) + * @param nMinMatchSize minimum match size (cannot be less than MIN_MATCH_SIZE) + * @param nFlags compression flags + * + * @return 0 for success, non-zero for failure + */ +int lzsa_compressor_init(lzsa_compressor *pCompressor, const int nMaxWindowSize, const int nMinMatchSize, const int nFormatVersion, const int nFlags) { + int nResult; + int nMinMatchSizeForFormat = (nFormatVersion == 1) ? MIN_MATCH_SIZE_V1 : MIN_MATCH_SIZE_V2; + int nMaxMinMatchForFormat = (nFormatVersion == 1) ? 5 : 3; + + nResult = divsufsort_init(&pCompressor->divsufsort_context); + pCompressor->intervals = NULL; + pCompressor->pos_data = NULL; + pCompressor->open_intervals = NULL; + pCompressor->match = NULL; + pCompressor->best_match = NULL; + pCompressor->improved_match = NULL; + pCompressor->arrival = NULL; + pCompressor->min_match_size = nMinMatchSize; + if (pCompressor->min_match_size < nMinMatchSizeForFormat) + pCompressor->min_match_size = nMinMatchSizeForFormat; + else if (pCompressor->min_match_size > nMaxMinMatchForFormat) + pCompressor->min_match_size = nMaxMinMatchForFormat; + pCompressor->format_version = nFormatVersion; + pCompressor->flags = nFlags; + pCompressor->safe_dist = 0; + pCompressor->num_commands = 0; + + memset(&pCompressor->stats, 0, sizeof(pCompressor->stats)); + pCompressor->stats.min_literals = -1; + pCompressor->stats.min_match_len = -1; + pCompressor->stats.min_offset = -1; + pCompressor->stats.min_rle1_len = -1; + pCompressor->stats.min_rle2_len = -1; + + if (!nResult) { + pCompressor->intervals = (unsigned int *)malloc(nMaxWindowSize * sizeof(unsigned int)); + + if (pCompressor->intervals) { + pCompressor->pos_data = (unsigned int *)malloc(nMaxWindowSize * sizeof(unsigned int)); + + if (pCompressor->pos_data) { + pCompressor->open_intervals = (unsigned int *)malloc((LCP_AND_TAG_MAX + 1) * sizeof(unsigned int)); + + if (pCompressor->open_intervals) { + pCompressor->arrival = (lzsa_arrival *)malloc(((BLOCK_SIZE + 1) << MATCHES_PER_ARRIVAL_SHIFT) * sizeof(lzsa_arrival)); + + if (pCompressor->arrival) { + pCompressor->best_match = (lzsa_match *)malloc(BLOCK_SIZE * sizeof(lzsa_match)); + + if (pCompressor->best_match) { + pCompressor->improved_match = (lzsa_match *)malloc(BLOCK_SIZE * sizeof(lzsa_match)); + + if (pCompressor->improved_match) { + if (pCompressor->format_version == 2) + pCompressor->match = (lzsa_match *)malloc(BLOCK_SIZE * NMATCHES_PER_INDEX_V2 * sizeof(lzsa_match)); + else + pCompressor->match = (lzsa_match *)malloc(BLOCK_SIZE * NMATCHES_PER_INDEX_V1 * sizeof(lzsa_match)); + if (pCompressor->match) + return 0; + } + } + } + } + } + } + } + + lzsa_compressor_destroy(pCompressor); + return 100; +} + +/** + * Clean up compression context and free up any associated resources + * + * @param pCompressor compression context to clean up + */ +void lzsa_compressor_destroy(lzsa_compressor *pCompressor) { + divsufsort_destroy(&pCompressor->divsufsort_context); + + if (pCompressor->match) { + free(pCompressor->match); + pCompressor->match = NULL; + } + + if (pCompressor->improved_match) { + free(pCompressor->improved_match); + pCompressor->improved_match = NULL; + } + + if (pCompressor->arrival) { + free(pCompressor->arrival); + pCompressor->arrival = NULL; + } + + if (pCompressor->best_match) { + free(pCompressor->best_match); + pCompressor->best_match = NULL; + } + + if (pCompressor->open_intervals) { + free(pCompressor->open_intervals); + pCompressor->open_intervals = NULL; + } + + if (pCompressor->pos_data) { + free(pCompressor->pos_data); + pCompressor->pos_data = NULL; + } + + if (pCompressor->intervals) { + free(pCompressor->intervals); + pCompressor->intervals = NULL; + } +} + +/** + * Compress one block of data + * + * @param pCompressor compression context + * @param pInWindow pointer to input data window (previously compressed bytes + bytes to compress) + * @param nPreviousBlockSize number of previously compressed bytes (or 0 for none) + * @param nInDataSize number of input bytes to compress + * @param pOutData pointer to output buffer + * @param nMaxOutDataSize maximum size of output buffer, in bytes + * + * @return size of compressed data in output buffer, or -1 if the data is uncompressible + */ +int lzsa_compressor_shrink_block(lzsa_compressor *pCompressor, unsigned char *pInWindow, const int nPreviousBlockSize, const int nInDataSize, unsigned char *pOutData, const int nMaxOutDataSize) { + int nCompressedSize; + + if (pCompressor->flags & LZSA_FLAG_RAW_BACKWARD) { + lzsa_reverse_buffer(pInWindow + nPreviousBlockSize, nInDataSize); + } + + if (lzsa_build_suffix_array(pCompressor, pInWindow, nPreviousBlockSize + nInDataSize)) + nCompressedSize = -1; + else { + if (nPreviousBlockSize) { + lzsa_skip_matches(pCompressor, 0, nPreviousBlockSize); + } + lzsa_find_all_matches(pCompressor, (pCompressor->format_version == 2) ? NMATCHES_PER_INDEX_V2 : NMATCHES_PER_INDEX_V1, nPreviousBlockSize, nPreviousBlockSize + nInDataSize); + + if (pCompressor->format_version == 1) { + nCompressedSize = lzsa_optimize_and_write_block_v1(pCompressor, pInWindow, nPreviousBlockSize, nInDataSize, pOutData, nMaxOutDataSize); + if (nCompressedSize != -1 && (pCompressor->flags & LZSA_FLAG_RAW_BACKWARD)) { + lzsa_reverse_buffer(pOutData, nCompressedSize); + } + } + else if (pCompressor->format_version == 2) { + nCompressedSize = lzsa_optimize_and_write_block_v2(pCompressor, pInWindow, nPreviousBlockSize, nInDataSize, pOutData, nMaxOutDataSize); + if (nCompressedSize != -1 && (pCompressor->flags & LZSA_FLAG_RAW_BACKWARD)) { + lzsa_reverse_buffer(pOutData, nCompressedSize); + } + } + else { + nCompressedSize = -1; + } + } + + if (pCompressor->flags & LZSA_FLAG_RAW_BACKWARD) { + lzsa_reverse_buffer(pInWindow + nPreviousBlockSize, nInDataSize); + } + + return nCompressedSize; +} + +/** + * Get the number of compression commands issued in compressed data blocks + * + * @return number of commands + */ +int lzsa_compressor_get_command_count(lzsa_compressor *pCompressor) { + return pCompressor->num_commands; +} diff --git a/Tools/unix/lzsa/src/shrink_context.h b/Tools/unix/lzsa/src/shrink_context.h new file mode 100644 index 00000000..70245cf6 --- /dev/null +++ b/Tools/unix/lzsa/src/shrink_context.h @@ -0,0 +1,183 @@ +/* + * shrink_context.h - compression context definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _SHRINK_CONTEXT_H +#define _SHRINK_CONTEXT_H + +#include "divsufsort.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define LCP_BITS 14 +#define TAG_BITS 4 +#define LCP_MAX ((1U<<(LCP_BITS - TAG_BITS)) - 1) +#define LCP_AND_TAG_MAX (1U<<(LCP_BITS - 1)) +#define LCP_SHIFT (31-LCP_BITS) +#define LCP_MASK (((1U< + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#include +#include +#include "shrink_inmem.h" +#include "shrink_context.h" +#include "frame.h" +#include "format.h" +#include "lib.h" + +/** + * Get maximum compressed size of input(source) data + * + * @param nInputSize input(source) size in bytes + * + * @return maximum compressed size + */ +size_t lzsa_get_max_compressed_size_inmem(size_t nInputSize) { + return lzsa_get_header_size() + ((nInputSize + (BLOCK_SIZE - 1)) >> 16) * lzsa_get_frame_size() + nInputSize + lzsa_get_frame_size() /* footer */; +} + +/** + * Compress memory + * + * @param pInputData pointer to input(source) data to compress + * @param pOutBuffer buffer for compressed data + * @param nInputSize input(source) size in bytes + * @param nMaxOutBufferSize maximum capacity of compression buffer + * @param nFlags compression flags (LZSA_FLAG_xxx) + * @param nMinMatchSize minimum match size + * @param nFormatVersion version of format to use (1-2) + * + * @return actual compressed size, or -1 for error + */ +size_t lzsa_compress_inmem(unsigned char *pInputData, unsigned char *pOutBuffer, size_t nInputSize, size_t nMaxOutBufferSize, + const unsigned int nFlags, const int nMinMatchSize, const int nFormatVersion) { + lzsa_compressor compressor; + size_t nOriginalSize = 0; + size_t nCompressedSize = 0L; + int nResult; + int nError = 0; + + nResult = lzsa_compressor_init(&compressor, BLOCK_SIZE * 2, nMinMatchSize, nFormatVersion, nFlags); + if (nResult != 0) { + return -1; + } + + if ((nFlags & LZSA_FLAG_RAW_BLOCK) == 0) { + int nHeaderSize = lzsa_encode_header(pOutBuffer, (int)nMaxOutBufferSize, nFormatVersion); + if (nHeaderSize < 0) + nError = LZSA_ERROR_COMPRESSION; + else { + nCompressedSize += nHeaderSize; + } + } + + int nPreviousBlockSize = 0; + int nNumBlocks = 0; + + while (nOriginalSize < nInputSize && !nError) { + int nInDataSize; + + nInDataSize = (int)(nInputSize - nOriginalSize); + if (nInDataSize > BLOCK_SIZE) + nInDataSize = BLOCK_SIZE; + + if (nInDataSize > 0) { + if ((nFlags & LZSA_FLAG_RAW_BLOCK) != 0 && nNumBlocks) { + nError = LZSA_ERROR_RAW_TOOLARGE; + break; + } + + int nOutDataSize; + int nOutDataEnd = (int)(nMaxOutBufferSize - (lzsa_get_frame_size() + nCompressedSize + lzsa_get_frame_size() /* footer */)); + int nFrameSize = lzsa_get_frame_size(); + + if ((nFlags & LZSA_FLAG_RAW_BLOCK) != 0) { + nFrameSize = 0; + nOutDataEnd = (int)(nMaxOutBufferSize - nCompressedSize); + } + + if (nOutDataEnd > BLOCK_SIZE) + nOutDataEnd = BLOCK_SIZE; + + nOutDataSize = lzsa_compressor_shrink_block(&compressor, pInputData + nOriginalSize - nPreviousBlockSize, nPreviousBlockSize, nInDataSize, pOutBuffer + nFrameSize + nCompressedSize, nOutDataEnd); + if (nOutDataSize >= 0) { + /* Write compressed block */ + + if ((nFlags & LZSA_FLAG_RAW_BLOCK) == 0) { + int nBlockheaderSize = lzsa_encode_compressed_block_frame(pOutBuffer + nCompressedSize, (int)(nMaxOutBufferSize - nCompressedSize), nOutDataSize); + if (nBlockheaderSize < 0) + nError = LZSA_ERROR_COMPRESSION; + else { + nCompressedSize += nBlockheaderSize; + } + } + + if (!nError) { + nOriginalSize += nInDataSize; + nCompressedSize += nOutDataSize; + } + } + else { + /* Write uncompressible, literal block */ + + if ((nFlags & LZSA_FLAG_RAW_BLOCK) != 0) { + nError = LZSA_ERROR_RAW_UNCOMPRESSED; + break; + } + + int nBlockheaderSize = lzsa_encode_uncompressed_block_frame(pOutBuffer + nCompressedSize, (int)(nMaxOutBufferSize - nCompressedSize), nInDataSize); + if (nBlockheaderSize < 0) + nError = LZSA_ERROR_COMPRESSION; + else { + if ((size_t)nInDataSize > (nMaxOutBufferSize - (nCompressedSize + nBlockheaderSize))) + nError = LZSA_ERROR_DST; + else { + memcpy(pOutBuffer + nBlockheaderSize + nCompressedSize, pInputData + nOriginalSize, nInDataSize); + + nOriginalSize += nInDataSize; + nCompressedSize += nBlockheaderSize + nInDataSize; + } + } + } + + nPreviousBlockSize = nInDataSize; + nNumBlocks++; + } + } + + if (!nError) { + int nFooterSize; + + if ((nFlags & LZSA_FLAG_RAW_BLOCK) != 0) { + nFooterSize = 0; + } + else { + nFooterSize = lzsa_encode_footer_frame(pOutBuffer + nCompressedSize, (int)(nMaxOutBufferSize - nCompressedSize)); + if (nFooterSize < 0) + nError = LZSA_ERROR_COMPRESSION; + } + + nCompressedSize += nFooterSize; + } + + lzsa_compressor_destroy(&compressor); + + if (nError) { + return -1; + } + else { + return nCompressedSize; + } +} + diff --git a/Tools/unix/lzsa/src/shrink_inmem.h b/Tools/unix/lzsa/src/shrink_inmem.h new file mode 100644 index 00000000..2bd8f277 --- /dev/null +++ b/Tools/unix/lzsa/src/shrink_inmem.h @@ -0,0 +1,71 @@ +/* + * shrink_inmem.h - in-memory compression definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _SHRINK_INMEM_H +#define _SHRINK_INMEM_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Get maximum compressed size of input(source) data + * + * @param nInputSize input(source) size in bytes + * + * @return maximum compressed size + */ +size_t lzsa_get_max_compressed_size_inmem(size_t nInputSize); + +/** + * Compress memory + * + * @param pInputData pointer to input(source) data to compress + * @param pOutBuffer buffer for compressed data + * @param nInputSize input(source) size in bytes + * @param nMaxOutBufferSize maximum capacity of compression buffer + * @param nFlags compression flags (LZSA_FLAG_xxx) + * @param nMinMatchSize minimum match size + * @param nFormatVersion version of format to use (1-2) + * + * @return actual compressed size, or -1 for error + */ +size_t lzsa_compress_inmem(unsigned char *pInputData, unsigned char *pOutBuffer, size_t nInputSize, size_t nMaxOutBufferSize, + const unsigned int nFlags, const int nMinMatchSize, const int nFormatVersion); + +#ifdef __cplusplus +} +#endif + +#endif /* _SHRINK_INMEM_H */ diff --git a/Tools/unix/lzsa/src/shrink_streaming.c b/Tools/unix/lzsa/src/shrink_streaming.c new file mode 100644 index 00000000..2e1cf125 --- /dev/null +++ b/Tools/unix/lzsa/src/shrink_streaming.c @@ -0,0 +1,320 @@ +/* + * shrink_streaming.c - streaming compression implementation + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#include +#include +#include "shrink_streaming.h" +#include "format.h" +#include "frame.h" +#include "lib.h" +#ifdef _WIN32 +#include +#else +#include +#endif + +/** + * Delete file + * + * @param pszInFilename name of file to delete + */ +static void lzsa_delete_file(const char *pszInFilename) { +#ifdef _WIN32 + DeleteFileA(pszInFilename); +#else + remove(pszInFilename); +#endif +} + +/*-------------- File API -------------- */ + +/** + * Compress file + * + * @param pszInFilename name of input(source) file to compress + * @param pszOutFilename name of output(compressed) file to generate + * @param pszDictionaryFilename name of dictionary file, or NULL for none + * @param nFlags compression flags (LZSA_FLAG_xxx) + * @param nMinMatchSize minimum match size + * @param nFormatVersion version of format to use (1-2) + * @param progress progress function, called after compressing each block, or NULL for none + * @param pOriginalSize pointer to returned input(source) size, updated when this function is successful + * @param pCompressedSize pointer to returned output(compressed) size, updated when this function is successful + * @param pCommandCount pointer to returned token(compression commands) count, updated when this function is successful + * @param pSafeDist pointer to return safe distance for raw blocks, updated when this function is successful + * @param pStats pointer to compression stats that are filled if this function is successful, or NULL + * + * @return LZSA_OK for success, or an error value from lzsa_status_t + */ +lzsa_status_t lzsa_compress_file(const char *pszInFilename, const char *pszOutFilename, const char *pszDictionaryFilename, const unsigned int nFlags, const int nMinMatchSize, const int nFormatVersion, + void(*progress)(long long nOriginalSize, long long nCompressedSize), long long *pOriginalSize, long long *pCompressedSize, int *pCommandCount, int *pSafeDist, lzsa_stats *pStats) { + lzsa_stream_t inStream, outStream; + void *pDictionaryData = NULL; + int nDictionaryDataSize = 0; + lzsa_status_t nStatus; + + if (lzsa_filestream_open(&inStream, pszInFilename, "rb") < 0) { + return LZSA_ERROR_SRC; + } + + if (lzsa_filestream_open(&outStream, pszOutFilename, "wb") < 0) { + inStream.close(&inStream); + return LZSA_ERROR_DST; + } + + nStatus = lzsa_dictionary_load(pszDictionaryFilename, &pDictionaryData, &nDictionaryDataSize); + + if (nStatus) { + outStream.close(&outStream); + inStream.close(&inStream); + lzsa_delete_file(pszOutFilename); + return nStatus; + } + + nStatus = lzsa_compress_stream(&inStream, &outStream, pDictionaryData, nDictionaryDataSize, nFlags, nMinMatchSize, nFormatVersion, progress, pOriginalSize, pCompressedSize, pCommandCount, pSafeDist, pStats); + + lzsa_dictionary_free(&pDictionaryData); + outStream.close(&outStream); + inStream.close(&inStream); + + if (nStatus) { + lzsa_delete_file(pszOutFilename); + } + + return nStatus; +} + +/*-------------- Streaming API -------------- */ + +/** + * Compress stream + * + * @param pInStream input(source) stream to compress + * @param pOutStream output(compressed) stream to write to + * @param pDictionaryData dictionary contents, or NULL for none + * @param nDictionaryDataSize size of dictionary contents, or 0 + * @param nFlags compression flags (LZSA_FLAG_xxx) + * @param nMinMatchSize minimum match size + * @param nFormatVersion version of format to use (1-2) + * @param progress progress function, called after compressing each block, or NULL for none + * @param pOriginalSize pointer to returned input(source) size, updated when this function is successful + * @param pCompressedSize pointer to returned output(compressed) size, updated when this function is successful + * @param pCommandCount pointer to returned token(compression commands) count, updated when this function is successful + * @param pSafeDist pointer to return safe distance for raw blocks, updated when this function is successful + * @param pStats pointer to compression stats that are filled if this function is successful, or NULL + * + * @return LZSA_OK for success, or an error value from lzsa_status_t + */ +lzsa_status_t lzsa_compress_stream(lzsa_stream_t *pInStream, lzsa_stream_t *pOutStream, const void *pDictionaryData, int nDictionaryDataSize, + const unsigned int nFlags, const int nMinMatchSize, const int nFormatVersion, + void(*progress)(long long nOriginalSize, long long nCompressedSize), long long *pOriginalSize, long long *pCompressedSize, int *pCommandCount, int *pSafeDist, lzsa_stats *pStats) { + unsigned char *pInData, *pOutData; + lzsa_compressor compressor; + long long nOriginalSize = 0LL, nCompressedSize = 0LL; + int nResult; + unsigned char cFrameData[16]; + int nError = 0; + int nRawPadding = (nFlags & LZSA_FLAG_RAW_BLOCK) ? 8 : 0; + + pInData = (unsigned char*)malloc(BLOCK_SIZE * 2); + if (!pInData) { + return LZSA_ERROR_MEMORY; + } + memset(pInData, 0, BLOCK_SIZE * 2); + + pOutData = (unsigned char*)malloc(BLOCK_SIZE); + if (!pOutData) { + free(pInData); + pInData = NULL; + + return LZSA_ERROR_MEMORY; + } + memset(pOutData, 0, BLOCK_SIZE); + + nResult = lzsa_compressor_init(&compressor, BLOCK_SIZE * 2, nMinMatchSize, nFormatVersion, nFlags); + if (nResult != 0) { + free(pOutData); + pOutData = NULL; + + free(pInData); + pInData = NULL; + + return LZSA_ERROR_MEMORY; + } + + if ((nFlags & LZSA_FLAG_RAW_BLOCK) == 0) { + int nHeaderSize = lzsa_encode_header(cFrameData, 16, nFormatVersion); + if (nHeaderSize < 0) + nError = LZSA_ERROR_COMPRESSION; + else { + if (pOutStream->write(pOutStream, cFrameData, nHeaderSize) != nHeaderSize) + nError = LZSA_ERROR_DST; + nCompressedSize += (long long)nHeaderSize; + } + } + + int nPreviousBlockSize = 0; + int nNumBlocks = 0; + + while (!pInStream->eof(pInStream) && !nError) { + int nInDataSize; + + if (nPreviousBlockSize) { + memcpy(pInData + BLOCK_SIZE - nPreviousBlockSize, pInData + BLOCK_SIZE, nPreviousBlockSize); + } + else if (nDictionaryDataSize && pDictionaryData) { + nPreviousBlockSize = nDictionaryDataSize; + memcpy(pInData + BLOCK_SIZE - nPreviousBlockSize, pDictionaryData, nPreviousBlockSize); + } + + nInDataSize = (int)pInStream->read(pInStream, pInData + BLOCK_SIZE, BLOCK_SIZE); + if (nInDataSize > 0) { + if ((nFlags & LZSA_FLAG_RAW_BLOCK) != 0 && nNumBlocks) { + nError = LZSA_ERROR_RAW_TOOLARGE; + break; + } + nDictionaryDataSize = 0; + + int nOutDataSize; + + nOutDataSize = lzsa_compressor_shrink_block(&compressor, pInData + BLOCK_SIZE - nPreviousBlockSize, nPreviousBlockSize, nInDataSize, pOutData, ((nInDataSize + nRawPadding) >= BLOCK_SIZE) ? BLOCK_SIZE : (nInDataSize + nRawPadding)); + if (nOutDataSize >= 0) { + /* Write compressed block */ + + if ((nFlags & LZSA_FLAG_RAW_BLOCK) == 0) { + int nBlockheaderSize = lzsa_encode_compressed_block_frame(cFrameData, 16, nOutDataSize); + if (nBlockheaderSize < 0) + nError = LZSA_ERROR_COMPRESSION; + else { + nCompressedSize += (long long)nBlockheaderSize; + if (pOutStream->write(pOutStream, cFrameData, nBlockheaderSize) != (size_t)nBlockheaderSize) { + nError = LZSA_ERROR_DST; + } + } + } + + if (!nError) { + if (pOutStream->write(pOutStream, pOutData, (size_t)nOutDataSize) != (size_t)nOutDataSize) { + nError = LZSA_ERROR_DST; + } + else { + nOriginalSize += (long long)nInDataSize; + nCompressedSize += (long long)nOutDataSize; + } + } + } + else { + /* Write uncompressible, literal block */ + + if ((nFlags & LZSA_FLAG_RAW_BLOCK) != 0) { + nError = LZSA_ERROR_RAW_UNCOMPRESSED; + break; + } + + int nBlockheaderSize = lzsa_encode_uncompressed_block_frame(cFrameData, 16, nInDataSize); + if (nBlockheaderSize < 0) + nError = LZSA_ERROR_COMPRESSION; + else { + if (pOutStream->write(pOutStream, cFrameData, nBlockheaderSize) != (size_t)nBlockheaderSize) { + nError = LZSA_ERROR_DST; + } + else { + if (pOutStream->write(pOutStream, pInData + BLOCK_SIZE, (size_t)nInDataSize) != (size_t)nInDataSize) { + nError = LZSA_ERROR_DST; + } + else { + nOriginalSize += (long long)nInDataSize; + nCompressedSize += (long long)nBlockheaderSize + (long long)nInDataSize; + } + } + } + } + + nPreviousBlockSize = nInDataSize; + nNumBlocks++; + } + + if (!nError && !pInStream->eof(pInStream)) { + if (progress) + progress(nOriginalSize, nCompressedSize); + } + } + + if (!nError) { + int nFooterSize; + + if ((nFlags & LZSA_FLAG_RAW_BLOCK) != 0) { + nFooterSize = 0; + } + else { + nFooterSize = lzsa_encode_footer_frame(cFrameData, 16); + if (nFooterSize < 0) + nError = LZSA_ERROR_COMPRESSION; + } + + if (pOutStream->write(pOutStream, cFrameData, nFooterSize) != nFooterSize) + nError = LZSA_ERROR_DST; + nCompressedSize += (long long)nFooterSize; + } + + if (progress) + progress(nOriginalSize, nCompressedSize); + + int nCommandCount = lzsa_compressor_get_command_count(&compressor); + int nSafeDist = compressor.safe_dist; + + if (pStats) + *pStats = compressor.stats; + + lzsa_compressor_destroy(&compressor); + + free(pOutData); + pOutData = NULL; + + free(pInData); + pInData = NULL; + + if (nError) { + return nError; + } + else { + if (pOriginalSize) + *pOriginalSize = nOriginalSize; + if (pCompressedSize) + *pCompressedSize = nCompressedSize; + if (pCommandCount) + *pCommandCount = nCommandCount; + if (pSafeDist) + *pSafeDist = nSafeDist; + return LZSA_OK; + } +} diff --git a/Tools/unix/lzsa/src/shrink_streaming.h b/Tools/unix/lzsa/src/shrink_streaming.h new file mode 100644 index 00000000..0920edfd --- /dev/null +++ b/Tools/unix/lzsa/src/shrink_streaming.h @@ -0,0 +1,99 @@ +/* + * shrink_streaming.h - streaming compression definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _SHRINK_STREAMING_H +#define _SHRINK_STREAMING_H + +#include "stream.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declaration */ +typedef enum _lzsa_status_t lzsa_status_t; +typedef struct _lzsa_stats lzsa_stats; + +/*-------------- File API -------------- */ + +/** + * Compress file + * + * @param pszInFilename name of input(source) file to compress + * @param pszOutFilename name of output(compressed) file to generate + * @param pszDictionaryFilename name of dictionary file, or NULL for none + * @param nFlags compression flags (LZSA_FLAG_xxx) + * @param nMinMatchSize minimum match size + * @param nFormatVersion version of format to use (1-2) + * @param progress progress function, called after compressing each block, or NULL for none + * @param pOriginalSize pointer to returned input(source) size, updated when this function is successful + * @param pCompressedSize pointer to returned output(compressed) size, updated when this function is successful + * @param pCommandCount pointer to returned token(compression commands) count, updated when this function is successful + * @param pSafeDist pointer to return safe distance for raw blocks, updated when this function is successful + * @param pStats pointer to compression stats that are filled if this function is successful, or NULL + * + * @return LZSA_OK for success, or an error value from lzsa_status_t + */ +lzsa_status_t lzsa_compress_file(const char *pszInFilename, const char *pszOutFilename, const char *pszDictionaryFilename, + const unsigned int nFlags, const int nMinMatchSize, const int nFormatVersion, + void(*progress)(long long nOriginalSize, long long nCompressedSize), long long *pOriginalSize, long long *pCompressedSize, int *pCommandCount, int *pSafeDist, lzsa_stats *pStats); + +/*-------------- Streaming API -------------- */ + +/** + * Compress stream + * + * @param pInStream input(source) stream to compress + * @param pOutStream output(compressed) stream to write to + * @param pDictionaryData dictionary contents, or NULL for none + * @param nDictionaryDataSize size of dictionary contents, or 0 + * @param nFlags compression flags (LZSA_FLAG_xxx) + * @param nMinMatchSize minimum match size + * @param nFormatVersion version of format to use (1-2) + * @param progress progress function, called after compressing each block, or NULL for none + * @param pOriginalSize pointer to returned input(source) size, updated when this function is successful + * @param pCompressedSize pointer to returned output(compressed) size, updated when this function is successful + * @param pCommandCount pointer to returned token(compression commands) count, updated when this function is successful + * @param pSafeDist pointer to return safe distance for raw blocks, updated when this function is successful + * @param pStats pointer to compression stats that are filled if this function is successful, or NULL + * + * @return LZSA_OK for success, or an error value from lzsa_status_t + */ +lzsa_status_t lzsa_compress_stream(lzsa_stream_t *pInStream, lzsa_stream_t *pOutStream, const void *pDictionaryData, int nDictionaryDataSize, + const unsigned int nFlags, const int nMinMatchSize, const int nFormatVersion, + void(*progress)(long long nOriginalSize, long long nCompressedSize), long long *pOriginalSize, long long *pCompressedSize, int *pCommandCount, int *pSafeDist, lzsa_stats *pStats); + +#ifdef __cplusplus +} +#endif + +#endif /* _SHRINK_STREAMING_H */ diff --git a/Tools/unix/lzsa/src/stream.c b/Tools/unix/lzsa/src/stream.c new file mode 100644 index 00000000..89374872 --- /dev/null +++ b/Tools/unix/lzsa/src/stream.c @@ -0,0 +1,111 @@ +/* + * stream.c - streaming I/O implementation + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#include +#include +#include +#include "stream.h" + +/** + * Close file stream + * + * @param stream stream + */ +static void lzsa_filestream_close(lzsa_stream_t *stream) { + if (stream->obj) { + fclose((FILE*)stream->obj); + stream->obj = NULL; + stream->read = NULL; + stream->write = NULL; + stream->eof = NULL; + stream->close = NULL; + } +} + +/** + * Read from file stream + * + * @param stream stream + * @param ptr buffer to read into + * @param size number of bytes to read + * + * @return number of bytes read + */ +static size_t lzsa_filestream_read(lzsa_stream_t *stream, void *ptr, size_t size) { + return fread(ptr, 1, size, (FILE*)stream->obj); +} + +/** + * Write to file stream + * + * @param stream stream + * @param ptr buffer to write from + * @param size number of bytes to write + * + * @return number of bytes written + */ +static size_t lzsa_filestream_write(lzsa_stream_t *stream, void *ptr, size_t size) { + return fwrite(ptr, 1, size, (FILE*)stream->obj); +} + +/** + * Check if file stream has reached the end of the data + * + * @param stream stream + * + * @return nonzero if the end of the data has been reached, 0 if there is more data + */ +static int lzsa_filestream_eof(lzsa_stream_t *stream) { + return feof((FILE*)stream->obj); +} + +/** + * Open file and create an I/O stream from it + * + * @param stream stream to fill out + * @param pszInFilename filename + * @param pszMode open mode, as with fopen() + * + * @return 0 for success, nonzero for failure + */ +int lzsa_filestream_open(lzsa_stream_t *stream, const char *pszInFilename, const char *pszMode) { + stream->obj = (void*)fopen(pszInFilename, pszMode); + if (stream->obj) { + stream->read = lzsa_filestream_read; + stream->write = lzsa_filestream_write; + stream->eof = lzsa_filestream_eof; + stream->close = lzsa_filestream_close; + return 0; + } + else + return -1; +} diff --git a/Tools/unix/lzsa/src/stream.h b/Tools/unix/lzsa/src/stream.h new file mode 100644 index 00000000..a8b79229 --- /dev/null +++ b/Tools/unix/lzsa/src/stream.h @@ -0,0 +1,103 @@ +/* + * stream.h - streaming I/O definitions + * + * Copyright (C) 2019 Emmanuel Marty + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +/* + * Uses the libdivsufsort library Copyright (c) 2003-2008 Yuta Mori + * + * Inspired by LZ4 by Yann Collet. https://github.com/lz4/lz4 + * With help, ideas, optimizations and speed measurements by spke + * With ideas from Lizard by Przemyslaw Skibinski and Yann Collet. https://github.com/inikep/lizard + * Also with ideas from smallz4 by Stephan Brumme. https://create.stephan-brumme.com/smallz4/ + * + */ + +#ifndef _STREAM_H +#define _STREAM_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declaration */ +typedef struct _lzsa_stream_t lzsa_stream_t; + +/* I/O stream */ +typedef struct _lzsa_stream_t { + /** Opaque stream-specific pointer */ + void *obj; + + /** + * Read from stream + * + * @param stream stream + * @param ptr buffer to read into + * @param size number of bytes to read + * + * @return number of bytes read + */ + size_t(*read)(lzsa_stream_t *stream, void *ptr, size_t size); + + /** + * Write to stream + * + * @param stream stream + * @param ptr buffer to write from + * @param size number of bytes to write + * + * @return number of bytes written + */ + size_t(*write)(lzsa_stream_t *stream, void *ptr, size_t size); + + + /** + * Check if stream has reached the end of the data + * + * @param stream stream + * + * @return nonzero if the end of the data has been reached, 0 if there is more data + */ + int(*eof)(lzsa_stream_t *stream); + + /** + * Close stream + * + * @param stream stream + */ + void(*close)(lzsa_stream_t *stream); +} lzsa_stream_t; + +/** + * Open file and create an I/O stream from it + * + * @param stream stream to fill out + * @param pszInFilename filename + * @param pszMode open mode, as with fopen() + * + * @return 0 for success, nonzero for failure + */ +int lzsa_filestream_open(lzsa_stream_t *stream, const char *pszInFilename, const char *pszMode); + +#ifdef __cplusplus +} +#endif + +#endif /* _STREAM_H */ diff --git a/Tools/unix/uz80as/Makefile b/Tools/unix/uz80as/Makefile new file mode 100644 index 00000000..712c47b0 --- /dev/null +++ b/Tools/unix/uz80as/Makefile @@ -0,0 +1,72 @@ +# =========================================================================== +# uz80as, an assembler for the Zilog Z80 and several other microprocessors. +# =========================================================================== + +DEST = ../../`uname` +CC = gcc +CFLAGS = -g + +OBJECTS = ngetopt.o main.o options.o \ + utils.o err.o incl.o sym.o \ + expr.o exprint.o pp.o list.o \ + prtable.o uz80as.o targets.o \ + z80.o gbcpu.o \ + dp2200.o i4004.o \ + i8008.o i8048.o \ + i8051.o i8080.o \ + mos6502.o mc6800.o + +SOURCES = \ + config.h \ + ngetopt.c ngetopt.h \ + main.c \ + options.c options.h \ + utils.c utils.h \ + err.c err.h \ + incl.c incl.h \ + sym.c sym.h \ + expr.c expr.h \ + exprint.c exprint.h \ + pp.c pp.h \ + list.c list.h \ + prtable.c prtable.h \ + uz80as.c uz80as.h \ + targets.c targets.h \ + z80.c \ + gbcpu.c \ + dp2200.c \ + i4004.c \ + i8008.c \ + i8048.c \ + i8051.c \ + i8080.c \ + mos6502.c \ + mc6800.c + +all: uz80as $(DEST) + cp uz80as $(DEST) + +$(DEST): + mkdir -p $(DEST) + +clobber: clean + -rm -f uz80as $(DEST)/uz80as + +clean: + -rm -f $(OBJECTS) + +uz80as: $(OBJECTS) + $(CC) $(CFLAGS) -o uz80as $(OBJECTS) + +TESTS=test*.asm + +test: uz80as + echo "running tests" + for i in $(TESTS) ; do \ + echo "====== $$i ======" ; \ + ./uz80as $$i ; \ + cat $$(basename $$i .asm).lst ; \ + done + +.c.o: + $(CC) $(CFLAGS) -I. -c $< -o $@ diff --git a/Tools/unix/uz80as/config.h b/Tools/unix/uz80as/config.h new file mode 100644 index 00000000..cca14cbd --- /dev/null +++ b/Tools/unix/uz80as/config.h @@ -0,0 +1,29 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Years of copyright */ +#define COPYRIGHT_YEARS "2018" + +/* Name of package */ +#define PACKAGE "uz80as" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "jorge.giner@hotmail.com" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "uz80as" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "uz80as 1.10" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "uz80as" + +/* Define to the home page for this package. */ +#define PACKAGE_URL "https://jorgicor.niobe.org/uz80as" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "1.10" + +/* Version number of package */ +#define VERSION "1.10" diff --git a/Tools/unix/uz80as/dp2200.c b/Tools/unix/uz80as/dp2200.c new file mode 100644 index 00000000..c6599247 --- /dev/null +++ b/Tools/unix/uz80as/dp2200.c @@ -0,0 +1,208 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Datapoint 2200. + * =========================================================================== + */ + +/* + * Datapoint 2200 Version I, 2K to 8K mem (program counter 13 bits). + * Datapoint 2200 Version II, 2K to 16K mem (protram counter 14 bits). + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: ADA,ADB,ADC,ADD,ADH,ADL,ADM, + * ACA,ACB,ACC,ACD,ACH,ACL,ACM, + * SUA,SUB,SUC,SUD,SUH,SUL,SUM, + * SBA,SBB,SBC,SBD,SBH,SBL,SBM, + * NDA,NDB,NDC,NDD,NDH,NDL,NDM, + * XRA,XRB,XRC,XRD,XRH,XRL,XRM, + * ORA,ORB,ORC,ORD,ORH,ORL,ORM, + * CPA,CPB,CPC,CPD,CPH,CPL,CPM + * c: NOP,LAB,LAC,LAD,LAE,LAH,LAL,LAM, + * LBA,LBC,LBD,LBE,LBH,LBL,LBM, + * LCA,LCB,LCD,LCE,LCH,LCL,LCM, + * LDA,LDB,LDC,LDE,LDH,LDL,LDM, + * LEA,LEB,LEC,LED,LEH,LEL,LEM, + * LHA,LHB,LHC,LHD,LHE,LHL,LHM, + * LLA,LLB,LLC,LLD,LLE,LLH,LLM, + * LMA,LMB,LMC,LMD,LME,LMH,LML,HALT + * d: ADR,STATUS,DATA,WRITE,COM1,COM2,COM3,COM4 + * BEEP,CLICK,DECK1,DECK2, + * RBK,WBK,BSP,SF,SB,REWND,TSTOP + * e: RFC,RFS,RTC,RTS,RFZ,RFP,RTZ,RTP + * f: JFC,JFZ,JFS,JFP,JTC,JTZ,JTS,JTP + * g: AD,SU,ND,OR,AC,SB,XR,CP + * h: LA,LB,LC,LD,LE,LH,LL + * i: CFC,CFZ,CFS,CFP,CTC,CTZ,CTS,CTP + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: (op << 1) + lastbyte + * g: (op << 4) | lastbyte + */ + +const struct matchtab s_matchtab_dp2200[] = { + { "SLC", "02.", 3, 0 }, + { "SRC", "0A.", 3, 0 }, + { "RETURN", "07.", 3, 0 }, + { "INPUT", "41.", 3, 0 }, + { "b", "80c0.", 3, 0 }, + { "c", "C0c0.", 3, 0 }, + { "EX d", "51f0.", 3, 0 }, + { "e", "03b0.", 3, 0 }, + { "g a", "04b0.d1.", 3, 0, "e8" }, + { "h a", "06b0.d1.", 3, 0, "e8"}, + { "f a", "40b0.e1", 3, 0 }, + { "i a", "42b0.e1", 3, 0 }, + { "JMP a", "44.e0", 3, 0 }, + { "CALL a", "46.e0", 3, 0 }, + /* version II */ + { "BETA", "10.", 2, 0 }, + { "DI", "20.", 2, 0 }, + { "POP", "30.", 2, 0 }, + { "ALPHA", "18.", 2, 0 }, + { "EI", "28.", 2, 0 }, + { "PUSH", "38.", 2, 0 }, + { NULL, NULL }, +}; + +static const char *const bval[] = { +"ADA", "ADB", "ADC", "ADD", "ADE", "ADH", "ADL", "ADM", +"ACA", "ACB", "ACC", "ACD", "ACE", "ACH", "ACL", "ACM", +"SUA", "SUB", "SUC", "SUD", "SUE", "SUH", "SUL", "SUM", +"SBA", "SBB", "SBC", "SBD", "SBE", "SBH", "SBL", "SBM", +"NDA", "NDB", "NDC", "NDD", "NDE", "NDH", "NDL", "NDM", +"XRA", "XRB", "XRC", "XRD", "XRE", "XRH", "XRL", "XRM", +"ORA", "ORB", "ORC", "ORD", "ORE", "ORH", "ORL", "ORM", +"CPA", "CPB", "CPC", "CPD", "CPE", "CPH", "CPL", "CPM", +NULL }; + +static const char *const cval[] = { +"NOP", "LAB", "LAC", "LAD", "LAE", "LAH", "LAL", "LAM", +"LBA", "", "LBC", "LBD", "LBE", "LBH", "LBL", "LBM", +"LCA", "LCB", "", "LCD", "LCE", "LCH", "LCL", "LCM", +"LDA", "LDB", "LDC", "", "LDE", "LDH", "LDL", "LDM", +"LEA", "LEB", "LEC", "LED", "", "LEH", "LEL", "LEM", +"LHA", "LHB", "LHC", "LHD", "LHE", "", "LHL", "LHM", +"LLA", "LLB", "LLC", "LLD", "LLE", "LLH", "", "LLM", +"LMA", "LMB", "LMC", "LMD", "LME", "LMH", "LML", "HALT", +NULL }; + +static const char *const dval[] = { +"ADR", "STATUS", "DATA", "WRITE", "COM1", "COM2", "COM3", "COM4", +"", "", "", "", "BEEP", "CLICK", "DECK1", "DECK2", +"RBK", "WBK", "", "BSP", "SF", "SB", "REWND", "TSTOP", +NULL }; + +static const char *const eval[] = { "RFC", "RFZ", "RFS", "RFP", + "RTC", "RTZ", "RTS", "RTP", + NULL }; + +static const char *const fval[] = { "JFC", "JFZ", "JFS", "JFP", + "JTC", "JTZ", "JTS", "JTP", + NULL }; + +static const char *const gval[] = { "AD", "AC", "SU", "SB", + "ND", "XR", "OR", "CP", + NULL }; + +static const char *const hval[] = { "LA", "LB", "LC", "LD", + "LE", "LH", "LL", + NULL }; + +static const char *const ival[] = { "CFC", "CFZ", "CFS", "CFP", + "CTC", "CTZ", "CTS", "CTP", + NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval, eval, fval, + gval, hval, ival +}; + +static int match_dp2200(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'i') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_dp2200(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': b += (vs[i] << 1); break; + case 'g': b |= (vs[i] << 4); break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_dp2200(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_dp2200(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'n') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_dp2200 = { + .id = "dp2200", + .descr = "Datapoint 2200 Version I", + .matcht = s_matchtab_dp2200, + .matchf = match_dp2200, + .genf = gen_dp2200, + .pat_char_rewind = pat_char_rewind_dp2200, + .pat_next_str = pat_next_str_dp2200, + .mask = 1 +}; + +const struct target s_target_dp2200ii = { + .id = "dp2200ii", + .descr = "Datapoint 2200 Version II", + .matcht = s_matchtab_dp2200, + .matchf = match_dp2200, + .genf = gen_dp2200, + .pat_char_rewind = pat_char_rewind_dp2200, + .pat_next_str = pat_next_str_dp2200, + .mask = 2 +}; diff --git a/Tools/unix/uz80as/err.c b/Tools/unix/uz80as/err.c new file mode 100644 index 00000000..b6745ae8 --- /dev/null +++ b/Tools/unix/uz80as/err.c @@ -0,0 +1,196 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Error reporting. + * =========================================================================== + */ + +#include "config.h" +#include "err.h" +#include "incl.h" + +#ifndef ASSERT_H +#include +#endif + +#ifndef CTYPE_H +#include +#endif + +#ifndef STDARG_H +#include +#endif + +#ifndef STDIO_H +#include +#endif + +#ifndef STDLIB_H +#include +#endif + +#ifndef STRING_H +#include +#endif + +/* Max number of errors before halt. */ +#define MAXERR 64 + +int s_nerrors; + +static void eprfl(void) +{ + fprintf(stderr, "%s:%d: ", curfile()->name, curfile()->linenum); +} + +static void eprwarn(void) +{ + fputs(_("warning:"), stderr); + fputc(' ', stderr); +} + +/* Print the characters in [p, q[ to stderr. */ +void echars(const char *p, const char *q) +{ + while (*p != '\0' && p != q) { + fputc(*p, stderr); + p++; + } +} + +/* + * Print a space, an opening parenthesis, the characters in [p, q[, + * and a closing parenthesis to stderr. + */ +void epchars(const char *p, const char *q) +{ + fputs(" (", stderr); + echars(p, q); + fputs(")", stderr); +} + +/* + * Increments the number of errors, and exit with failure if + * maximum number of errors allowed is reached. + */ +void newerr(void) +{ + s_nerrors++; + if (s_nerrors >= MAXERR) { + eprogname(); + fprintf(stderr, _("exiting: too many errors")); + enl(); + exit(EXIT_FAILURE); + } +} + +static void evprint(int warn, const char *ecode, va_list args) +{ + if (nfiles() > 0) + eprfl(); + else + eprogname(); + + if (warn) + eprwarn(); + + assert(ecode != NULL); + vfprintf(stderr, ecode, args); +} + +/* Prints only the printable characters, the rst as space. */ +static void eprint_printable(const char *p) +{ + for (; *p != '\0'; p++) { + if (isprint(*p)) + putc(*p, stderr); + else + putc(' ', stderr); + } +} + +/* Prints the line and a marker pointing to the charcater q inside line. */ +void eprcol(const char *line, const char *q) +{ + putc(' ', stderr); + eprint_printable(line); + fputs("\n ", stderr); + while (line != q) { + putc(' ', stderr); + line++; + } + fputs("^\n", stderr); +} + +/* + * Like fprintf but prints to stderr. + * If we are parsing any file (incl.c), print first the file and the line. + * If not, print first the program name. + */ +void eprint(const char *ecode, ...) +{ + va_list args; + + va_start(args, ecode); + evprint(0, ecode, args); + va_end(args); +} + +/* Same as eprint, but print "warning: " before ecode str. */ +void wprint(const char *ecode, ...) +{ + va_list args; + + va_start(args, ecode); + evprint(1, ecode, args); + va_end(args); +} + +/* Print \n on stderr. */ +void enl(void) +{ + fputc('\n', stderr); +} + +/* Print the program name on stderr. */ +void eprogname(void) +{ + fprintf(stderr, PACKAGE": "); +} + +/* Call malloc, but if no memory, print that error and exit with failure. */ +void *emalloc(size_t n) +{ + void *p; + + if ((p = malloc(n)) == NULL) { + eprint(_("malloc fail\n")); + exit(EXIT_FAILURE); + } + // printf("emalloc: %d = %x\n", n, p); + return p; +} + +/* Call realloc, but if no memory, print that error and exit with failure. */ +void *erealloc(void *p, size_t n) +{ + // void *q = p; + if ((p = realloc(p, n)) == NULL) { + eprint(_("realloc fail\n")); + exit(EXIT_FAILURE); + } + // printf("erealloc: %x %d = %x\n", q, n, p); + return p; +} + +/* Call fopen, but if any error, print it and exit with failure. */ +FILE *efopen(const char *fname, const char *ops) +{ + FILE *fp; + + if ((fp = fopen(fname, ops)) == NULL) { + eprint(_("cannot open file %s\n"), fname); + exit(EXIT_FAILURE); + } + return fp; +} diff --git a/Tools/unix/uz80as/err.h b/Tools/unix/uz80as/err.h new file mode 100644 index 00000000..86d8e1dc --- /dev/null +++ b/Tools/unix/uz80as/err.h @@ -0,0 +1,32 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Error reporting. + * =========================================================================== + */ + +#ifndef ERR_H +#define ERR_H + +#ifndef STDIO_H +#define STDIO_H +#include +#endif + +#define _(str) (str) + +extern int s_nerrors; + +void newerr(void); +void eprogname(void); +void echars(const char *p, const char *q); +void epchars(const char *p, const char *q); +void eprint(const char *ecode, ...); +void wprint(const char *ecode, ...); +void eprcol(const char *line, const char *q); +void enl(void); +void *emalloc(size_t n); +void *erealloc(void *p, size_t n); +FILE *efopen(const char *fname, const char *ops); + +#endif diff --git a/Tools/unix/uz80as/expr.c b/Tools/unix/uz80as/expr.c new file mode 100644 index 00000000..445e36f0 --- /dev/null +++ b/Tools/unix/uz80as/expr.c @@ -0,0 +1,459 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Expression parsing. + * =========================================================================== + */ + +#include "config.h" +#include "expr.h" +#include "utils.h" +#include "err.h" +#include "sym.h" + +#ifndef ASSERT_H +#include +#endif + +#ifndef CTYPE_H +#include +#endif + +#ifndef LIMITS_H +#include +#endif + +#ifndef STDIO_H +#include +#endif + +#ifndef STDLIB_H +#include +#endif + +/* Max nested expressions. */ +#define ESTKSZ 16 +#define ESTKSZ2 (ESTKSZ*2) + +/* Return -1 on syntax error. + * *p must be a digit already. + * *q points to one past the end of the number without suffix. + */ +static int takenum(const char *p, const char *q, int radix) +{ + int k, n; + + n = 0; + while (p != q) { + k = hexval(*p); + p++; + if (k >= 0 && k < radix) + n = n * radix + k; + else + return -1; + } + return n; +} + +/* Go to the end of a number (advance all digits or letters). */ +static const char *goendnum(const char *p) +{ + const char *q; + + for (q = p; isalnum(*q); q++) + ; + return q; +} + +/* + * Returns NULL on error. + * '*p' must be a digit already. + */ +static const char *getnum(const char *p, int *v) +{ + int n; + char c; + const char *q; + + assert(isdigit(*p)); + + n = 0; + q = goendnum(p) - 1; + if (isalpha(*q)) { + c = toupper(*q); + if (c == 'H') { + n = takenum(p, q, 16); + } else if (c == 'D') { + n = takenum(p, q, 10); + } else if (c == 'O') { + n = takenum(p, q, 8); + } else if (c == 'B') { + n = takenum(p, q, 2); + } else { + return NULL; + } + } else { + n = takenum(p, q + 1, 10); + } + + if (n < 0) + return NULL; + + *v = n; + return q + 1; +} + +/* + * Gets a number that was prefixed. + * Returns NULL on error. + */ +static const char *getpnum(const char *p, int radix, int *v) +{ + const char *q; + int n; + + q = goendnum(p); + n = takenum(p, q, radix); + if (n < 0) + return NULL; + *v = n; + return q; +} + +/* Left shift */ +static int shl(int r, int n) +{ + n &= int_precission(); + return r << n; +} + + +/* Portable arithmetic right shift. */ +static int ashr(int r, int n) +{ + n &= int_precission(); + if (r & INT_MIN) { + return ~(~r >> n); + } else { + return r >> n; + } +} + +/* Parses expression pointed by 'p'. + * If success, returns pointer to the end of parsed expression, and + * 'v' contains the calculated value of the expression. + * Returns NULL if a syntactic error has occurred. + * Operators are evaluated left to right. + * To allow precedence use parenthesis. + * 'linepc' is the program counter to consider when we find the $ current + * pointer location symbol ($). + * 'allowfr' stands for 'allow forward references'. We will issue an error + * if we find a label that is not defined. + * 'ecode' will be valid if NULL is returned. NULL can be passed as ecode. + * 'ep' is the pointer to the position where the error ocurred. NULL can be + * passed as ep. + */ +const char *expr(const char *p, int *v, int linepc, int allowfr, + enum expr_ecode *ecode, const char **ep) +{ + int si, usi, usl; + const char *q; + char last; + int stack[ESTKSZ2]; + int uopstk[ESTKSZ]; + int r, n; + struct sym *sym; + int err; + enum expr_ecode ec; + + ec = EXPR_E_NO_EXPR; + err = 0; + usi = 0; + si = 0; + r = 0; + last = 'V'; /* first void */ + usl = 0; +loop: + p = skipws(p); + if (*p == '(') { + if (last == 'n') { + goto end; + } else { + if (si >= ESTKSZ2) { + eprint(_("expression too complex\n")); + exit(EXIT_FAILURE); + } + stack[si++] = last; + stack[si++] = r; + stack[si++] = usl; + usl = usi; + p++; + r = 0; + last = 'v'; /* void */ + } + } else if (*p == ')') { + if (last != 'n') { + ec = EXPR_E_CPAR; + goto esyntax; + } else if (si == 0) { + goto end; + } else { + p++; + n = r; + usl = stack[--si]; + r = stack[--si]; + last = (char) stack[--si]; + goto oper; + } + } else if (*p == '+') { + p++; + if (last == 'n') + last = '+'; + } else if (*p == '-') { + if (last == 'n') { + p++; + last = '-'; + } else { + goto uoper; + } + } else if (*p == '~') { + goto uoper; + } else if (*p == '!') { + if (*(p + 1) == '=') { + if (last != 'n') { + ec = EXPR_E_OPER; + goto esyntax; + } else { + p += 2; + last = 'N'; + } + } else { + goto uoper; + } + } else if (*p == '*') { + if (last == 'n') { + last = *p++; + } else { + p++; + n = linepc; + goto oper; + } + } else if (*p == '/' || *p == '&' || *p == '|' + || *p == '^') + { + if (last != 'n') { + ec = EXPR_E_OPER; + goto esyntax; + } else { + last = *p++; + } + } else if (*p == '>') { + if (last != 'n') { + ec = EXPR_E_OPER; + goto esyntax; + } + p++; + if (*p == '=') { + last = 'G'; + p++; + } else if (*p == '>') { + last = 'R'; + p++; + } else { + last = '>'; + } + } else if (*p == '<') { + if (last != 'n') { + ec = EXPR_E_OPER; + goto esyntax; + } + p++; + if (*p == '=') { + last = 'S'; + p++; + } else if (*p == '<') { + last = 'L'; + p++; + } else { + last = '<'; + } + } else if (*p == '=') { + if (last != 'n') { + ec = EXPR_E_OPER; + goto esyntax; + } + p++; + if (*p == '=') + p++; + last = '='; + } else if (*p == '\'') { + if (last == 'n') + goto end; + p++; + n = *p++; + if (*p != '\'') { + ec = EXPR_E_CHAR; + goto esyntax; + } + p++; + goto oper; + } else if (*p == '$') { + if (last == 'n') + goto end; + p++; + if (hexval(*p) < 0) { + n = linepc; + goto oper; + } + q = getpnum(p, 16, &n); + if (q == NULL) { + p--; + ec = EXPR_E_HEX; + goto esyntax; + } + p = q; + goto oper; + } else if (*p == '@') { + if (last == 'n') + goto end; + p++; + q = getpnum(p, 8, &n); + if (q == NULL) { + p--; + ec = EXPR_E_OCTAL; + goto esyntax; + } + p = q; + goto oper; + } else if (*p == '%') { + if (last == 'n') { + last = *p; + p++; + } else { + p++; + q = getpnum(p, 2, &n); + if (q == NULL) { + ec = EXPR_E_BIN; + goto esyntax; + } + p = q; + goto oper; + } + } else if ((p[0] == '0') && (p[1] == 'x')) { + p+=2; + q = getpnum(p, 16, &n); + if (q == NULL) { + p--; + ec = EXPR_E_HEX; + goto esyntax; + } + p = q; + goto oper; + } else if (isdigit(*p)) { + if (last == 'n') + goto end; + q = getnum(p, &n); + if (q == NULL) { + ec = EXPR_E_DEC; + goto esyntax; + } + p = q; + goto oper; + } else if (isidc0(*p)) { + if (last == 'n') + goto end; + q = p; + while (isidc(*p)) + p++; + sym = lookup(q, p, 0, 0); + if (sym == NULL) { + n = 0; + if (!allowfr) { + err = 1; + eprint(_("undefined label")); + epchars(q, p); + enl(); + newerr(); + } + } else { + n = sym->val; + } + goto oper; + } else if (last == 'V') { + goto esyntax; + } else if (last != 'n') { + ec = EXPR_E_SYNTAX; + goto esyntax; + } else { +end: if (v != NULL) + *v = r; + return p; + } + goto loop; +uoper: + if (last == 'n') + goto end; + if (usi >= ESTKSZ) { + eprint(_("expression too complex\n")); + exit(EXIT_FAILURE); + } + uopstk[usi++] = *p++; + goto loop; +oper: + while (usi > usl) { + usi--; + switch (uopstk[usi]) { + case '~': n = ~n; break; + case '-': n = -n; break; + case '!': n = !n; break; + } + } + switch (last) { + case 'V': r = n; break; + case 'v': r = n; break; + case '+': r += n; break; + case '-': r -= n; break; + case '*': r *= n; break; + case '&': r &= n; break; + case '|': r |= n; break; + case '^': r ^= n; break; + case '=': r = r == n; break; + case '<': r = r < n; break; + case '>': r = r > n ; break; + case 'G': r = r >= n; break; + case 'S': r = r <= n; break; + case 'N': r = r != n; break; + /* This would be logical right shift: + * case 'R': r = (unsigned int) r >> n; break; + */ + case 'R': r = ashr(r, n); break; + case 'L': r = shl(r, n); break; + case '~': r = ~n; break; + case '%': + if (n != 0) { + r %= n; + } else if (!err && !allowfr) { + err = 1; + eprint(_("modulo by zero\n")); + exit(EXIT_FAILURE); + } + break; + case '/': + if (n != 0) { + r /= n; + } else if (!err && !allowfr) { + err = 1; + eprint(_("division by zero\n")); + exit(EXIT_FAILURE); + } + break; + } + last = 'n'; + goto loop; +esyntax: + if (ecode != NULL) + *ecode = ec; + if (ep != NULL) + *ep = p; + return NULL; +} diff --git a/Tools/unix/uz80as/expr.h b/Tools/unix/uz80as/expr.h new file mode 100644 index 00000000..e1e55fc2 --- /dev/null +++ b/Tools/unix/uz80as/expr.h @@ -0,0 +1,26 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Expression parsing. + * =========================================================================== + */ + +#ifndef EXPR_H +#define EXPR_H + +enum expr_ecode { + EXPR_E_NO_EXPR, /* There was no expression parsed. */ + EXPR_E_SYNTAX, /* Syntax error. */ + EXPR_E_CPAR, + EXPR_E_OPER, + EXPR_E_CHAR, + EXPR_E_HEX, + EXPR_E_OCTAL, + EXPR_E_BIN, + EXPR_E_DEC, +}; + +const char *expr(const char *p, int *v, int linepc, int allowfr, + enum expr_ecode *ecode, const char **ep); + +#endif diff --git a/Tools/unix/uz80as/exprint.c b/Tools/unix/uz80as/exprint.c new file mode 100644 index 00000000..67699636 --- /dev/null +++ b/Tools/unix/uz80as/exprint.c @@ -0,0 +1,32 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Expression error reporting. + * =========================================================================== + */ + +#include "config.h" +#include "exprint.h" +#include "err.h" + +static const char *expr_get_error_str(enum expr_ecode ecode) +{ + switch (ecode) { + case EXPR_E_NO_EXPR: return _("expression expected\n"); + case EXPR_E_SYNTAX: return _("syntax error in expression\n"); + case EXPR_E_CPAR: return _("unexpected ')'\n"); + case EXPR_E_OPER: return _("misplaced operator\n"); + case EXPR_E_CHAR: return _("invalid character code\n"); + case EXPR_E_HEX: return _("invalid hexadecimal constant\n"); + case EXPR_E_OCTAL: return _("invalid octal constant\n"); + case EXPR_E_BIN: return _("invalid binary constant\n"); + case EXPR_E_DEC: return _("invalid decimal constant\n"); + default: return "\n"; + } +} + +void exprint(enum expr_ecode ecode, const char *pline, const char *ep) +{ + eprint(expr_get_error_str(ecode)); + eprcol(pline, ep); +} diff --git a/Tools/unix/uz80as/exprint.h b/Tools/unix/uz80as/exprint.h new file mode 100644 index 00000000..00b43c2f --- /dev/null +++ b/Tools/unix/uz80as/exprint.h @@ -0,0 +1,17 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Expression error reporting. + * =========================================================================== + */ + +#ifndef EXPRINT_H +#define EXPRINT_H + +#ifndef EXPR_H +#include "expr.h" +#endif + +void exprint(enum expr_ecode ecode, const char *pline, const char *ep); + +#endif diff --git a/Tools/unix/uz80as/gbcpu.c b/Tools/unix/uz80as/gbcpu.c new file mode 100644 index 00000000..d6b347f3 --- /dev/null +++ b/Tools/unix/uz80as/gbcpu.c @@ -0,0 +1,301 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Sharp LR35902 (Nintendo Gameboy CPU). + * =========================================================================== + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +#if 0 +Opcode Z80 GMB + --------------------------------------- + 08 EX AF,AF LD (nn),SP + 10 DJNZ PC+dd STOP + 22 LD (nn),HL LDI (HL),A + 2A LD HL,(nn) LDI A,(HL) + 32 LD (nn),A LDD (HL),A + 3A LD A,(nn) LDD A,(HL) + D3 OUT (n),A - + D9 EXX RETI + DB IN A,(n) - + DD - + E0 RET PO LD (FF00+n),A + E2 JP PO,nn LD (FF00+C),A + E3 EX (SP),HL - + E4 CALL PO,nn - + E8 RET PE ADD SP,dd + EA JP PE,nn LD (nn),A + EB EX DE,HL - + EC CALL PE,nn - + ED - + F0 RET P LD A,(FF00+n) + F2 JP P,nn LD A,(FF00+C) + F4 CALL P,nn - + F8 RET M LD HL,SP+dd + FA JP M,nn LD A,(nn) + FC CALL M,nn - + FD - + CB3X SLL r/(HL) SWAP r/(HL) +#endif + +/* pat: + * a: expr + * b: B,C,D,E,H,L,A + * c: * + * d: BC,DE,HL,SP + * e: * + * f: BC,DE,HL,AF + * g: ADD,ADC,SUB,SBC,AND,XOR,OR,CP + * h: INC,DEC + * i: * + * j: * + * k: * + * l: BIT,RES,SET + * m: * + * n: NZ,Z,NC,C + * o: RLC,RRC,RL,RR,SLA,SRA,SWAP,SRL + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: (op << 4) | lastbyte + * g: (op << 6) | lastbyte + * h: if op >= FF00 output last byte and then op as 8 bit value; + * else output (lastbyte | 0x0A) and output op as word + * (no '.' should follow) + * i: relative jump to op + * j: possible value to RST + * k: possible value to IM + * l: * + * m: check arithmetic used with A register + * n: check arithmetic used without A register + */ + +const struct matchtab s_matchtab_gbcpu[] = { + { "LD b,b", "40b0c1.", 1, 0 }, + { "LD b,(HL)", "46b0.", 1, 0 }, + { "LD A,(C)", "F2.", 1, 0 }, // * LD A,(FF00+C) + { "LD A,(BC)", "0A.", 1, 0 }, + { "LD A,(DE)", "1A.", 1, 0 }, + { "LD A,(HLI)", "2A.", 1, 0 }, // * + { "LD A,(HLD)", "3A.", 1, 0 }, // * + { "LD A,(a)", "F0h0", 1, 0 }, // * LD A,(nn) & LD A,(FF00+n) + { "LD b,a", "06b0.d1.", 1, 0, "e8" }, + { "LD SP,HL", "F9.", 1, 0 }, + { "LDHL SP,a", "F8.d0.", 1, 0, "e8" }, // * LD HL,SP+n + { "LD d,a", "01f0.e1", 1, 0 }, + { "LD (C),A", "E2.", 1, 0 }, // * LD (FF00+C),A + { "LD (HL),b", "70c0.", 1, 0 }, + { "LD (HL),a", "36.d0.", 1, 0, "e8" }, + { "LD (HLI),A", "22.", 1, 0 }, // * + { "LD (HLD),A", "32.", 1, 0 }, // * + { "LD (BC),A", "02.", 1, 0 }, + { "LD (DE),A", "12.", 1, 0 }, + { "LD (a),A", "E0h0", 1, 0 }, // * LD (nn),A & LD (FF00+n),A + { "LD (a),SP", "08.e0", 1, 0 }, // * + { "LDH A,(a)", "F0.d0.", 1, 0, "e8" }, // * LD A,(FF00+n) + { "LDH (a),A", "E0.d0.", 1, 0, "e8" }, // * LD (FF00+n),A + { "PUSH f", "C5f0.", 1, 0 }, + { "POP f", "C1f0.", 1, 0 }, + { "ADD HL,d", "09f0.", 1, 0 }, + { "ADD SP,a", "E8.d0.", 1, 0, "e8" }, // * + { "g A,b", "m080b0c1.", 1, 0 }, + { "g A,(HL)", "m086b0.", 1, 0 }, + { "g A,a", "m0C6b0.d1.", 1, 0, "e8" }, + { "g b", "n080b0c1.", 1, 0 }, + { "g (HL)", "n086b0.", 1, 0 }, + { "g a", "n0C6b0.d1.", 1, 0, "e8" }, + { "h b", "04b1c0.", 1, 0 }, + { "h (HL)", "34c0.", 1, 0 }, + { "INC d", "03f0.", 1, 0 }, + { "DEC d", "0Bf0.", 1, 0 }, + { "DAA", "27.", 1, 0 }, + { "CPL", "2F.", 1, 0 }, + { "CCF", "3F.", 1, 0 }, + { "SCF", "37.", 1, 0 }, + { "NOP", "00.", 1, 0 }, + { "HALT", "76.", 1, 0 }, + { "DI", "F3.", 1, 0 }, + { "EI", "FB.", 1, 0 }, + { "RLCA", "07.", 1, 0 }, + { "RLA", "17.", 1, 0 }, + { "RRCA", "0F.", 1, 0 }, + { "RRA", "1F.", 1, 0 }, + { "o b", "CB.00b0c1.", 1, 0 }, + { "o (HL)", "CB.06b0.", 1, 0 }, + { "l a,b", "CB.00g0b1c2.", 1, 0, "b3" }, + { "l a,(HL)", "CB.06g0b1.", 1, 0, "b3" }, + { "JP (HL)", "E9.", 1, 0 }, + { "JP n,a", "C2b0.e1", 1, 0 }, // * + { "JP a", "C3.e0", 1, 0 }, + { "JR n,a", "20b0.i1.", 1, 0, "r8" }, + { "JR a", "18.i0.", 1, 0, "r8" }, + { "STOP", "10.00.", 1, 0 }, // * + { "CALL n,a", "C4b0.e1", 1, 0 }, // * + { "CALL a", "CD.e0", 1, 0 }, + { "RETI", "D9.", 1, 0 }, // * + { "RET n", "C0b0.", 1, 0 }, + { "RET", "C9.", 1, 0 }, + { "RST a", "C7j0.", 1, 0, "ss" }, + { NULL, NULL }, +}; + +static const char *const bval[] = { "B", "C", "D", "E", + "H", "L", "", "A", NULL }; +static const char *const dval[] = { "BC", "DE", "HL", "SP", NULL }; +static const char *const fval[] = { "BC", "DE", "HL", "AF", NULL }; +static const char *const gval[] = { "ADD", "ADC", "SUB", "SBC", + "AND", "XOR", "OR", "CP", NULL }; +static const char *const hval[] = { "INC", "DEC", NULL }; +static const char *const lval[] = { "", "BIT", "RES", "SET", NULL }; +static const char *const nval[] = { "NZ", "Z", "NC", "C", NULL }; +static const char *const oval[] = { "RLC", "RRC", "RL", "RR", + "SLA", "SRA", "SWAP", "SRL", NULL }; +static const char *const nullv[] = { NULL }; + +static const char *const *const valtab[] = { + bval, nullv, dval, nullv, fval, + gval, hval, nullv, nullv, nullv, + lval, nullv, nval, oval +}; + +static int match_gbcpu(char c, const char *p, const char **q) +{ + int v; + + switch (c) { + case 'b': + case 'd': + case 'f': + case 'g': + case 'h': + case 'l': + case 'n': + case 'o': + v = mreg(p, valtab[(int) (c - 'b')], q); + break; + default: + v = -1; + } + + return v; +} + +static int gen_gbcpu(int *eb, char p, const int *vs, int i, int savepc) +{ + int w, b; + + b = *eb; + switch (p) { + case 'f': b |= (vs[i] << 4); break; + case 'g': b |= (vs[i] << 6); break; + case 'h': w = vs[i] & 0xffff; + if (w >= 0xff00) { + genb(b, s_pline_ep); + b = 0; + genb(w & 0xff, s_pline_ep); + } else { + b |= 0x0A; + genb(b, s_pline_ep); + b = 0; + genb(w & 0xff, s_pline_ep); + genb(w >> 8, s_pline_ep); + } + break; + case 'i': b = (vs[i] - savepc - 2); break; + case 'j': if (s_pass > 0 && (vs[i] & ~56) != 0) { + eprint(_("invalid RST argument (%d)\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= vs[i]; + break; + case 'k': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 2)) { + eprint(_("invalid IM argument (%d)\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b = 0x46; + if (vs[i] == 1) + b = 0x56; + else if (vs[i] == 2) + b = 0x5E; + break; + case 'm': if (s_pass == 0 && !s_extended_op) { + if (vs[i] != 0 && vs[i] != 1 && vs[i] != 3) { + eprint(_("unofficial syntax\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + } + } + break; + case 'n': if (s_pass == 0 && !s_extended_op) { + if (vs[i] == 0 || vs[i] == 1 || vs[i] == 3) { + eprint(_("unofficial syntax\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + } + } + break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_gbcpu(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_gbcpu(void) +{ + const char *s; + + switch (s_pat_char) { + case 'b': + case 'd': + case 'f': + case 'g': + case 'h': + case 'l': + case 'n': + case 'o': + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + break; + default: + s = NULL; + } + + return s; +}; + +const struct target s_target_gbcpu = { + .id = "gbcpu", + .descr = "Sharp LR35902 (Nintendo Gameboy CPU)", + .matcht = s_matchtab_gbcpu, + .matchf = match_gbcpu, + .genf = gen_gbcpu, + .pat_char_rewind = pat_char_rewind_gbcpu, + .pat_next_str = pat_next_str_gbcpu, + .mask = 1 +}; diff --git a/Tools/unix/uz80as/i4004.c b/Tools/unix/uz80as/i4004.c new file mode 100644 index 00000000..98649abd --- /dev/null +++ b/Tools/unix/uz80as/i4004.c @@ -0,0 +1,189 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Intel 4004. + * Intel 4040. + * =========================================================================== + */ + +/* Intel 4004. Max. memory 4K (12 bit addresses). + * Intel 4040. Max. memory 8K (13 bit addresses). + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: ADD,SUB,LD,XCH,BBL,LDM + * c: WRM,WMP,WRR,WPM,WR0,WR1,WR2,WR3 + * SBM,RDM,RDR,ADM,RD0,RD1,RD2,RD3 + * d: CLB,CLC,IAC,CMC,CMA,RAL,RAR,TCC, + * DAC,TCS,STC,DAA,KBP,DCL, + * e: HLT,BBS,LCR,OR4,OR5,AN6,AN7 + * DB0,DB1,SB0,SB1,EIN,DIN,RPM + * f: 0P,1P,2P,3P,4P,5P,6P,7P + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: op | lastbyte, op in [0-15] + * g: op | lastbyte, op in [0,2,4,6,8,10,12,14] + * h: output (op & 0xff0000 >> 8) | lastbyte; + * then ouput op as 8 bit value + * i: (op << 4) | lastbyte + * j: (op << 1) | lastbyte + */ + +static const struct matchtab s_matchtab_i4004[] = { + { "NOP", "00.", 1, 0 }, + { "JCN a,a", "10f0.d1.", 1, 0, "b4e8" }, + { "FIM f,a", "20j0.d1.", 1, 0, "e8" }, + { "FIM a,a", "20g0.d1.", 1, 0, "ppe8" }, + { "SRC f", "21j0.", 1, 0 }, + { "SRC a", "21g0.", 1, 0, "pp" }, + { "FIN f", "30j0.", 1, 0 }, + { "FIN a", "30g0.", 1, 0, "pp" }, + { "JIN f", "31j0.", 1, 0 }, + { "JIN a", "31g0.", 1, 0, "pp" }, + { "JUN a", "40h0", 1, 0 }, + { "JMS a", "50h0", 1, 0 }, + { "INC a", "60f0.", 1, 0, "b4" }, + { "ISZ a,a", "70f0.d1.", 1, 0, "b4e8" }, + { "b a", "80i0f1.", 1, 0, "b4" }, + { "c", "E0c0.", 1, 0 }, + { "d", "F0c0.", 1, 0 }, + { "e", "00c0.", 2, 0 }, + { NULL, NULL }, +}; + +static const char *const bval[] = { +"ADD", "SUB", "LD", "XCH", "BBL", "LDM", +NULL }; + +static const char *const cval[] = { +"WRM", "WMP", "WRR", "WPM", "WR0", "WR1", "WR2", "WR3", +"SBM", "RDM", "RDR", "ADM", "RD0", "RD1", "RD2", "RD3", +NULL }; + +static const char *const dval[] = { +"CLB", "CLC", "IAC", "CMC", "CMA", "RAL", "RAR", "TCC", +"DAC", "TCS", "STC", "DAA", "KBP", "DCL", +NULL }; + +static const char *const eval[] = { +"", "HLT", "BBS", "LCR", "OR4", "OR5", "AN6", "AN7", +"DB0", "DB1", "SB0", "SB1", "EIN", "DIN", "RPM", +NULL }; + +static const char *const fval[] = { +"0P", "1P", "2P", "3P", "4P", "5P", "6P", "7P", +NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval, eval, fval +}; + +static int match_i4004(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'f') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_i4004(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 15)) { + eprint(_("argument (%d) must be in range [0-15]\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= vs[i]; + break; + case 'g': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 14 || (vs[i] & 1))) { + eprint( + _("argument (%d) must be an even number in range [0-14]\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= vs[i]; + break; + case 'h': b |= ((vs[i] >> 8) & 0x0f); + genb(b, s_pline_ep); + genb(vs[i], s_pline_ep); + break; + case 'i': b |= (vs[i] << 4); break; + case 'j': b |= (vs[i] << 1); break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_i4004(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_i4004(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'f') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_i4004 = { + .id = "i4004", + .descr = "Intel 4004", + .matcht = s_matchtab_i4004, + .matchf = match_i4004, + .genf = gen_i4004, + .pat_char_rewind = pat_char_rewind_i4004, + .pat_next_str = pat_next_str_i4004, + .mask = 1 +}; + +const struct target s_target_i4040 = { + .id = "i4040", + .descr = "Intel 4040", + .matcht = s_matchtab_i4004, + .matchf = match_i4004, + .genf = gen_i4004, + .pat_char_rewind = pat_char_rewind_i4004, + .pat_next_str = pat_next_str_i4004, + .mask = 3 +}; + diff --git a/Tools/unix/uz80as/i8008.c b/Tools/unix/uz80as/i8008.c new file mode 100644 index 00000000..a7fd9f58 --- /dev/null +++ b/Tools/unix/uz80as/i8008.c @@ -0,0 +1,220 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Intel 8008. + * =========================================================================== + */ + +/* Max. memory 16K (14 bits addresses). */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: ADA,ADB,ADC,ADD,ADH,ADL,ADM, + * ACA,ACB,ACC,ACD,ACH,ACL,ACM, + * SUA,SUB,SUC,SUD,SUH,SUL,SUM, + * SBA,SBB,SBC,SBD,SBH,SBL,SBM, + * NDA,NDB,NDC,NDD,NDH,NDL,NDM, + * XRA,XRB,XRC,XRD,XRH,XRL,XRM, + * ORA,ORB,ORC,ORD,ORH,ORL,ORM, + * CPA,CPB,CPC,CPD,CPH,CPL,CPM + * c: NOP,LAB,LAC,LAD,LAE,LAH,LAL,LAM, + * LBA,LBB,LBC,LBD,LBE,LBH,LBL,LBM, + * LCA,LCB,LCC,LCD,LCE,LCH,LCL,LCM, + * LDA,LDB,LDC,LDD,LDE,LDH,LDL,LDM, + * LEA,LEB,LEC,LED,LEE,LEH,LEL,LEM, + * LHA,LHB,LHC,LHD,LHE,LHH,LHL,LHM, + * LLA,LLB,LLC,LLD,LLE,LLH,LLL,LLM, + * LMA,LMB,LMC,LMD,LME,LMH,LML,HLT + * d: JFC,CFC,JMP,CAL,JFZ,CFZ, + * JFS,CFS,JFP,CFP, + * JTC,CTC,JTZ,CTZ, + * JTS,CTS,JTP,CTP + * e: INB,INC,IND,INE,INH,INL + * f: DCB,DCC,DCD,DCE,DCH,DCL + * g: ADI,ACI,SUI,SBI,NDI,XRI,ORI,CPI + * h: LAI,LBI,LCI,LDI,LEI,LHI,LLI,LMI + * i: RFC,RFS,RTC,RTS,RFZ,RFP,RTZ,RTP + * j: RLC,RRC,RAL,RAR + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: (op << 1) | lastbyte, op in [0-7] + * g: (op << 1) | lastbyte, op in [8-31] + * h: (op << 4) | lastbyte + * i: (op << 1) | lastbyte + * j: (op << 3) | lastbyte, op in [0-7] + */ + +static const struct matchtab s_matchtab_i8008[] = { + { "RET", "07.", 1, 0 }, + { "j", "02b0.", 1, 0 }, + { "i", "03b0.", 1, 0 }, + { "h a", "06b0.d1.", 1, 0, "e8" }, + { "g a", "04b0.d1.", 1, 0, "e8" }, + { "e", "00b0.", 1, 0 }, + { "f", "01b0.", 1, 0 }, + { "RST a", "05j0.", 1, 0, "b3" }, + { "d a", "40i0.e1", 1, 0 }, + { "INP a", "41f0.", 1, 0, "b3" }, + { "OUT a", "41g0.", 1, 0, "kk" }, + { "b", "80c0.", 1, 0 }, + { "c", "C0c0.", 1, 0 }, + { NULL, NULL }, +}; + +static const char *const bval[] = { +"ADA", "ADB", "ADC", "ADD", "ADE", "ADH", "ADL", "ADM", +"ACA", "ACB", "ACC", "ACD", "ACE", "ACH", "ACL", "ACM", +"SUA", "SUB", "SUC", "SUD", "SUE", "SUH", "SUL", "SUM", +"SBA", "SBB", "SBC", "SBD", "SBE", "SBH", "SBL", "SBM", +"NDA", "NDB", "NDC", "NDD", "NDE", "NDH", "NDL", "NDM", +"XRA", "XRB", "XRC", "XRD", "XRE", "XRH", "XRL", "XRM", +"ORA", "ORB", "ORC", "ORD", "ORE", "ORH", "ORL", "ORM", +"CPA", "CPB", "CPC", "CPD", "CPE", "CPH", "CPL", "CPM", +NULL }; + +static const char *const cval[] = { +"NOP", "LAB", "LAC", "LAD", "LAE", "LAH", "LAL", "LAM", +"LBA", "LBB", "LBC", "LBD", "LBE", "LBH", "LBL", "LBM", +"LCA", "LCB", "LCC", "LCD", "LCE", "LCH", "LCL", "LCM", +"LDA", "LDB", "LDC", "LDD", "LDE", "LDH", "LDL", "LDM", +"LEA", "LEB", "LEC", "LED", "LEE", "LEH", "LEL", "LEM", +"LHA", "LHB", "LHC", "LHD", "LHE", "LHH", "LHL", "LHM", +"LLA", "LLB", "LLC", "LLD", "LLE", "LLH", "LLL", "LLM", +"LMA", "LMB", "LMC", "LMD", "LME", "LMH", "LML", "HLT", +NULL }; + +static const char *const dval[] = { +"JFC", "CFC", "JMP", "CAL", "JFZ", "CFZ", "", "", +"JFS", "CFS", "", "", "JFP", "CFP", "", "", +"JTC", "CTC", "", "", "JTZ", "CTZ", "", "", +"JTS", "CTS", "", "", "JTP", "CTP", +NULL }; + +static const char *const eval[] = { "", "INB", "INC", "IND", + "INE", "INH", "INL", + NULL }; + +static const char *const fval[] = { "", "DCB", "DCC", "DCD", + "DCE", "DCH", "DCL", + NULL }; + +static const char *const gval[] = { "ADI", "ACI", "SUI", "SBI", + "NDI", "XRI", "ORI", "CPI", + NULL }; + +static const char *const hval[] = { "LAI", "LBI", "LCI", "LDI", + "LEI", "LHI", "LLI", "LMI", + NULL }; + +static const char *const ival[] = { "RFC", "RFZ", "RFS", "RFP", + "RTC", "RTZ", "RTS", "RTP", + NULL }; + +static const char *const jval[] = { "RLC", "RRC", "RAL", "RAR", + NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval, eval, fval, + gval, hval, ival, jval +}; + +static int match_i8008(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'j') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_i8008(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 7)) { + eprint(_("argument (%d) must be in range [0-7]\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= (vs[i] << 1); + break; + case 'g': if (s_pass > 0 && (vs[i] < 8 || vs[i] > 31)) { + eprint(_("argument (%d) must be in range [8-31]\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= (vs[i] << 1); + break; + case 'h': b |= (vs[i] << 4); break; + case 'i': b |= (vs[i] << 1); break; + case 'j': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 7)) { + eprint(_("argument (%d) must be in range [0-7]\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= (vs[i] << 3); + break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_i8008(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_i8008(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'j') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_i8008 = { + .id = "i8008", + .descr = "Intel 8008", + .matcht = s_matchtab_i8008, + .matchf = match_i8008, + .genf = gen_i8008, + .pat_char_rewind = pat_char_rewind_i8008, + .pat_next_str = pat_next_str_i8008, + .mask = 1 +}; + diff --git a/Tools/unix/uz80as/i8048.c b/Tools/unix/uz80as/i8048.c new file mode 100644 index 00000000..1526d252 --- /dev/null +++ b/Tools/unix/uz80as/i8048.c @@ -0,0 +1,276 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Intel 8021. + * Intel 8022. + * Intel 8041. + * Intel 8048. + * =========================================================================== + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: R0,R1,R2,R3,R4,R5,R6,R7 + * c: R0,R1 + * d: P1,P2 + * e: P4,P5,P6,P7 + * f: JB0,JB1,JB2,JB3,JB4,JB5,JB6,JB7 + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: output lastbyte | ((op & 0x700) >> 3) + * output op as 8 bit value + * (no '.' should follow) + * g: (op << 5) | lastbyte + */ + +static const struct matchtab s_matchtab_i8048[] = { + { "NOP", "00.", 1, 0 }, + { "ADD A,b", "68c0.", 1, 0 }, + { "ADD A,@c", "60c0.", 1, 0 }, + { "ADD A,#a", "03.d0.", 1, 0, "e8" }, + { "ADDC A,b", "78c0.", 1, 0 }, + { "ADDC A,@c", "70c0.", 1, 0 }, + { "ADDC A,#a", "13.d0.", 1, 0, "e8" }, + { "ANL A,b", "58c0.", 1, 0 }, + { "ANL A,@c", "50c0.", 1, 0 }, + { "ANL A,#a", "53.d0.", 1, 0, "e8" }, + { "ANL BUS,#a", "98.d0.", 32, 0, "e8" }, + { "ANL d,#a", "98c0.d1.", 4, 0, "e8" }, + { "ANLD e,A", "9Cc0.", 1, 0 }, + { "CALL a", "14f0", 1, 0 }, + { "CLR A", "27.", 1, 0 }, + { "CLR C", "97.", 1, 0 }, + { "CLR F1", "A5.", 4, 0 }, + { "CLR F0", "85.", 4, 0 }, + { "CPL A", "37.", 1, 0 }, + { "CPL C", "A7.", 1, 0 }, + { "CPL F0", "95.", 4, 0 }, + { "CPL F1", "B5.", 4, 0 }, + { "DA A", "57.", 1, 0 }, + { "DEC A", "07.", 1, 0 }, + { "DEC b", "C8c0.", 4, 0 }, + { "DIS I", "15.", 2, 0 }, + { "DIS TCNTI", "35.", 2, 0 }, + { "DJNZ b,a", "E8c0.d1.", 1, 0, "e8" }, + { "EN DMA", "E5.", 64, 0 }, + { "EN FLAGS", "F5.", 64, 0 }, + { "EN I", "05.", 2, 0 }, + { "EN TCNTI", "25.", 2, 0 }, + { "ENT0 CLK", "75.", 32, 0 }, + { "IN A,DBB", "22.", 64, 0 }, + { "IN A,P0", "08.", 8, 0 }, + { "IN A,d", "08c0.", 1, 0 }, + { "INC A", "17.", 1, 0 }, + { "INC b", "18c0.", 1, 0 }, + { "INC @c", "10c0.", 1, 0 }, + { "INS A,BUS", "08.", 32, 0 }, + { "f a", "12g0.d1.", 4, 0, "e8" }, + { "JC a", "F6.d0.", 1, 0, "e8" }, + { "JF0 a", "B6.d0.", 4, 0, "e8" }, + { "JF1 a", "76.d0.", 4, 0, "e8" }, + { "JMP a", "04f0", 1, 0, "e11" }, + { "JMPP @A", "B3.", 1, 0 }, + { "JNC a", "E6.d0.", 1, 0, "e8" }, + { "JNI a", "86.d0.", 32, 0, "e8" }, + { "JNIBF a", "D6.d0.", 64, 0, "e8" }, + { "JNT0 a", "26.d0.", 2, 0, "e8" }, + { "JNT1 a", "46.d0.", 1, 0, "e8" }, + { "JNZ a", "96.d0.", 1, 0, "e8" }, + { "JOBF a", "86.d0.", 64, 0, "e8" }, + { "JTF a", "16.d0.", 1, 0, "e8" }, + { "JT0 a", "36.d0.", 2, 0, "e8" }, + { "JT1 a", "56.d0.", 1, 0, "e8" }, + { "JZ a", "C6.d0.", 1, 0, "e8" }, + { "MOV A,#a", "23.d0.", 1, 0, "e8" }, + { "MOV A,PSW", "C7.", 4, 0 }, + { "MOV A,b", "F8c0.", 1, 0 }, + { "MOV A,@c", "F0c0.", 1, 0 }, + { "MOV A,T", "42.", 1, 0 }, + { "MOV PSW,A", "D7.", 4, 0 }, + { "MOV b,A", "A8c0.", 1, 0 }, + { "MOV b,#a", "B8c0.d1.", 1, 0, "e8" }, + { "MOV @c,A", "A0c0.", 1, 0 }, + { "MOV @c,#a", "B0c0.d1.", 1, 0, "e8" }, + { "MOV STS,A", "90.", 64, 0 }, + { "MOV T,A", "62.", 1, 0 }, + { "MOVD A,e", "0Cc0.", 1, 0 }, + { "MOVD e,A", "3Cc0.", 1, 0 }, + { "MOVP A,@A", "A3.", 1, 0 }, + { "MOVP3 A,@A", "E3.", 4, 0 }, + { "MOVX A,@c", "80c0.", 32, 0 }, + { "MOVX @c,A", "90c0.", 32, 0 }, + { "NOP", "00.", 1, 0 }, + { "ORL A,b", "48c0.", 1, 0 }, + { "ORL A,@c", "40c0.", 1, 0 }, + { "ORL A,#a", "43.d0.", 1, 0, "e8" }, + { "ORL BUS,#a", "88.d0.", 32, 0, "e8" }, + { "ORL d,#a", "88c0.d1.", 4, 0, "e8" }, + { "ORLD e,A", "8Cc0.", 1, 0 }, + { "OUT DBB,A", "02.", 64, 0 }, + { "OUTL BUS,A", "02.", 32, 0 }, + { "OUTL P0,A", "90.", 8, 0 }, + { "OUTL d,A", "38c0.", 1, 0 }, + { "RAD", "80.", 16, 0 }, + { "RET", "83.", 1, 0 }, + { "RETR", "93.", 4, 0 }, + { "RETI", "93.", 16, 0 }, + { "RL A", "E7.", 1, 0 }, + { "RLC A", "F7.", 1, 0 }, + { "RR A", "77.", 1, 0 }, + { "RRC A", "67.", 1, 0 }, + { "SEL AN0", "85.", 16, 0 }, + { "SEL AN1", "95.", 16, 0 }, + { "SEL MB0", "E5.", 32, 0 }, + { "SEL MB1", "F5.", 32, 0 }, + { "SEL RB0", "C5.", 4, 0 }, + { "SEL RB1", "D5.", 4, 0 }, + { "STOP TCNT", "65.", 1, 0 }, + { "STRT CNT", "45.", 1, 0 }, + { "STRT T", "55.", 1, 0 }, + { "SWAP A", "47.", 1, 0 }, + { "XCH A,b", "28c0.", 1, 0 }, + { "XCH A,@c", "20c0.", 1, 0 }, + { "XCHD A,@c", "30c0.", 1, 0 }, + { "XRL A,b", "D8c0.", 1, 0 }, + { "XRL A,@c", "D0c0.", 1, 0 }, + { "XRL A,#a", "D3.d0.", 1, 0, "e8" }, + { NULL, NULL }, +}; + +static const char *const bval[] = { +"R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", +NULL }; + +static const char *const cval[] = { +"R0", "R1", +NULL }; + +static const char *const dval[] = { +"", "P1", "P2", +NULL }; + +static const char *const eval[] = { +"P4", "P5", "P6", "P7", +NULL }; + +static const char *const fval[] = { +"JB0", "JB1", "JB2", "JB3", "JB4", "JB5", "JB6", "JB7", +NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval, eval, fval +}; + +static int match_i8048(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'f') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_i8048(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': b |= ((vs[i] & 0x700) >> 3); + genb(b, s_pline_ep); + genb(vs[i], s_pline_ep); + break; + case 'g': b |= (vs[i] << 5); + break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_i8048(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_i8048(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'f') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_i8041 = { + .id = "i8041", + .descr = "Intel 8041", + .matcht = s_matchtab_i8048, + .matchf = match_i8048, + .genf = gen_i8048, + .pat_char_rewind = pat_char_rewind_i8048, + .pat_next_str = pat_next_str_i8048, + .mask = 71 +}; + +const struct target s_target_i8048 = { + .id = "i8048", + .descr = "Intel 8048", + .matcht = s_matchtab_i8048, + .matchf = match_i8048, + .genf = gen_i8048, + .pat_char_rewind = pat_char_rewind_i8048, + .pat_next_str = pat_next_str_i8048, + .mask = 39 +}; + +const struct target s_target_i8021 = { + .id = "i8021", + .descr = "Intel 8021", + .matcht = s_matchtab_i8048, + .matchf = match_i8048, + .genf = gen_i8048, + .pat_char_rewind = pat_char_rewind_i8048, + .pat_next_str = pat_next_str_i8048, + .mask = 9 +}; + +const struct target s_target_i8022 = { + .id = "i8022", + .descr = "Intel 8022", + .matcht = s_matchtab_i8048, + .matchf = match_i8048, + .genf = gen_i8048, + .pat_char_rewind = pat_char_rewind_i8048, + .pat_next_str = pat_next_str_i8048, + .mask = 27 +}; + diff --git a/Tools/unix/uz80as/i8051.c b/Tools/unix/uz80as/i8051.c new file mode 100644 index 00000000..034e1292 --- /dev/null +++ b/Tools/unix/uz80as/i8051.c @@ -0,0 +1,244 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Intel 8051. + * =========================================================================== + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: R0,R1,R2,R3,R4,R5,R6,R7 + * c: R0,R1 + * d: ADD,ADDC,ORL,ANL,XRL,SUBB,XCH,MOV + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: output lastbyte | ((op & 0x700) >> 3) + * output op as 8 bit value + * (no '.' should follow) + * g: relative jump -2 + * h: relative jump -3 + * i: ouput op as big endian word + * j: b + (op << 4) + */ + +static const struct matchtab s_matchtab_i8051[] = { + { "ACALL a", "11f0", 1, 0, "e11" }, + { "d A,b", "08j0c1.", 1, 0 }, + { "d A,@c", "06j0c1.", 1, 0 }, + { "ADD A,#a", "24.d0.", 1, 0, "e8" }, + { "ADDC A,#a", "34.d0.", 1, 0, "e8" }, + { "ORL A,#a", "44.d0.", 1, 0, "e8" }, + { "ANL A,#a", "54.d0.", 1, 0, "e8" }, + { "XRL A,#a", "64.d0.", 1, 0, "e8" }, + { "SUBB A,#a", "94.d0.", 1, 0, "e8" }, + { "MOV A,#a", "74.d0.", 1, 0, "e8" }, + { "d A,a", "05j0.d1.", 1, 0, "e8" }, + // { "ADD A,b", "28c0.", 1, 0 }, + // { "ADD A,@c", "26c0.", 1, 0 }, + // { "ADD A,a", "25.d0.", 1, 0, "e8" }, + // { "ADDC A,b", "38c0.", 1, 0 }, + // { "ADDC A,@c", "36c0.", 1, 0 }, + // { "ADDC A,a", "35.d0.", 1, 0, "e8" }, + { "AJMP a", "01f0", 1, 0, "e11" }, + // { "ANL A,b", "58c0.", 1, 0 }, + // { "ANL A,@c", "56c0.", 1, 0 }, + // { "ANL A,a", "55.d0.", 1, 0, "e8" }, + { "ANL C,/a", "B0.d0.", 1, 0, "e8" }, + { "ANL C,a", "82.d0.", 1, 0, "e8" }, + { "ANL a,A", "52.d0.", 1, 0 }, + { "ANL a,#a", "53.d0.d1.", 1, 0, "e8e8" }, + { "CJNE A,#a,a", "B4.d0.h1.", 1, 0, "e8r8" }, + { "CJNE b,#a,a", "B8c0.d1.h2.", 1, 0, "e8r8" }, + { "CJNE @c,#a,a", "B6c0.d1.h2.", 1, 0, "e8r8" }, + { "CJNE A,a,a", "B5.d0.h1.", 1, 0, "e8r8" }, + { "CLR A", "E4.", 1, 0 }, + { "CLR C", "C3.", 1, 0 }, + { "CLR a", "C2.d0.", 1, 0, "e8" }, + { "CPL A", "F4.", 1, 0 }, + { "CPL C", "B3.", 1, 0 }, + { "CPL a", "B2.d0.", 1, 0, "e8" }, + { "DA A", "D4.", 1, 0 }, + { "DEC A", "14.", 1, 0 }, + { "DEC b", "18c0.", 1, 0 }, + { "DEC @c", "16c0.", 1, 0 }, + { "DEC a", "15.d0.", 1, 0, "e8" }, + { "DIV AB", "84.", 1, 0 }, + { "DJNZ b,a", "D8c0.g1.", 1, 0, "r8" }, + { "DJNZ a,a", "D5.d0.h1.", 1, 0, "e8r8" }, + { "INC DPTR", "A3.", 1, 0 }, + { "INC A", "04.", 1, 0 }, + { "INC b", "08c0.", 1, 0 }, + { "INC @c", "06c0.", 1, 0 }, + { "INC a", "05.d0.", 1, 0, "e8" }, + { "JB a,a", "20.d0.h1.", 1, 0, "e8r8" }, + { "JBC a,a", "10.d0.h1.", 1, 0, "e8r8" }, + { "JC a", "40.g0.", 1, 0, "r8" }, + { "JMP @A+DPTR", "73.", 1, 0 }, + { "JNB a,a", "30.d0.h1.", 1, 0, "e8r8" }, + { "JNC a", "50.g0.", 1, 0, "r8" }, + { "JNZ a", "70.g0.", 1, 0, "r8" }, + { "JZ a", "60.g0.", 1, 0, "r8" }, + { "LCALL a", "12.i0", 1, 0 }, + { "LJMP a", "02.i0", 1, 0 }, + // { "MOV A,b", "E8c0.", 1, 0 }, + // { "MOV A,@c", "E6c0.", 1, 0 }, + // { "MOV A,a", "E5.d0.", 1, 0, "e8" }, // MOV A,ACC not valid? + { "MOV b,A", "F8c0.", 1, 0 }, + { "MOV b,#a", "78c0.d1.", 1, 0, "e8" }, + { "MOV b,a", "A8c0.d1.", 1, 0, "e8" }, + { "MOV @c,A", "F6c0.", 1, 0 }, + { "MOV @c,#a", "76c0.d1.", 1, 0, "e8" }, + { "MOV @c,a", "A6c0.d1.", 1, 0, "e8" }, + { "MOV C,a", "A2.d0.", 1, 0, "e8" }, + { "MOV DPTR,#a", "90.i0", 1, 0, "e8" }, + { "MOV a,A", "F5.d0.", 1, 0, "e8" }, + { "MOV a,C", "92.d0.", 1, 0, "e8" }, + { "MOV a,b", "88c1.d0.", 1, 0, "e8" }, + { "MOV a,@c", "86c1.d0.", 1, 0, "e8" }, + { "MOV a,#a", "75.d0.d1.", 1, 0, "e8e8" }, + { "MOV a,a", "85.d1.d0.", 1, 0, "e8e8" }, + { "MOVC A,@A+DPTR", "93.", 1, 0 }, + { "MOVC A,@A+PC", "83.", 1, 0 }, + { "MOVX A,@c", "E2c0.", 1, 0 }, + { "MOVX A,@DPTR", "E0.", 1, 0 }, + { "MOVX @c,A", "F2c0.", 1, 0 }, + { "MOVX @DPTR,A", "F0.", 1, 0 }, + { "MUL AB", "A4.", 1, 0 }, + { "NOP", "00.", 1, 0 }, + // { "ORL A,b", "48c0.", 1, 0 }, + // { "ORL A,@c", "46c0.", 1, 0 }, + // { "ORL A,a", "45.d0.", 1, 0, "e8" }, + { "ORL C,/a", "A0.d0.", 1, 0, "e8" }, + { "ORL C,a", "72.d0.", 1, 0, "e8" }, + { "ORL a,A", "42.d0.", 1, 0, "e8" }, + { "ORL a,#a", "43.d0.d1.", 1, 0, "e8e8" }, + { "POP a", "D0.d0.", 1, 0, "e8" }, + { "PUSH a", "C0.d0.", 1, 0, "e8" }, + { "RET", "22.", 1, 0 }, + { "RETI", "32.", 1, 0 }, + { "RL A", "23.", 1, 0 }, + { "RLC A", "33.", 1, 0 }, + { "RR A", "03.", 1, 0 }, + { "RRC A", "13.", 1, 0 }, + { "SETB C", "D3.", 1, 0 }, + { "SETB a", "D2.d0.", 1, 0, "e8" }, + { "SJMP a", "80.g0.", 1, 0, "r8" }, + // { "SUBB A,b", "98c0.", 1, 0 }, + // { "SUBB A,@c", "96c0.", 1, 0 }, + // { "SUBB A,a", "95.d0.", 1, 0, "e8" }, + { "SWAP A", "C4.", 1, 0 }, + // { "XCH A,b", "C8c0.", 1, 0 }, + // { "XCH A,@c", "C6c0.", 1, 0 }, + // { "XCH A,a", "C5.d0.", 1, 0, "e8" }, + { "XCHD A,@c", "D6c0.", 1, 0 }, + // { "XRL A,b", "68c0.", 1, 0 }, + // { "XRL A,@c", "66c0.", 1, 0 }, + // { "XRL A,a", "65.d0.", 1, 0, "e8" }, + { "XRL a,A", "62.d0.", 1, 0, "e8" }, + { "XRL a,#a", "63.d0.d1.", 1, 0, "e8e8" }, + { NULL, NULL }, +}; + +static const char *const bval[] = { +"R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", +NULL }; + +static const char *const cval[] = { +"R0", "R1", +NULL }; + +static const char *const dval[] = { +"", "", "ADD", "ADDC", "ORL", "ANL", "XRL", "", +"", "SUBB", "", "", "XCH", "", "MOV", +NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval +}; + +static int match_i8051(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'd') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_i8051(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': b |= ((vs[i] & 0x700) >> 3); + genb(b, s_pline_ep); + genb(vs[i], s_pline_ep); + break; + case 'g': b = (vs[i] - savepc - 2); break; + break; + case 'h': b = (vs[i] - savepc - 3); break; + break; + case 'i': genb(vs[i] >> 8, s_pline_ep); + genb(vs[i], s_pline_ep); + break; + case 'j': b += (vs[i] << 4); break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_i8051(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_i8051(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'd') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_i8051 = { + .id = "i8051", + .descr = "Intel 8051", + .matcht = s_matchtab_i8051, + .matchf = match_i8051, + .genf = gen_i8051, + .pat_char_rewind = pat_char_rewind_i8051, + .pat_next_str = pat_next_str_i8051, + .mask = 1 +}; + diff --git a/Tools/unix/uz80as/i8080.c b/Tools/unix/uz80as/i8080.c new file mode 100644 index 00000000..e0f7f51b --- /dev/null +++ b/Tools/unix/uz80as/i8080.c @@ -0,0 +1,214 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Intel 8080. + * =========================================================================== + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: B,C,D,E,H,L,M,A + * c: B,D,H,SP + * d: B,D + * e: B,D,H,PSW + * f: JNZ,JZ,JNC,JC,JPO,JPE,JP,JM + * g: CNZ,CZ,CNC,CC,CPO,CPE,CP,CM + * h: RNZ,RZ,RNC,RC,RPO,RPE,RP,RM + * i: B,C,D,E,H,L,A + * j: ADD,ADC,SUB,SBB,ANA,XRA,ORA,CMP + * k: RLC,RRC,RAL,RAR + * l: ADI,ACI,SUI,SBI,ANI,XRI,ORI,CPI + * m: SHLD,LHLD,STA,LDA + * n: DI,EI + * o: OUT,IN + * p: STC,CMC + * q: POP,PUSH + * r: STAX,LDAX + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: (op << 4) | lastbyte + * g: possible value to RST + * h: (op << 2) | lastbyte + */ + +static const struct matchtab s_matchtab_i8080[] = { + { "MOV M,i", "70c0.", 3, 0 }, + { "MOV i,M", "46b0.", 3, 0 }, + { "MOV i,i", "40b0c1.", 3, 0 }, + { "MVI b,a", "06b0.d1.", 3, 0, "e8" }, + { "LXI c,a", "01f0.e1", 3, 0 }, + { "m a", "22b0.e1", 3, 0 }, + { "r d", "02b0f1.", 3, 0 }, + { "XCHG", "EB.", 3, 0 }, + { "j b", "80b0c1.", 3, 0 }, + { "l a", "C6b0.d1.", 3, 0, "e8" }, + { "INR b", "04b0.", 3, 0 }, + { "DCR b", "05b0.", 3, 0 }, + { "INX c", "03f0.", 3, 0 }, + { "DCX c", "0Bf0.", 3, 0 }, + { "DAD c", "09f0.", 3, 0 }, + { "DAA", "27.", 3, 0 }, + { "k", "07b0.", 3, 0 }, + { "CMA", "2F.", 3, 0 }, + { "p", "37b0.", 3, 0 }, + { "JMP a", "C3.e0", 3, 0 }, + { "f a", "C2b0.e1", 3, 0 }, + { "CALL a", "CD.e0", 3, 0 }, + { "g a", "C4b0.e1", 3, 0 }, + { "RET", "C9.", 3, 0 }, + { "h", "C0b0.", 3, 0 }, + { "RST a", "C7g0.", 3, 0, "b3" }, + { "PCHL", "E9.", 3, 0 }, + { "q e", "C1h0f1.", 3, 0 }, + { "XTHL", "E3.", 3, 0 }, + { "SPHL", "F9.", 3, 0 }, + { "o a", "D3b0.d1.", 3, 0, "e8" }, + { "n", "F3b0.", 3, 0 }, + { "HLT", "76.", 3, 0 }, + { "NOP", "00.", 3, 0 }, + /* 8085 added instructions */ + { "RIM", "20.", 2, 0 }, + { "SIM", "30.", 2, 0 }, + { "ARHL", "10.", 2, 2 }, + { "DSUB", "08.", 2, 2 }, + { "RDEL", "18.", 2, 2 }, + { "LDHI a", "28.d0.", 2, 2, "e8" }, + { "LDSI a", "38.d0.", 2, 2, "e8" }, + { "RSTV", "CB.", 2, 2 }, + { "SHLX", "D9.", 2, 2 }, + { "LHLX", "ED.", 2, 2 }, + { "JNK a", "DD.e0", 2, 2 }, + { "JNX5 a", "DD.e0", 2, 2 }, + { "JNUI a", "DD.e0", 2, 2 }, + { "JK a", "FD.e0", 2, 2 }, + { "JX5 a", "FD.e0", 2, 2 }, + { "JUI a", "FD.e0", 2, 2 }, + { NULL, NULL }, +}; + +static const char *const bval[] = { "B", "C", "D", "E", + "H", "L", "M", "A", NULL }; +static const char *const cval[] = { "B", "D", "H", "SP", NULL }; +static const char *const dval[] = { "B", "D", NULL }; +static const char *const eval[] = { "B", "D", "H", "PSW", NULL }; +static const char *const fval[] = { "JNZ", "JZ", "JNC", "JC", + "JPO", "JPE", "JP", "JM", NULL }; +static const char *const gval[] = { "CNZ", "CZ", "CNC", "CC", + "CPO", "CPE", "CP", "CM", NULL }; +static const char *const hval[] = { "RNZ", "RZ", "RNC", "RC", + "RPO", "RPE", "RP", "RM", NULL }; +static const char *const ival[] = { "B", "C", "D", "E", + "H", "L", "", "A", NULL }; +static const char *const jval[] = { "ADD", "ADC", "SUB", "SBB", + "ANA", "XRA", "ORA", "CMP", NULL }; +static const char *const kval[] = { "RLC", "RRC", "RAL", "RAR", NULL }; +static const char *const lval[] = { "ADI", "ACI", "SUI", "SBI", + "ANI", "XRI", "ORI", "CPI", NULL }; +static const char *const mval[] = { "SHLD", "LHLD", "STA", "LDA", NULL }; +static const char *const nval[] = { "DI", "EI", NULL }; +static const char *const oval[] = { "OUT", "IN", NULL }; +static const char *const pval[] = { "STC", "CMC", NULL }; +static const char *const qval[] = { "POP", "PUSH", NULL }; +static const char *const rval[] = { "STAX", "LDAX", NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval, eval, fval, + gval, hval, ival, jval, kval, + lval, mval, nval, oval, pval, + qval, rval +}; + +static int match_i8080(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'r') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_i8080(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': b |= (vs[i] << 4); break; + case 'g': if (s_pass > 0 && (vs[i] & ~7) != 0) { + eprint(_("invalid RST argument (%d)\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= (vs[i] << 3); + break; + case 'h': b |= (vs[i] << 2); break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_i8080(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_i8080(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'r') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_i8080 = { + .id = "i8080", + .descr = "Intel 8080", + .matcht = s_matchtab_i8080, + .matchf = match_i8080, + .genf = gen_i8080, + .pat_char_rewind = pat_char_rewind_i8080, + .pat_next_str = pat_next_str_i8080, + .mask = 1 +}; + +const struct target s_target_i8085 = { + .id = "i8085", + .descr = "Intel 8085", + .matcht = s_matchtab_i8080, + .matchf = match_i8080, + .genf = gen_i8080, + .pat_char_rewind = pat_char_rewind_i8080, + .pat_next_str = pat_next_str_i8080, + .mask = 2 +}; diff --git a/Tools/unix/uz80as/incl.c b/Tools/unix/uz80as/incl.c new file mode 100644 index 00000000..e11d4675 --- /dev/null +++ b/Tools/unix/uz80as/incl.c @@ -0,0 +1,81 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Include file stack. + * =========================================================================== + */ + +#include "config.h" +#include "incl.h" +#include "utils.h" +#include "err.h" + +#ifndef ASSERT_H +#include +#endif + +#ifndef STDIO_H +#include +#endif + +#ifndef STDLIB_H +#include +#endif + +/* Max number of nested included files. */ +#define NFILES 128 + +/* Number of nested files. */ +static int s_nfiles; + +/* Current file. */ +static struct incfile *s_curfile; + +/* Get the current file. Never returns NULL. */ +struct incfile *curfile(void) +{ + assert(s_curfile != NULL); + return s_curfile; +} + +/* The number of nested files. 0 means no file loaded. */ +int nfiles(void) +{ + return s_nfiles; +} + +/* Leave the current included file. */ +void popfile(void) +{ + struct incfile *ifile; + + assert(s_curfile != NULL); + fclose(s_curfile->fin); + ifile = s_curfile; + s_curfile = ifile->prev; + free(ifile); + s_nfiles--; +} + +/* Include a file whose name is [p, q[. */ +void pushfile(const char *p, const char *q) +{ + struct incfile *ifile; + + if (s_nfiles == NFILES) { + eprint(_("maximum number of nested includes exceeded (%d)\n"), + NFILES); + exit(EXIT_FAILURE); + } + + // printf("pushfile: %s\n", p); + ifile = emalloc((sizeof *ifile) + (q - p) + 1); + ifile->name = (char *) ((unsigned char *) ifile + sizeof *ifile); + copychars(ifile->name, p, q); + + ifile->fin = efopen(ifile->name, "r"); + ifile->linenum = 0; + ifile->prev = s_curfile; + s_curfile = ifile; + s_nfiles++; +} diff --git a/Tools/unix/uz80as/incl.h b/Tools/unix/uz80as/incl.h new file mode 100644 index 00000000..1da62f61 --- /dev/null +++ b/Tools/unix/uz80as/incl.h @@ -0,0 +1,28 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Include file stack. + * =========================================================================== + */ + +#ifndef INCL_H +#define INCL_H + +#ifndef STDIO_H +#define STDIO_H +#include +#endif + +struct incfile { + struct incfile *prev; + FILE *fin; + int linenum; + char *name; +}; + +void pushfile(const char *p, const char *q); +void popfile(void); +struct incfile *curfile(void); +int nfiles(void); + +#endif diff --git a/Tools/unix/uz80as/list.c b/Tools/unix/uz80as/list.c new file mode 100644 index 00000000..f340e30b --- /dev/null +++ b/Tools/unix/uz80as/list.c @@ -0,0 +1,164 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Assembly listing generation. + * =========================================================================== + */ + +#include "config.h" +#include "list.h" +#include "err.h" + +#ifndef STDIO_H +#include +#endif + +#ifndef STLIB_H +#include +#endif + +#ifndef STRING_H +#include +#endif + +static FILE *s_list_file; + +/* N characters in current line. */ +static int s_nchars; + +/* Generated data bytes in this line. */ +static int s_nbytes; + +/* Line text. */ +static const char *s_line; + +/* Line number. */ +static int s_linenum; + +/* Program counter. */ +static int s_pc; + +/* Number of current nested files. */ +static int s_nfiles; + +/* If listing line number, etc are generated or just the line. */ +int s_codes = 1; + +/* If listing is enabled or not. */ +int s_list_on = 1; + +/* If we are skipping lines. */ +static int s_skip_on = 0; + +void list_open(const char *fname) +{ + s_list_file = fopen(fname, "w"); + if (s_list_file == NULL) { + eprint(_("cannot open file %s\n"), fname); + } +} + +void list_close(void) +{ + if (s_list_file != NULL) + fclose(s_list_file); +} + +static void prhead(void) +{ + int i, j, n; + + s_nchars = fprintf(s_list_file, "%-.4d", s_linenum); + + n = 7 - s_nchars; + if (n <= 0) + n = 1; + j = 0; + if (s_nfiles > 0) + j = s_nfiles - 1; + if (j > n) + j = n; + for (i = 0; i < j; i++) + fputc('+', s_list_file); + j = n - j; + while (j--) + fputc(' ', s_list_file); + s_nchars += n; + + s_nchars += fprintf(s_list_file, "%.4X", s_pc); + if (s_skip_on) + fputc('~', s_list_file); + else + fputc(' ', s_list_file); + s_nchars += 1; +} + +static void prline(void) +{ + if (s_line == NULL) { + fputs("\n", s_list_file); + } else { + if (s_codes) { + while (s_nchars < 24) { + s_nchars++; + fputc(' ', s_list_file); + } + } + fprintf(s_list_file, "%s\n", s_line); + s_line = NULL; + } + s_nchars = 0; + s_nbytes = 0; +} + +void list_startln(const char *line, int linenum, int pc, int nested_files) +{ + if (s_list_file == NULL) + return; + s_linenum = linenum; + s_pc = pc; + s_line = line; + s_nchars = 0; + s_nbytes = 0; + s_nfiles = nested_files; +} + +void list_setpc(int pc) +{ + s_pc = pc; +} + +void list_skip(int on) +{ + s_skip_on = on; +} + +void list_eject(void) +{ + if (s_list_file == NULL || !s_list_on) + return; +} + +void list_genb(int b) +{ + if (s_list_file == NULL || !s_codes || !s_list_on) + return; + if (s_nchars == 0) + prhead(); + if (s_nbytes >= 4) { + prline(); + prhead(); + } + s_nchars += fprintf(s_list_file, "%2.2X ", (b & 0xff)); + s_nbytes++; + s_pc++; +} + +void list_endln(void) +{ + if (s_list_file == NULL || !s_list_on) + return; + if (s_codes && s_nchars == 0) + prhead(); + prline(); +} diff --git a/Tools/unix/uz80as/list.h b/Tools/unix/uz80as/list.h new file mode 100644 index 00000000..7868cd29 --- /dev/null +++ b/Tools/unix/uz80as/list.h @@ -0,0 +1,23 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Assembly listing generation. + * =========================================================================== + */ + +#ifndef LIST_H +#define LIST_H + +extern int s_codes; +extern int s_list_on; + +void list_open(const char *fname); +void list_close(void); +void list_startln(const char *line, int linenum, int pc, int nested_files); +void list_setpc(int pc); +void list_skip(int on); +void list_eject(void); +void list_genb(int b); +void list_endln(void); + +#endif diff --git a/Tools/unix/uz80as/main.c b/Tools/unix/uz80as/main.c new file mode 100644 index 00000000..e3e3ff1e --- /dev/null +++ b/Tools/unix/uz80as/main.c @@ -0,0 +1,303 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * main(), handling of command line options. + * =========================================================================== + */ + +#include "config.h" +#include "ngetopt.h" +#include "options.h" +#include "utils.h" +#include "err.h" +#include "uz80as.h" +#include "prtable.h" +#include "targets.h" + +#ifndef STDIO_H +#include +#endif + +#ifndef STDLIB_H +#include +#endif + +#ifndef STRING_H +#include +#endif + +#ifndef CTYPE_H +#include +#endif + +void print_copyright(FILE *f) +{ + static const char *copyright = +"Copyright (C) " COPYRIGHT_YEARS " Jorge Giner Cordero.\n"; + + fputs(copyright, f); +} + +static void print_license(FILE *f) +{ + static const char *license[] = { +"Permission is hereby granted, free of charge, to any person obtaining", +"a copy of this software and associated documentation files (the", +"\"Software\"), to deal in the Software without restriction, including", +"without limitation the rights to use, copy, modify, merge, publish,", +"distribute, sublicense, and/or sell copies of the Software, and to", +"permit persons to whom the Software is furnished to do so, subject to", +"the following conditions:", +"", +"The above copyright notice and this permission notice shall be included", +"in all copies or substantial portions of the Software.", +"", +"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,", +"EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF", +"MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.", +"IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY", +"CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,", +"TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE", +"SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + }; + + int i; + + for (i = 0; i < NELEMS(license); i++) { + fprintf(f, "%s\n", license[i]); + } +} + +static void print_version(FILE *f) +{ + const struct target *p; + + fputs(PACKAGE_STRING, f); + fputs("\n", f); + fputs("Targets:", f); + for (p = first_target(); p != NULL; p = next_target()) { + fprintf(f, " %s", p->id); + } + fputs("\n", f); +} + +static void print_help(const char *argv0) +{ + static const char *help = +"Usage: %s [OPTION]... ASM_FILE [OBJ_FILE [LST_FILE]]\n" +"\n" +"Assemble ASM_FILE into OBJ_FILE and generate the listing LST_FILE.\n" +"If not specified, OBJ_FILE is ASM_FILE with the extension changed to .obj.\n" +"If not specified, LST_FILE is ASM_FILE with the extension changed to .lst.\n" +"\n" +"Options:\n" +" -h, --help Display this help and exit.\n" +" -V, --verbose be chatty.\n" +" -v, --version Output version information and exit.\n" +" -l, --license Display the license text and exit.\n" +" -d, --define=MACRO Define a macro.\n" +" -f, --fill=n Fill memory with value n.\n" +" -q, --quiet Do not generate the listing file.\n" +" -x, --extended Enable extended instruction syntax.\n" +" -u, --undocumented Enable undocumented instructions.\n" +" -t, --target=NAME Select the target micro. z80 is the default.\n" +" -e, --list-targets List the targets supported.\n" +"\n" +"Examples:\n" +" " PACKAGE " p.asm Assemble p.asm into p.obj\n" +" " PACKAGE " p.asm p.bin Assemble p.asm into p.bin\n" +" " PACKAGE " -d\"MUL(a,b) (a*b)\" p.asm Define the macro MUL and assemble p.asm\n" +"\n" +"Report bugs to: <" PACKAGE_BUGREPORT ">.\n" +"Home page: <" PACKAGE_URL ">.\n"; + + printf(help, argv0); +} + +/* + * Get the filename part of fname (that is, for ../../fname.abc, get + * fname.abc). + * Then substitute the extension .abd by .ext or append .ext. + */ +static char *mkfname(const char *fname, const char *ext) +{ + size_t alen, elen; + const char *p, *q; + char *s; + + alen = strlen(fname); + elen = strlen(ext); + + /* Find start of filename in path string */ + p = fname + alen; + while (p > fname && *p != '/' && *p != '\\') + p--; + + if (*p == '/' || *p == '\\') + p++; + + /* Find the extension */ + q = fname + alen; + while (q > p && *q != '.') + q--; + + if (*q != '.') + q = fname + alen; + + s = emalloc((q - p) + 1 + elen + 1); + if (q > p) + memmove(s, p, (q - p)); + s[q - p] = '\0'; + strcat(s, "."); + strcat(s, ext); + return s; +} + +static void parse_fill_byte(const char *optarg) +{ + int hi, lo; + + if (strlen(optarg) != 2) + goto error; + + if ((hi = hexval(optarg[0])) < 0) + goto error; + if ((lo = hexval(optarg[1])) < 0) + goto error; + + s_mem_fillval = hi * 16 + lo; + return; + +error: eprogname(); + fprintf(stderr, _("invalid command line fill value (%s)\n"), optarg); + eprogname(); + fprintf(stderr, " "); + fprintf(stderr, _("Please, use two hexadecimal digits.\n")); + exit(EXIT_FAILURE); +} + +static void parse_target_id(const char *optarg) +{ + const struct target *p; + + p = find_target(optarg); + if (p == NULL) { + eprogname(); + fprintf(stderr, _("invalid target '%s'\n"), optarg); + exit(EXIT_FAILURE); + } else { + s_target_id = p->id; + } +} + +static void list_targets(FILE *f) +{ + const struct target *p; + + for (p = first_target(); p != NULL; p = next_target()) { + fprintf(f, "%-14s%s\n", p->id, p->descr); + } +} + +int main(int argc, char *argv[]) +{ + int c; + struct ngetopt ngo; + + static struct ngetopt_opt ops[] = { + { "define", 1, 'd' }, + { "extended", 0, 'x' }, + { "list-targets", 0, 'e' }, + { "fill", 1, 'f' }, + { "help", 0, 'h' }, + { "license", 0, 'l' }, + { "quiet", 0, 'q' }, + { "target", 1, 't' }, + { "undocumented", 0, 'u' }, + { "version", 0, 'v' }, + { "print-table", 1, 0 }, + { "print-delta", 1, 0 }, + { "verbose", 0, 'V' }, + { NULL, 0, 0 }, + }; + + ngetopt_init(&ngo, argc, argv, ops); + do { + c = ngetopt_next(&ngo); + switch (c) { + case 'V': + verbose++; + break; + case 'v': + print_version(stdout); + exit(EXIT_SUCCESS); + case 'h': + print_help(argv[0]); + exit(EXIT_SUCCESS); + case 'l': + print_copyright(stdout); + fputs("\n", stdout); + print_license(stdout); + exit(EXIT_SUCCESS); + case 't': + parse_target_id(ngo.optarg); + break; + case 'e': + list_targets(stdout); + exit(EXIT_SUCCESS); + break; + case 'd': + predefine(ngo.optarg); + break; + case 'f': + parse_fill_byte(ngo.optarg); + break; + case 'q': + s_listing = 0; + break; + case 'x': + s_extended_op = 1; + break; + case 'u': + s_undocumented_op = 1; + break; + case '?': + eprint(_("unrecognized option %s\n"), + ngo.optarg); + exit(EXIT_FAILURE); + case ':': + eprint(_("%s needs an argument\n"), + ngo.optarg); + exit(EXIT_FAILURE); + case ';': + eprint(_("%s does not allow for arguments\n"), + ngo.optarg); + exit(EXIT_FAILURE); + case 0: + if (strcmp(ngo.optstr, "print-table") == 0) { + print_table(stdout, ngo.optarg); + exit(EXIT_SUCCESS); + } + } + } while (c != -1); + + if (argc == ngo.optind) { + eprint(_("wrong number of arguments\n")); + exit(EXIT_FAILURE); + } + + s_asmfname = argv[ngo.optind]; + + if (argc - ngo.optind > 1) + s_objfname = argv[ngo.optind + 1]; + else + s_objfname = mkfname(s_asmfname, "obj"); + + if (argc - ngo.optind > 2) + s_lstfname = argv[ngo.optind + 2]; + else + s_lstfname = mkfname(s_asmfname, "lst"); + + uz80as(); + return 0; +} diff --git a/Tools/unix/uz80as/mc6800.c b/Tools/unix/uz80as/mc6800.c new file mode 100644 index 00000000..4ff80be5 --- /dev/null +++ b/Tools/unix/uz80as/mc6800.c @@ -0,0 +1,338 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Motorola 6800, 6801. + * =========================================================================== + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: NEGA,COMA,LSRA,RORA,ASRA, + * ROLA,DECA,INCA,TSTA,CLRA + * c: NEGB,COMB,LSRB,RORB,ASRB, + * ROLB,DECB,INCB,TSTB,CLRB + * d: NEG,COM,LSR,ROR,ASR, + * ROL,DEC,INC,TST,JMP,CLR + * e: SUBA,CMPA,SBCA,ANDA,BITA,LDAA, + * EORA,ADCA,ORAA,ADDA + * f: SUBB,CMPB,SBCB,ANDB,BITB,LDAB, + * EORB,ADCB,ORAB,ADDB + * g: INX,DEX,CLV,SEV,CLC,SEC,CLI,SEI + * h: BRA,BHI,BLS,BCC,BCS,BNE,BEQ, + * BVC,BVS,BPL,BMI,BGE,BLT,BGT,BLE + * i: TSX,INS,PULA,PULB,DES,TXS,PSHA, + * PSHB,RTS,RTI,WAI,SWI + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: ouput op as big endian word (no '.' should follow) + * g: if op<=$ff output lastbyte and output op as byte + * else output (lastbyte | 0x20) and output op as big endian word + * (no '.' should follow) + * h: relative - 2 + * i: relative - 4 + * i: relative - 5 + */ + +static const struct matchtab s_matchtab_mc6800[] = { + { "NOP", "01.", 1, 0 }, + { "TAP", "06.", 1, 0 }, + { "TPA", "07.", 1, 0 }, + { "g", "08c0.", 1, 0 }, + { "SBA", "10.", 1, 0 }, + { "CBA", "11.", 1, 0 }, + { "TAB", "16.", 1, 0 }, + { "TBA", "17.", 1, 0 }, + { "DAA", "19.", 1, 0 }, + { "ABA", "1B.", 1, 0 }, + { "i", "30c0.", 1, 0 }, + { "h a", "20c0.h1.", 1, 0, "r8" }, + { "b", "40c0.", 1, 0 }, + { "c", "50c0.", 1, 0 }, + { "d a,X", "60c0.d1.", 1, 0, "e8" }, + { "d a,Y", "18.60c0.d1.", 8, 0, "e8" }, + { "d a", "70c0.f1", 1, 0, "e16" }, + { "e #a", "80c0.d1.", 1, 0, "e8" }, + { "f #a", "C0c0.d1.", 1, 0, "e8" }, + { "e >a", "B0c0.f1", 1, 0, "e16" }, + { "f >a", "F0c0.f1", 1, 0, "e16" }, + { "e a,X", "A0c0.d1.", 1, 0, "e8" }, + { "f a,X", "E0c0.d1.", 1, 0, "e8" }, + { "e a,Y", "18.A0c0.d1.", 8, 0, "e8" }, + { "f a,Y", "18.E0c0.d1.", 8, 0, "e8" }, + { "e a", "90c0g1", 1, 0 }, + { "f a", "D0c0g1", 1, 0 }, + { "STAA >a", "B7.f0", 1, 0, "e16" }, + { "STAA a,X", "A7.d0.", 1, 0, "e8" }, + { "STAA a,Y", "18.A7.d0.", 8, 0, "e8" }, + { "STAA a", "97g0", 1, 0 }, + { "STAB >a", "F7.f0", 1, 0, "e16" }, + { "STAB a,X", "E7.d0.", 1, 0, "e8" }, + { "STAB a,Y", "18.E7.d0.", 8, 0, "e8" }, + { "STAB a", "D7g0", 1, 0 }, + { "CPX #a", "8C.f0", 1, 0, "e16" }, + { "CPX >a", "BC.f0", 1, 0, "e16" }, + { "CPX a,X", "AC.d0.", 1, 0, "e8" }, + { "CPX a,Y", "CD.AC.d0.", 8, 0, "e8" }, + { "CPX a", "9Cg0", 1, 0 }, + { "LDS #a", "8E.f0", 1, 0, "e16" }, + { "LDS >a", "BE.f0", 1, 0, "e16" }, + { "LDS a,X", "AE.d0.", 1, 0, "e8" }, + { "LDS a,Y", "18.AE.d0.", 8, 0, "e8" }, + { "LDS a", "9Eg0", 1, 0 }, + { "STS >a", "BF.f0", 1, 0, "e16" }, + { "STS a,X", "AF.d0.", 1, 0, "e8" }, + { "STS a,Y", "18.AF.d0.", 8, 0, "e8" }, + { "STS a", "9Fg0", 1, 0 }, + { "LDX #a", "CE.f0", 1, 0, "e16" }, + { "LDX >a", "FE.f0", 1, 0, "e16" }, + { "LDX a,X", "EE.d0.", 1, 0, "e8" }, + { "LDX a,Y", "CD.EE.d0.", 8, 0, "e8" }, + { "LDX a", "DEg0", 1, 0 }, + { "STX >a", "FF.f0", 1, 0, "e16" }, + { "STX a,X", "EF.d0.", 1, 0, "e8" }, + { "STX a,Y", "CD.EF.d0.", 8, 0, "e8" }, + { "STX a", "DFg0", 1, 0 }, + { "BSR a", "8D.h0.", 1, 0, "r8" }, + { "JSR >a", "BD.f0", 4, 0, "e16" }, + { "JSR a,X", "AD.d0.", 1, 0, "e8" }, + { "JSR a,Y", "18.AD.d0.", 8, 0, "e8" }, + { "JSR a", "BD.f0", 2, 0, "e16" }, + { "JSR a", "9Dg0", 4, 0 }, + { "ABX", "3A.", 4, 0 }, + { "ADDD #a", "C3.f0", 4, 0, "e16" }, + { "ADDD >a", "F3.f0", 4, 0, "e16" }, + { "ADDD a,X", "E3.d0.", 4, 0, "e8" }, + { "ADDD a,Y", "18.E3.d0.", 8, 0, "e8" }, + { "ADDD a", "D3g0", 4, 0 }, + { "ASLD", "05.", 4, 0 }, + { "LSLD", "05.", 4, 0 }, + { "BHS a", "24.h0.", 4, 0, "r8" }, + { "BLO a", "25.h0.", 4, 0, "r8" }, + { "BRN a", "21.h0.", 4, 0, "r8" }, + { "LDD #a", "CC.f0", 4, 0, "e16" }, + { "LDD >a", "FC.f0", 4, 0, "e16" }, + { "LDD a,X", "EC.d0.", 4, 0, "e8" }, + { "LDD a,Y", "18.EC.d0.", 8, 0, "e8" }, + { "LDD a", "DCg0", 4, 0 }, + { "LSL a,X", "68.d0.", 4, 0, "e8" }, + { "LSL a,Y", "18.68.d0.", 8, 0, "e8" }, + { "LSL a", "78.f0", 4, 0, "e16" }, + { "LSRD", "04.", 4, 0 }, + { "MUL", "3D.", 4, 0 }, + { "PSHX", "3C.", 4, 0 }, + { "PSHY", "18.3C.", 8, 0 }, + { "PULX", "38.", 4, 0 }, + { "PULY", "18.38.", 8, 0 }, + { "STD >a", "FD.f0", 4, 0, "e16" }, + { "STD a,X", "ED.d0.", 4, 0, "e8" }, + { "STD a,Y", "18.ED.d0.", 8, 0, "e8" }, + { "STD a", "DDg0", 4, 0 }, + { "SUBD #a", "83.f0", 4, 0, "e16" }, + { "SUBD >a", "B3.f0", 4, 0, "e16" }, + { "SUBD a,X", "A3.d0.", 4, 0, "e8" }, + { "SUBD a,Y", "18.A3.d0.", 8, 0, "e8" }, + { "SUBD a", "93g0", 4, 0 }, + { "TEST", "00.", 8, 0 }, + { "IDIV", "02.", 8, 0 }, + { "FDIV", "03.", 8, 0 }, + { "BRSET a,X,a,a", "1E.d0.d1.i2.", 8, 0, "e8e8r8" }, + { "BRSET a,Y,a,a", "18.1E.d0.d1.j2.", 8, 0, "e8e8r8" }, + { "BRSET a,a,a", "12.d0.d1.i2.", 8, 0, "e8e8r8" }, + { "BRCLR a,X,a,a", "1F.d0.d1.i2.", 8, 0, "e8e8r8" }, + { "BRCLR a,Y,a,a", "18.1F.d0.d1.j2.", 8, 0, "e8e8r8" }, + { "BRCLR a,a,a", "13.d0.d1.i2.", 8, 0, "e8e8r8" }, + { "BSET a,X,a", "1C.d0.d1.", 8, 0, "e8e8" }, + { "BSET a,Y,a", "18.1C.d0.d1.", 8, 0, "e8e8" }, + { "BSET a,a", "14.d0.d1.", 8, 0, "e8e8" }, + { "BCLR a,X,a", "1D.d0.d1.", 8, 0, "e8e8" }, + { "BCLR a,Y,a", "18.1D.d0.d1.", 8, 0, "e8e8" }, + { "BCLR a,a", "15.d0.d1.", 8, 0, "e8e8" }, + { "LSLA", "48.", 8, 0 }, + { "LSLB", "58.", 8, 0 }, + { "XGDX", "8F.", 8, 0 }, + { "STOP", "CF.", 8, 0 }, + { "ABY", "18.3A.", 8, 0 }, + { "CPY #a", "18.8C.f0", 8, 0, "e16" }, + { "CPY >a", "18.BC.f0", 8, 0, "e16" }, + { "CPY a,X", "1A.AC.d0.", 8, 0, "e8" }, + { "CPY a,Y", "18.AC.d0.", 8, 0, "e8" }, + { "CPY a", "18.9Cg0", 8, 0 }, + { "DEY", "18.09.", 8, 0 }, + { "INY", "18.08.", 8, 0 }, + { "LDY #a", "18.CE.f0", 8, 0, "e16" }, + { "LDY >a", "18.FE.f0", 8, 0, "e16" }, + { "LDY a,X", "1A.EE.d0.", 8, 0, "e8" }, + { "LDY a,Y", "18.EE.d0.", 8, 0, "e8" }, + { "LDY a", "18.DEg0", 8, 0 }, + { "STY >a", "18.FF.f0", 8, 0, "e16" }, + { "STY a,X", "1A.EF.d0.", 8, 0, "e8" }, + { "STY a,Y", "18.EF.d0.", 8, 0, "e8" }, + { "STY a", "18.DFg0", 8, 0 }, + { "TSY", "18.30.", 8, 0 }, + { "TYS", "18.35.", 8, 0 }, + { "XGDY", "18.8F.", 8, 0 }, + { "CPD #a", "1A.83.f0", 8, 0, "e16" }, + { "CPD >a", "1A.B3.f0", 8, 0, "e16" }, + { "CPD a,X", "1A.A3.d0.", 8, 0, "e8" }, + { "CPD a,Y", "CD.A3.d0.", 8, 0, "e8" }, + { "CPD a", "1A.93g0", 8, 0 }, + { NULL, NULL }, +}; + +static const char *const bval[] = { +"NEGA", "", "", "COMA", "LSRA", "", "RORA", "ASRA", +"ASLA", "ROLA", "DECA", "", "INCA", "TSTA", "", "CLRA", +NULL }; + +static const char *const cval[] = { +"NEGB", "", "", "COMB", "LSRB", "", "RORB", "ASRB", +"ASLB", "ROLB", "DECB", "", "INCB", "TSTB", "", "CLRB", +NULL }; + +static const char *const dval[] = { +"NEG", "", "", "COM", "LSR", "", "ROR", "ASR", +"ASL", "ROL", "DEC", "", "INC", "TST", "JMP", "CLR", +NULL }; + +static const char *const eval[] = { +"SUBA", "CMPA", "SBCA", "", "ANDA", "BITA", "LDAA", "", +"EORA", "ADCA", "ORAA", "ADDA", +NULL }; + +static const char *const fval[] = { +"SUBB", "CMPB", "SBCB", "", "ANDB", "BITB", "LDAB", "", +"EORB", "ADCB", "ORAB", "ADDB", +NULL }; + +static const char *const gval[] = { +"INX", "DEX", "CLV", "SEV", "CLC", "SEC", "CLI", "SEI", +NULL }; + +static const char *const hval[] = { +"BRA", "", "BHI", "BLS", "BCC", "BCS", "BNE", "BEQ", +"BVC", "BVS", "BPL", "BMI", "BGE", "BLT", "BGT", "BLE", +NULL }; + +static const char *const ival[] = { +"TSX", "INS", "PULA", "PULB", "DES", "TXS", "PSHA", +"PSHB", "", "RTS", "", "RTI", "", "", "WAI", "SWI", +NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval, eval, fval, + gval, hval, ival +}; + +static int match_mc6800(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'i') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_mc6800(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': genb(vs[i] >> 8, s_pline_ep); + genb(vs[i], s_pline_ep); + break; + case 'g': if (vs[i] <= 255) { + genb(b, s_pline_ep); + genb(vs[i], s_pline_ep); + } else { + genb(b | 0x20, s_pline_ep); + genb(vs[i] >> 8, s_pline_ep); + genb(vs[i], s_pline_ep); + } + break; + case 'h': b = (vs[i] - savepc - 2); + break; + case 'i': b = (vs[i] - savepc - 4); + break; + case 'j': b = (vs[i] - savepc - 5); + break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_mc6800(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_mc6800(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'i') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_mc6800 = { + .id = "mc6800", + .descr = "Motorola 6800", + .matcht = s_matchtab_mc6800, + .matchf = match_mc6800, + .genf = gen_mc6800, + .pat_char_rewind = pat_char_rewind_mc6800, + .pat_next_str = pat_next_str_mc6800, + .mask = 3 +}; + +const struct target s_target_mc6801 = { + .id = "mc6801", + .descr = "Motorola 6801", + .matcht = s_matchtab_mc6800, + .matchf = match_mc6800, + .genf = gen_mc6800, + .pat_char_rewind = pat_char_rewind_mc6800, + .pat_next_str = pat_next_str_mc6800, + .mask = 5 +}; + +const struct target s_target_m68hc11 = { + .id = "m68hc11", + .descr = "Motorola 68HC11", + .matcht = s_matchtab_mc6800, + .matchf = match_mc6800, + .genf = gen_mc6800, + .pat_char_rewind = pat_char_rewind_mc6800, + .pat_next_str = pat_next_str_mc6800, + .mask = 13 +}; diff --git a/Tools/unix/uz80as/mos6502.c b/Tools/unix/uz80as/mos6502.c new file mode 100644 index 00000000..f1041b24 --- /dev/null +++ b/Tools/unix/uz80as/mos6502.c @@ -0,0 +1,385 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * MOS Technology 6502. + * Rockwell R6501. + * California Micro Devices G65SC02. + * Rockwell R65C02. + * Rockwell R65C29. + * Western Design Center W65C02S. + * =========================================================================== + */ + +/* mos6502, the original + * + * g65sc02 California Micro Devices, adds to mos6502: + * - zp ADC,AND,CMP,EOR,LDA,ORA,SBC,STA + * - DEC A, INC A + * - JMP (abs,X) + * - BRA + * - PHX,PHY,PLX,PLY + * - STZ + * - TRB + * - TSB + * - More addressing modes for BIT, etc + * + * r6501 Rockwell, adds to mos6502: + * - BBR, BBS + * - RMB, SMB + * + * r65c02 Rockwell, adds the instructions of the g65sc02 and r6501 + * + * r65c29 Rockwell, adds to r65c02: + * - MUL + * + * w65c02s Western Design Center, adds to r65c02: + * - STP,WAI + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: ORA,AND,EOR,ADC,STA,LDA,CMP,SBC + * c: ORA,AND,EOR,ADC,LDA,CMP,SBC + * d: PHP,CLC,PLP,SEC,PHA,CLI,PLA,SEI, + * DEY,TYA,TAY,CLV,INY,CLD,INX,SED + * e: ASL,ROL,LSR,ROR + * f: DEC, INC + * g: BPL,BMI,BVC,BVS,BCC,BCS,BNE,BEQ + * h: TXA,TXS,TAX,TSX,DEX,NOP + * i: CPY,CPX + * j: TSB,TRB + * k: BBR0,BBR1,BBR2,BBR3,BBR4,BBR5,BBR6,BBR6, + * BBS0,BBS1,BBS2,BBS3,BBS4,BBS5,BBS6,BBS7 + * l: RMB0,RMB1,RMB2,RMB3,RMB4,RMB5,EMB6,RMB7, + * SMB0,SMB1,SMB2,SMB3,SMB4,SMB5,SMB6,SMB7 + * m: PHY,PLY + * n: PHX,PLX + * o: INC, DEC + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: (op << 5) | lastbyte + * g: if op <= $FF output last byte and then op as 8 bit value; + * else output (lastbyte | 0x08) and output op as word + * (no '.' should follow) + * h: (op << 4) | lastbyte + * i: relative jump to op (-2) + * j: if op <= $FF output $64 and op as 8 bit + * else output $9C and op as word + * (no '.' should follow) + * k: if op <= $FF ouput $74 and op as 8 bit + * else output $9E and op as word + * (no '.' should follow) + * l: relative jump to op (-3) + */ + +static const struct matchtab s_matchtab_mos6502[] = { + { "BRK", "00.", 1, 0 }, + { "JSR a", "20.e0", 1, 0 }, + { "RTI", "40.", 1, 0 }, + { "RTS", "60.", 1, 0 }, + { "h", "8Ah0.", 1, 0 }, + { "d", "08h0.", 1, 0 }, + { "c #a", "09f0.d1.", 1, 0, "e8" }, + { "b (a,X)", "01f0.d1.", 1, 0, "e8" }, + { "b (a),Y", "11f0.d1.", 1, 0, "e8" }, + { "b (a)", "12f0.d1.", 2, 0, "e8" }, + { "b a", "05f0g1", 1, 0 }, + { "b a,X", "15f0g1", 1, 0 }, + { "b a,Y", "19f0.e1", 1, 0 }, + { "e A", "0Af0.", 1, 0 }, + { "e a", "06f0g1", 1, 0 }, + { "e a,X", "16f0g1", 1, 0 }, + { "STX a", "86g0", 1, 0 }, + { "STX a,Y", "96.d0.", 1, 0, "e8" }, + { "LDX #a", "A2.d0.", 1, 0, "e8" }, + { "LDX a", "A6g0", 1, 0 }, + { "LDX a,Y", "B6g0", 1, 0 }, + { "o A", "1Af0.", 2, 0 }, + { "f a", "C6f0g1", 1, 0 }, + { "f a,X", "D6f0g1", 1, 0 }, + { "g a", "10f0.i1.", 1, 0, "r8" }, + { "BIT #a", "89.d0.", 2, 0, "e8" }, + { "BIT a", "24g0", 1, 0 }, + { "BIT a,X", "34g0", 2, 0 }, + { "JMP (a)", "6C.e0", 1, 0 }, + { "JMP (a,X)", "7C.e0", 2, 0 }, + { "JMP a", "4C.e0", 1, 0 }, + { "STY a", "84g0", 1, 0 }, + { "STY a,X", "94.d0.", 1, 0, "e8" }, + { "LDY #a", "A0.d0.", 1, 0, "e8" }, + { "LDY a", "A4g0", 1, 0 }, + { "LDY a,X", "B4g0", 1, 0 }, + { "i #a", "C0f0.d1.", 1, 0, "e8" }, + { "i a", "C4f0g1", 1, 0 }, + { "j a", "04h0g1", 2, 0 }, + { "k a,a", "0Fh0.d1.l2.", 4, 0, "e8r8" }, + { "l a", "07h0.d1.", 4, 0, "e8" }, + { "m", "5Af0.", 2, 0 }, + { "n", "DAf0.", 2, 0 }, + { "BRA a", "80.i0.", 2, 0, "r8" }, + { "STZ a,X", "k1", 2, 0 }, + { "STZ a", "j1", 2, 0 }, + { "MUL", "02.", 8, 0 }, + { "WAI", "CB.", 16, 0 }, + { "STP", "DB.", 16, 0 }, + { NULL, NULL }, +}; + +static const char *const bval[] = { + "ORA", "AND", "EOR", "ADC", + "STA", "LDA", "CMP", "SBC", + NULL +}; + +static const char *const cval[] = { + "ORA", "AND", "EOR", "ADC", + "", "LDA", "CMP", "SBC", NULL +}; + +static const char *const dval[] = { + "PHP", "CLC", "PLP", "SEC", + "PHA", "CLI", "PLA", "SEI", + "DEY", "TYA", "TAY", "CLV", + "INY", "CLD", "INX", "SED", + NULL +}; + + +static const char *const eval[] = { + "ASL", "ROL", "LSR", "ROR", + NULL +}; + +static const char *const fval[] = { + "DEC", "INC", + NULL +}; + +static const char *const gval[] = { + "BPL", "BMI", "BVC", "BVS", + "BCC", "BCS", "BNE", "BEQ", + NULL +}; + +static const char *const hval[] = { + "TXA", "TXS", "TAX", "TSX", + "DEX", "", "NOP", + NULL +}; + +static const char *const ival[] = { + "CPY", "CPX", + NULL +}; + +static const char *const jval[] = { + "TSB", "TRB", + NULL +}; + +static const char *const kval[] = { + "BBR0", "BBR1", "BBR2", "BBR3", + "BBR4", "BBR5", "BBR6", "BBR7", + "BBS0", "BBS1", "BBS2", "BBS3", + "BBS4", "BBS5", "BBS6", "BBS7", + NULL +}; + +static const char *const lval[] = { + "RMB0", "RMB1", "RMB2", "RMB3", + "RMB4", "RMB5", "RMB6", "RMB7", + "SMB0", "SMB1", "SMB2", "SMB3", + "SMB4", "SMB5", "SMB6", "SMB7", + NULL +}; + +static const char *const mval[] = { + "PHY", "PLY", + NULL +}; + +static const char *const nval[] = { + "PHX", "PLX", + NULL +}; + +static const char *const oval[] = { + "INC", "DEC", + NULL +}; + +static const char *const *const valtab[] = { + bval, cval, dval, eval, fval, + gval, hval, ival, jval, kval, + lval, mval, nval, oval +}; + +static int match_mos6502(char c, const char *p, const char **q) +{ + int v; + + if (c <= 'o') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_mos6502(int *eb, char p, const int *vs, int i, int savepc) +{ + int b, w; + + b = *eb; + switch (p) { + case 'f': b |= (vs[i] << 5); break; + case 'g': w = vs[i] & 0xffff; + if (w <= 0xff) { + genb(b, s_pline_ep); + b = 0; + genb(w, s_pline_ep); + } else { + b |= 0x08; + genb(b, s_pline_ep); + b = 0; + genb(w, s_pline_ep); + genb(w >> 8, s_pline_ep); + } + break; + case 'h': b |= (vs[i] << 4); break; + case 'i': b = (vs[i] - savepc - 2); break; + case 'j': w = vs[i] & 0xffff; + if (w <= 0xff) { + genb(0x64, s_pline_ep); + b = 0; + genb(w, s_pline_ep); + } else { + genb(0x9C, s_pline_ep); + b = 0; + genb(w, s_pline_ep); + genb(w >> 8, s_pline_ep); + } + break; + case 'k': w = vs[i] & 0xffff; + if (w <= 0xff) { + genb(0x74, s_pline_ep); + b = 0; + genb(w, s_pline_ep); + } else { + genb(0x9E, s_pline_ep); + b = 0; + genb(w, s_pline_ep); + genb(w >> 8, s_pline_ep); + } + break; + case 'l': b = (vs[i] - savepc - 3); break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_mos6502(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_mos6502(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'o') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_mos6502 = { + .id = "mos6502", + .descr = "MOS Technology 6502", + .matcht = s_matchtab_mos6502, + .matchf = match_mos6502, + .genf = gen_mos6502, + .pat_char_rewind = pat_char_rewind_mos6502, + .pat_next_str = pat_next_str_mos6502, + .mask = 1 +}; + +const struct target s_target_r6501 = { + .id = "r6501", + .descr = "Rockwell R6501", + .matcht = s_matchtab_mos6502, + .matchf = match_mos6502, + .genf = gen_mos6502, + .pat_char_rewind = pat_char_rewind_mos6502, + .pat_next_str = pat_next_str_mos6502, + .mask = 5 +}; + +const struct target s_target_g65sc02 = { + .id = "g65sc02", + .descr = "California Micro Devices G65SC02", + .matcht = s_matchtab_mos6502, + .matchf = match_mos6502, + .genf = gen_mos6502, + .pat_char_rewind = pat_char_rewind_mos6502, + .pat_next_str = pat_next_str_mos6502, + .mask = 3 +}; + +const struct target s_target_r65c02 = { + .id = "r65c02", + .descr = "Rockwell R65C02", + .matcht = s_matchtab_mos6502, + .matchf = match_mos6502, + .genf = gen_mos6502, + .pat_char_rewind = pat_char_rewind_mos6502, + .pat_next_str = pat_next_str_mos6502, + .mask = 7 +}; + +const struct target s_target_r65c29 = { + .id = "r65c29", + .descr = "Rockwell R65C29, R65C00/21", + .matcht = s_matchtab_mos6502, + .matchf = match_mos6502, + .genf = gen_mos6502, + .pat_char_rewind = pat_char_rewind_mos6502, + .pat_next_str = pat_next_str_mos6502, + .mask = 15 +}; + +const struct target s_target_w65c02s = { + .id = "w65c02s", + .descr = "Western Design Center W65C02S", + .matcht = s_matchtab_mos6502, + .matchf = match_mos6502, + .genf = gen_mos6502, + .pat_char_rewind = pat_char_rewind_mos6502, + .pat_next_str = pat_next_str_mos6502, + .mask = 027 +}; diff --git a/Tools/unix/uz80as/ngetopt.c b/Tools/unix/uz80as/ngetopt.c new file mode 100644 index 00000000..c0404012 --- /dev/null +++ b/Tools/unix/uz80as/ngetopt.c @@ -0,0 +1,237 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Handling of command line options, similar to getopt. + * =========================================================================== + */ + +/* + * Changes: + * + * - Jul 22 2018: long options without short option character recognized. + * + */ + +#include "ngetopt.h" + +#ifndef STRING_H +#include +#endif + +static int find_short_opt(int val, struct ngetopt_opt *ops) +{ + int i; + + i = 0; + while (ops[i].name != NULL) { + if (ops[i].val > 0 && ops[i].val == val) + return i; + i++; + } + + return -1; +} + +static int find_long_opt(char *str, struct ngetopt_opt *ops) +{ + int i; + const char *p, *q; + + i = 0; + while (ops[i].name != NULL) { + p = ops[i].name; + q = str; + while (*p != '\0' && *p == *q) { + p++; + q++; + } + if (*p == '\0' && (*q == '\0' || *q == '=')) { + return i; + } + i++; + } + + return -1; +} + +void ngetopt_init(struct ngetopt *p, int argc, char *const *argv, + struct ngetopt_opt *ops) +{ + p->argc = argc; + p->argv = argv; + p->ops = ops; + p->optind = 1; + p->subind = 0; + strcpy(p->str, "-X"); +} + +static int get_short_opt(struct ngetopt *p) +{ + int i; + char *opt; + + opt = p->argv[p->optind]; + i = find_short_opt(opt[p->subind], p->ops); + if (i < 0) { + /* unrecognized option */ + p->str[1] = (char) opt[p->subind]; + p->optarg = p->str; + p->subind++; + return '?'; + } + + if (!p->ops[i].has_arg) { + /* it's ok */ + p->subind++; + return p->ops[i].val; + } + + /* needs an argument */ + if (opt[p->subind + 1] != '\0') { + /* the argument is the suffix */ + p->optarg = &opt[p->subind + 1]; + p->subind = 0; + p->optind++; + return p->ops[i].val; + } + + /* the argument is the next token */ + p->optind++; + p->subind = 0; + if (p->optind < p->argc) { + p->optarg = p->argv[p->optind]; + p->optind++; + return p->ops[i].val; + } + + /* ups, argument missing */ + p->str[1] = (char) p->ops[i].val; + p->optarg = p->str; + return ':'; +} + +static int get_opt(struct ngetopt *p) +{ + int i; + char *opt, *optnext; + + /* all arguments consumed */ + if (p->optind >= p->argc) + return -1; + + opt = p->argv[p->optind]; + if (opt[0] != '-') { + /* non option */ + return -1; + } + + /* - */ + if (opt[1] == '\0') { + /* stdin */ + return -1; + } + + if (opt[1] != '-') { + /* -xxxxx */ + p->subind = 1; + return get_short_opt(p); + } + + /* -- */ + if (opt[2] == '\0') { + /* found "--" */ + p->optind++; + return -1; + } + + /* long option */ + i = find_long_opt(&opt[2], p->ops); + if (i < 0) { + /* not found */ + p->optind++; + p->optarg = opt; + while (*opt != '\0' && *opt != '=') { + opt++; + } + *opt = '\0'; + return '?'; + } + + /* found, go to end of option */ + optnext = opt + 2 + strlen(p->ops[i].name); + + if (*optnext == '\0' && !p->ops[i].has_arg) { + /* doesn't need arguments */ + p->optind++; + p->optstr = opt + 2; + return p->ops[i].val; + } + + if (*optnext == '=' && !p->ops[i].has_arg) { + /* does not need arguments but argument supplied */ + *optnext = '\0'; + p->optarg = opt; + return ';'; + } + + /* the argument is the next token */ + if (*optnext == '\0') { + p->optind++; + if (p->optind < p->argc) { + p->optstr = opt + 2; + p->optarg = p->argv[p->optind]; + p->optind++; + return p->ops[i].val; + } + + /* ups, argument missing */ + p->optarg = opt; + p->optind++; + return ':'; + } + + /* *optnext == '=' */ + *optnext = '\0'; + p->optstr = opt + 2; + p->optarg = optnext + 1; + p->optind++; + return p->ops[i].val; +} + +/* + * If ok: + * + * - For a long option with a zero value single character option, 0 is + * returned, optstr is the string of the long option (without '-' or '--') + * and optarg is the option argument or NULL. + * + * - For anything else the single option character is returned and optarg + * is the option argument or NULL. + * + * If the option is not recognized, '?' is returned, and optarg is the + * literal string of the option not recognized (already with '-' or '--' + * prefixed). + * + * If the option is recognized but the argument is missing, ':' is + * returned and optarg is the option as supplied (with '-' or '--' prefixed). + * + * If the option is recognized and it is a long option followed by '=', but the + * option does not take arguments, ';' is returned and optarg is the option + * (with '-' or '--' prefixed). + * + * -1 is returned if no more options. + */ +int ngetopt_next(struct ngetopt *p) +{ + if (p->subind == 0) + return get_opt(p); + + /* p->subind > 0 */ + if (p->argv[p->optind][p->subind] != '\0') + return get_short_opt(p); + + /* no more options in this list of short options */ + p->subind = 0; + p->optind++; + return get_opt(p); +} diff --git a/Tools/unix/uz80as/ngetopt.h b/Tools/unix/uz80as/ngetopt.h new file mode 100644 index 00000000..31ff8a60 --- /dev/null +++ b/Tools/unix/uz80as/ngetopt.h @@ -0,0 +1,40 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Handling of command line options, similar to getopt. + * =========================================================================== + */ + +#ifndef NGETOPT_H +#define NGETOPT_H + +/* + * Changelog: + * + * - Jul 21 2018: long options without short option character recognized. + * + */ + +struct ngetopt_opt { + const char *name; + int has_arg; + int val; +}; + +struct ngetopt { + char *optstr; + char *optarg; + /* private */ + int optind; + int argc; + char *const *argv; + struct ngetopt_opt *ops; + int subind; + char str[3]; +}; + +void ngetopt_init(struct ngetopt *p, int argc, char *const *argv, + struct ngetopt_opt *ops); +int ngetopt_next(struct ngetopt *p); + +#endif diff --git a/Tools/unix/uz80as/options.c b/Tools/unix/uz80as/options.c new file mode 100644 index 00000000..c3c24cd2 --- /dev/null +++ b/Tools/unix/uz80as/options.c @@ -0,0 +1,33 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Global options, normally coming from the command line. + * =========================================================================== + */ + +#include "config.h" +#include "options.h" +#include "err.h" + +const char *s_asmfname; /* Name of source file. */ +const char *s_objfname; /* Name of generated binary file. */ +const char *s_lstfname; /* Name of listing file. */ +const char *s_target_id = "z80"; /* ID of target */ +int s_listing = 1; /* If we generate the listing file or not. */ +int s_extended_op = 0; /* Allow extended instruction syntax. */ +int s_undocumented_op = 0; /* Allow undocumented instructions. */ +int s_mem_fillval = 0; /* Default value to fill the 64K memory. */ + +/* Command line macro definitions. */ +struct predef *s_predefs; + +/* Predefine a macro in the command line that must persist between passes. */ +void predefine(const char *text) +{ + struct predef *pdef; + + pdef = emalloc(sizeof(*pdef)); + pdef->name = text; + pdef->next = s_predefs; + s_predefs = pdef; +} diff --git a/Tools/unix/uz80as/options.h b/Tools/unix/uz80as/options.h new file mode 100644 index 00000000..c23ed913 --- /dev/null +++ b/Tools/unix/uz80as/options.h @@ -0,0 +1,29 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Global options, normally coming from the command line. + * =========================================================================== + */ + +#ifndef OPTIONS_H +#define OPTIONS_H + +/* Predefined macro at the command line. */ +struct predef { + struct predef *next; + const char *name; +}; + +extern const char *s_asmfname; +extern const char *s_objfname; +extern const char *s_lstfname; +extern const char *s_target_id; +extern int s_listing; +extern int s_extended_op; +extern int s_undocumented_op; +extern int s_mem_fillval; +extern struct predef *s_predefs; + +void predefine(const char *name); + +#endif diff --git a/Tools/unix/uz80as/pp.c b/Tools/unix/uz80as/pp.c new file mode 100644 index 00000000..b3b69f2a --- /dev/null +++ b/Tools/unix/uz80as/pp.c @@ -0,0 +1,745 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Preprocessor. + * =========================================================================== + */ + +#include "config.h" +#include "pp.h" +#include "utils.h" +#include "err.h" +#include "incl.h" +#include "expr.h" +#include "exprint.h" + +#ifndef CTYPE_H +#include +#endif + +#ifndef STDIO_H +#include +#endif + +#ifndef STDLIB_H +#include +#endif + +#ifndef STRING_H +#include +#endif + +/* Max number of macros. */ +#define NMACROS 1000 + +/* Closest prime to NMACROS / 4. */ +#define MACTABSZ 241 + +/* Max number of macro arguments. */ +#define NPARAMS 20 + +#define DEFINESTR "DEFINE" +#define DEFCONTSTR "DEFCONT" +#define INCLUDESTR "INCLUDE" +#define IFSTR "IF" +#define IFDEFSTR "IFDEF" +#define IFNDEFSTR "IFNDEF" +#define ENDIFSTR "ENDIF" +#define ELSESTR "ELSE" + +/* + * Macro. + * + * For example, the macro: + * + * #define SUM(a,b) (a+b) + * + * is: + * + * name = SUM + * pars = a\0b\0 + * ppars[0] points to &pars[0], that is to "a" + * ppars[1] points to &pars[2], that is to "b" + * npars = 2 + * text is "(a+b)" + */ +struct macro { + struct macro *next; /* Next in hash chain. */ + char *name; /* Identifier. */ + char *pars; /* String with params separated by '\0'. */ + char *text; /* Text to expand. */ + char *ppars[NPARAMS]; /* Pointers to the beginning of each param. */ + int npars; /* Valid number of params in ppars. */ +}; + +/* Hash table of preprocessor symbols. */ +static struct macro *s_mactab[MACTABSZ]; + +/* Preprocessing line buffers. */ +static char s_ppbuf[2][LINESZ]; + +/* If we are discarding lines; if not 0, level of if. */ +int s_skipon; + +/* Number of nested #if or #ifdef or #ifndef. */ +static int s_nifs; + +/* Last defined macro. */ +static struct macro *s_lastmac; + +/* Number of macros in table. */ +static int s_nmacs; + +/* The preprocessed line, points to one of s_ppbuf. */ +char *s_pline; + +/* Current program counter. */ +int s_pc; + +/* Current pass. */ +int s_pass; + + +/* Only valid while in the call to pp_line(). */ +static const char *s_line; /* original line */ +static const char *s_line_ep; /* pointer inside s_line for error reporting */ + +/* + * Copy [p, q[ to [dp, dq[. + */ +static char *copypp(char *dp, char *dq, const char *p, const char *q) +{ + while (dp < dq && p < q) + *dp++ = *p++; + return dp; +} + +/* + * Find the 'argnum' argument in 'args' and return a pointer to it. + * + * 'args' is a list of arguments "([id [,id]*). + * 'argnum' is the argument number to find. + * + * Return not found. + */ +static const char *findarg(const char *args, int argnum) +{ + if (*args == '(') { + do { + args++; + if (argnum == 0) + return args; + argnum--; + while (*args != '\0' && *args != ',' + && *args != ')') + { + args++; + } + } while (*args == ','); + } + return NULL; +} + +/* + * Find the 'argnum' argument in 'args' and copy it to [dp, dq[. + * + * 'args' points to a list of arguments "([id [,id]*). + * 'argnum' is the argument number to copy. + * + * Return the new 'dp' after copying. + */ +static char *copyarg(char *dp, char *dq, const char *args, int argnum) +{ + const char *p; + + p = findarg(args, argnum); + if (p == NULL) + return dp; + + while (dp < dq && *p != '\0' && *p != ',' && *p != ')') + *dp++ = *p++; + return dp; +} + +/* + * Sees if [idp, idq[ is a parameter of the macro 'pps'. + * If it is, return the number of parameter. + * Else return -1. + */ +static int findparam(const char *idp, const char *idq, struct macro *pps) +{ + int i; + const char *p, *r; + + for (i = 0; i < pps->npars; i++) { + p = pps->ppars[i]; + r = idp; + while (*p != '\0' && r < idq && *p == *r) { + p++; + r++; + } + if (*p == '\0' && r == idq) + return i; + } + return -1; +} + +/* + * Lookup the string in [p, q[ in 's_mactab'. + * Return the symbol or NULL if it is not in the table. + */ +static struct macro *pplookup(const char *p, const char *q) +{ + int h; + struct macro *nod; + + h = hash(p, q, MACTABSZ); + for (nod = s_mactab[h]; nod != NULL; nod = nod->next) + if (scmp(p, q, nod->name) == 0) + return nod; + + return nod; +} + +/* + * Expand macro in [dp, dq[. + * + * 'pps' is the macro to expand. + * 'args' points to the start of the arguments to substitute, if any. + * + * Return new dp. + */ +static char *expandid(char *dp, char *dq, struct macro *pps, const char *args) +{ + const char *p, *q; + int validid, argnum; + + validid = 1; + p = pps->text; + while (*p != '\0' && dp < dq) { + if (isidc0(*p)) { + for (q = p; isidc(*q); q++) + ; + if (validid) { + argnum = findparam(p, q, pps); + if (argnum >= 0) + dp = copyarg(dp, dq, args, argnum); + else + dp = copypp(dp, dq, p, q); + } else { + dp = copypp(dp, dq, p, q); + } + p = q; + validid = 1; + } else { + validid = !isidc(*p); + *dp++ = *p++; + } + } + return dp; +} + +/* + * If 'p' points the the start of an argument list, that is, '(', + * point to one character past the first ')' after 'p'. + * Else return 'p'. + */ +static const char *skipargs(const char *p) +{ + if (*p == '(') { + while (*p != '\0' && *p != ')') + p++; + if (*p == ')') + p++; + } + return p; +} + +/* + * Expand macros found in 'p' (null terminated) into [dp, dq[. + * dq must be writable to put a final '\0'. + */ +static int expand_line(char *dp, char *dq, const char *p) +{ + char *op; + int expanded, validid; + const char *s; + struct macro *nod; + + validid = 1; + expanded = 0; + while (dp < dq && *p != '\0' && *p != ';') { + if (*p == '\'' && *(p + 1) != '\0' && *(p + 2) == '\'') { + /* characters */ + dp = copypp(dp, dq, p, p + 3); + p += 3; + validid = 1; + } else if (*p == '\"') { + /* strings */ + s = p; + p++; + /* skip over the string literal */ + while (*p != '\0' && *p != '\"') { + if (p[0] == '\\' && p[1] == '\"') + p++; + p++; + } + if (*p == '\"') + p++; + dp = copypp(dp, dq, s, p); + validid = 1; + } else if (isidc0(*p)) { + s = p; + while (isidc(*p)) + p++; + if (validid) { + nod = pplookup(s, p); + if (nod != NULL) { + op = dp; + dp = expandid(dp, dq, nod, p); + expanded = dp != op; + p = skipargs(p); + } else { + dp = copypp(dp, dq, s, p); + } + } else { + dp = copypp(dp, dq, s, p); + } + validid = 1; + } else { + validid = *p != '.' && !isalnum(*p); + *dp++ = *p++; + } + } + *dp = '\0'; + return expanded; +} + +/* + * Expand macros found in 'p' (null terminated). + * Return a pointer to an internal preprocessed line (null terminated). + */ +static char *expand_line0(const char *p) +{ + int iter, expanded; + char *np, *nq, *op; + + iter = 0; + np = &s_ppbuf[iter & 1][0]; + nq = &s_ppbuf[iter & 1][LINESZ - 1]; + expanded = expand_line(np, nq, p); + /* TODO: recursive macro expansion limit */ + while (expanded && iter < 5) { + op = np; + iter++; + np = &s_ppbuf[iter & 1][0]; + nq = &s_ppbuf[iter & 1][LINESZ - 1]; + expanded = expand_line(np, nq, op); + } + return np; +} + +/* + * Check if 'p' starts with the preprocessor directive 'ucq', that must be in + * upper case. + * 'p' can have any case. + * After the preprocessor directive must be a space or '\0'. + * Return 1 if all the above is true. 0 otherwise. + */ +static int isppid(const char *p, const char *ucq) +{ + while (*p != '\0' && *ucq != '\0' && toupper(*p) == *ucq) { + p++; + ucq++; + } + return (*ucq == '\0') && (*p == '\0' || isspace(*p)); +} + +/* + * Define a macro. + * + * [idp, idq[ is the macro id. + * [ap, aq[ is the macro argument list. If ap == aq there are no arguments. + * [tp, tq[ is the macro text. + */ +static void define(const char *idp, const char *idq, + const char *tp, const char *tq, + const char *ap, const char *aq) +{ + int h; + char *p; + struct macro *nod; + + h = hash(idp, idq, MACTABSZ); + for (nod = s_mactab[h]; nod != NULL; nod = nod->next) { + if (scmp(idp, idq, nod->name) == 0) { + /* Already defined. */ + return; + } + } + + s_nmacs++; + if (s_nmacs >= NMACROS) { + eprint(_("maximum number of macros exceeded (%d)\n"), NMACROS); + exit(EXIT_FAILURE); + } + + nod = emalloc((sizeof *nod) + (idq - idp) + (aq - ap) + 2); + nod->text = emalloc(tq - tp + 1); + nod->name = (char *) ((unsigned char *) nod + (sizeof *nod)); + nod->pars = nod->name + (idq - idp + 1); + + copychars(nod->name, idp, idq); + copychars(nod->text, tp, tq); + copychars(nod->pars, ap, aq); + + // printf("DEF %s(%s) %s\n", nod->name, nod->pars, nod->text); + + /* We don't check whether the arguments are different. */ + + /* + * Make ppars point to each argument and null terminate each one. + * Count the number of arguments. + */ + nod->npars = 0; + p = nod->pars; + while (*p != '\0') { + nod->ppars[nod->npars++] = p; + while (*p != '\0' && *p != ',') + p++; + if (*p == ',') + *p++ = '\0'; + } + + nod->next = s_mactab[h]; + s_mactab[h] = nod; + s_lastmac = nod; +} + +/* Add the text [p, q[ to the last macro text. */ +static void defcont(const char *p, const char *q) +{ + char *nt; + size_t len; + + len = strlen(s_lastmac->text); + nt = erealloc(s_lastmac->text, (q - p) + len + 1); + copychars(nt + len, p, q); + s_lastmac->text = nt; +} + +/* + * If 'p' points to a valid identifier start, go to the end of the identifier. + * Else return 'p'. + */ +static const char *getid(const char *p) +{ + if (isidc0(*p)) { + while (isidc(*p)) + p++; + } + return p; +} + +/* Issues error in a macro definition. */ +static void macdeferr(int cmdline, const char *estr, const char *ep) +{ + if (cmdline) { + eprint(_("error in command line macro definition\n")); + } + eprint(estr); + eprcol(s_line, ep); + if (cmdline) { + exit(EXIT_FAILURE); + } else { + newerr(); + } +} + +/* Parse macro definition. */ +static void pmacdef(const char *p, int cmdline) +{ + const char *q, *ap, *aq, *idp, *idq; + + idp = p; + idq = getid(idp); + if (idq == idp) { + macdeferr(cmdline, _("identifier excepted\n"), p); + return; + } + p = idq; + ap = aq = p; + if (*p == '(') { + p++; + ap = p; + while (isidc0(*p)) { + p = getid(p); + if (*p != ',') + break; + p++; + } + if (*p != ')') { + macdeferr(cmdline, _("')' expected\n"), p); + return; + } + aq = p; + p++; + } + if (*p != '\0' && !isspace(*p)) { + macdeferr(cmdline, _("space expected\n"), p); + return; + } + p = skipws(p); + /* go to the end */ + for (q = p; *q != '\0'; q++) + ; + /* go to the first non white from the end */ + while (q > p && isspace(*(q - 1))) + q--; + define(idp, idq, p, q, ap, aq); +} + +/* Parse #define. */ +static void pdefine(const char *p) +{ + p = skipws(p + sizeof(DEFINESTR) - 1); + pmacdef(p, 0); +} + +/* Parse #defcont. */ +static void pdefcont(const char *p) +{ + const char *q; + + p = skipws(p + sizeof(DEFCONTSTR) - 1); + + /* go to the end */ + for (q = p; *q != '\0'; q++) + ; + + /* go to the first non white from the end */ + while (q > p && isspace(*(q - 1))) + q--; + + if (p == q) { + /* nothing to add */ + return; + } + + if (s_lastmac == NULL) { + eprint(_("#DEFCONT without a previous #DEFINE\n")); + eprcol(s_line, s_line_ep); + newerr(); + return; + } + + defcont(p, q); +} + +/* Parse #include. */ +static void pinclude(const char *p) +{ + const char *q; + + p = skipws(p + sizeof(INCLUDESTR) - 1); + if (*p != '\"') { + eprint(_("#INCLUDE expects a filename between quotes\n")); + eprcol(s_line, p); + newerr(); + return; + } + q = ++p; + while (*q != '\0' && *q != '\"') + q++; + if (*q != '\"') { + wprint(_("no terminating quote\n")); + eprcol(s_line, q); + } + pushfile(p, q); +} + +/* + * Parse #ifdef or #ifndef. + * 'idsz' is the length of the string 'ifdef' or 'ifndef', plus '\0'. + * 'ifdef' must be 1 if we are #ifdef, 0 if #ifndef. + */ +static void pifdef(const char *p, size_t idsz, int ifdef) +{ + const char *q; + struct macro *nod; + + s_nifs++; + if (s_skipon) + return; + + p = skipws(p + idsz - 1); + if (!isidc0(*p)) { + s_skipon = s_nifs; + eprint(_("identifier expected\n")); + eprcol(s_line, p); + newerr(); + return; + } + q = p; + while (isidc(*q)) + q++; + nod = pplookup(p, q); + if (ifdef == (nod != NULL)) + s_skipon = 0; + else + s_skipon = s_nifs; +} + +/* Parse #else. */ +static void pelse(const char *p) +{ + if (s_nifs == 0) { + eprint(_("unbalanced #ELSE\n")); + eprcol(s_line, s_line_ep); + newerr(); + return; + } + + if (s_skipon && s_nifs == s_skipon) + s_skipon = 0; + else if (!s_skipon) + s_skipon = s_nifs; +} + +/* Parse #endif. */ +static void pendif(const char *p) +{ + if (s_nifs == 0) { + eprint(_("unbalanced #ENDIF\n")); + eprcol(s_line, s_line_ep); + newerr(); + return; + } + + if (s_skipon && s_nifs == s_skipon) + s_skipon = 0; + s_nifs--; +} + +/* + * Parse #if. + */ +static void pif(const char *p) +{ + int v; + enum expr_ecode ex_ec; + const char *ep; + + s_nifs++; + if (s_skipon) + return; + + p = skipws(p + sizeof(IFSTR) - 1); + if (!expr(p, &v, s_pc, 1, &ex_ec, &ep)) { + s_skipon = 1; + exprint(ex_ec, s_line, ep); + newerr(); + return; + } + + if (v == 0) + s_skipon = s_nifs; + else + s_skipon = 0; +} + +/* + * Parse a preprocessor line. + * 'p' points to the next character after the '#'. + */ +static int +parse_line(const char *p) +{ + if (isppid(p, IFDEFSTR)) { + pifdef(p, sizeof IFDEFSTR, 1); + } else if (isppid(p, IFNDEFSTR)) { + pifdef(p, sizeof IFNDEFSTR, 0); + } else if (isppid(p, IFSTR)) { + pif(p); + } else if (isppid(p, ELSESTR)) { + pelse(p); + } else if (isppid(p, ENDIFSTR)) { + pendif(p); + } else if (s_skipon) { + ; + } else if (isppid(p, INCLUDESTR)) { + pinclude(p); + } else if (isppid(p, DEFINESTR)) { + pdefine(p); + } else if (isppid(p, DEFCONTSTR)) { + pdefcont(p); + } else { + return 0; +/* + eprint(_("unknown preprocessor directive\n")); + eprcol(s_line, s_line_ep); + newerr(); +*/ + } + return 1; +} + +/* + * Preprocess 'line' in 's_pline'. + * In this module, while we are preprocessing: + * s_line is the original line. + * s_line_ep is a pointer inside line that we keep for error reporting. + */ +void pp_line(const char *line) +{ + const char *p; + + s_line = line; + s_line_ep = line; + + p = skipws(line); + if ((*p == '#') || (*p == '.')) { + s_line_ep = p; + if (parse_line(p + 1)) { + s_ppbuf[0][0] = '\0'; + s_pline = &s_ppbuf[0][0]; + return; + } + } + if (s_skipon) { + s_ppbuf[0][0] = '\0'; + s_pline = &s_ppbuf[0][0]; + return; + } + s_pline = expand_line0(line); +} + +/* Reset the module for other passes. */ +void pp_reset(void) +{ + int i; + struct macro *nod, *cur; + + s_nmacs = 0; + s_nifs = 0; + s_skipon = 0; + s_lastmac = NULL; + for (i = 0; i < MACTABSZ; i++) { + nod = s_mactab[i]; + while (nod != NULL) { + cur = nod; + nod = nod->next; + free(cur->text); + free(cur); + } + } + memset(s_mactab, 0, MACTABSZ * sizeof(s_mactab[0])); +} + +void pp_define(const char *mactext) +{ + s_line = mactext; + s_line_ep = mactext; + pmacdef(mactext, 1); + s_lastmac = NULL; +} diff --git a/Tools/unix/uz80as/pp.h b/Tools/unix/uz80as/pp.h new file mode 100644 index 00000000..74a6d6f2 --- /dev/null +++ b/Tools/unix/uz80as/pp.h @@ -0,0 +1,23 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Preprocessor. + * =========================================================================== + */ + +#ifndef PP_H +#define PP_H + +/* Max line length after macro expansion + '\0'. */ +#define LINESZ 512 + +extern char *s_pline; +extern int s_pc; +extern int s_pass; +extern int s_skipon; + +void pp_line(const char *line); +void pp_reset(void); +void pp_define(const char *name); + +#endif diff --git a/Tools/unix/uz80as/prtable.c b/Tools/unix/uz80as/prtable.c new file mode 100644 index 00000000..1c3be97f --- /dev/null +++ b/Tools/unix/uz80as/prtable.c @@ -0,0 +1,343 @@ +#include "prtable.h" +#include "err.h" +#include "targets.h" +#include "uz80as.h" + +#ifndef STDLIB_H +#include +#endif + +#ifndef CTYPE_H +#include +#endif + +#ifndef STRING_H +#include +#endif + +enum { STRSZ = 32 }; + +struct itext { + struct itext *next; + int undoc; + char str[STRSZ]; +}; + +struct ilist { + struct itext *head; + int nelems; +}; + +struct itable { + struct itext **table; + int nelems; +}; + +static char s_buf[STRSZ]; + +static void nomem(const char *str) +{ + eprogname(); + fprintf(stderr, _("not enough memory (%s)\n"), str); + exit(EXIT_FAILURE); +} + +static int compare(const void *pa, const void *pb) +{ + const struct itext * const *ia; + const struct itext * const *ib; + + ia = (const struct itext * const *) pa; + ib = (const struct itext * const *) pb; + return strcmp((*ia)->str, (*ib)->str); +} + +/* + * Returns a new allocated itable of pointers that point to each element in the + * list ilist, alphabetically sorted. + */ +static struct itable *sort_list(struct ilist *ilist) +{ + int n; + struct itable *itable; + struct itext *p; + + if ((itable = calloc(1, sizeof(*itable))) == NULL) { + return NULL; + } + itable->nelems = 0; + + if (ilist->nelems == 0) { + return itable; + } + + itable->table = malloc(ilist->nelems * sizeof(*itable->table)); + if (itable->table == NULL) { + free(itable); + return NULL; + } + + for (n = 0, p = ilist->head; + p != NULL && n < ilist->nelems; + p = p->next, n++) + { + itable->table[n] = p; + } + itable->nelems = n; + + qsort(itable->table, itable->nelems, sizeof(*itable->table), compare); + return itable; +} + +static void print_itable(struct itable *itable, FILE *f) +{ + int i, col; + struct itext *p; + + if (itable == NULL) { + return; + } + + fputs("@multitable @columnfractions .25 .25 .25 .25\n", f); + col = 0; + for (i = 0; i < itable->nelems; i++) { + p = itable->table[i]; + if (col == 0) { + fputs("@item ", f); + } else { + fputs("@tab ", f); + } + if (p->undoc) { + fputs("* ", f); + } + fprintf(f, "%s\n", p->str); + col++; + if (col >= 4) { + col = 0; + } + } + fputs("@end multitable\n", f); +} + +#if 0 +static void print_ilist(struct ilist *ilist, FILE *f) +{ + int col; + struct itext *p; + + if (ilist == NULL) { + return; + } + + fputs("@multitable @columnfractions .25 .25 .25 .25\n", f); + col = 0; + for (p = ilist->head; p != NULL; p = p->next) { + if (col == 0) { + fputs("@item ", f); + } else { + fputs("@tab ", f); + } + if (p->undoc) { + fputs("* ", f); + } + fprintf(f, "%s\n", p->str); + col++; + if (col >= 4) { + col = 0; + } + } + fputs("@end multitable\n", f); +} +#endif + +static void bufset(int i, char c) +{ + if (i >= STRSZ) { + eprogname(); + fputs(_("prtable: please, increase s_buf size\n"), stderr); + exit(EXIT_FAILURE); + } else { + s_buf[i] = c; + } +} + +static void gen_inst2(struct ilist *ilist, char *instr, int undoc) +{ + struct itext *p; + + if ((p = malloc(sizeof(*p))) == NULL) { + nomem("gen_inst2"); + } + + snprintf(p->str, STRSZ, "%s", instr); + p->undoc = undoc; + p->next = ilist->head; + ilist->head = p; + ilist->nelems++; +} + +static void gen_inst(struct ilist *ilist, const struct target *t, + unsigned char undoc, const char *p, size_t bufi, + const char *pr) +{ + size_t bufk; + const char *s; + + while (*p) { + if (!islower(*p)) { + if (*p == '@') { + bufset(bufi++, '@'); + } + bufset(bufi++, *p); + p++; + } else if (*p == 'a') { + if (pr == NULL) { + bufset(bufi++, 'e'); + } else if (pr[0] && pr[1]) { + if (pr[0] == pr[1]) { + bufset(bufi++, pr[0]); + pr += 2; + } else if (isdigit(pr[1])) { + bufset(bufi++, *pr); + pr++; + while (isdigit(*pr)) { + bufset(bufi++, *pr); + pr++; + } + } else { + bufset(bufi++, pr[0]); + bufset(bufi++, pr[1]); + pr += 2; + } + } else { + bufset(bufi++, 'e'); + } + p++; + } else { + break; + } + } + + if (*p == '\0') { + bufset(bufi, '\0'); + gen_inst2(ilist, s_buf, t->mask & undoc); + } else { + t->pat_char_rewind(*p); + while ((s = t->pat_next_str()) != NULL) { + if (s[0] != '\0') { + bufset(bufi, '\0'); + bufk = bufi; + while (*s != '\0') { + bufset(bufk++, *s); + s++; + } + bufset(bufk, '\0'); + gen_inst(ilist, t, undoc, p + 1, bufk, pr); + } + } + } +} + +/* Generate a list of instructions. */ +static struct ilist *gen_list(const struct target *t, unsigned char mask2, + int delta) +{ + int i, pr; + const struct matchtab *mt; + struct ilist *ilist; + + if ((ilist = calloc(1, sizeof(*ilist))) == NULL) { + return NULL; + } + + i = 0; + mt = t->matcht; + while (mt[i].pat != NULL) { + pr = 0; + if (t->mask == 1 && (mt[i].mask & 1)) { + pr = 1; + } else if (delta) { + if ((mt[i].mask & t->mask) && + !(mt[i].mask & mask2)) + { + pr = 1; + } + } else if (t->mask & mt[i].mask) { + pr = 1; + } + if (pr) { + gen_inst(ilist, t, mt[i].undoc, mt[i].pat, + 0, mt[i].pr); + } + i++; + } + + return ilist; +} + +/* + * Prints the instruction set of a target or if target_id is "target2,target1" + * prints the instructions in target2 not in target1. + */ +void print_table(FILE *f, const char *target_id) +{ + struct ilist *ilist; + struct itable *itable; + const struct target *t, *t2; + char target1[STRSZ]; + const char *target2; + unsigned char mask2; + int delta; + + /* check if we have "target" or "target,target" as arguments */ + if ((target2 = strchr(target_id, ',')) != NULL) { + delta = 1; + snprintf(target1, sizeof(target1), "%s", target_id); + target1[target2 - target_id] = '\0'; + target2++; + } else { + delta = 0; + snprintf(target1, sizeof(target1), "%s", target_id); + target2 = NULL; + } + + t = find_target(target1); + if (t == NULL) { + eprogname(); + fprintf(stderr, _("invalid target '%s'\n"), target1); + exit(EXIT_FAILURE); + } + + if (target2) { + t2 = find_target(target2); + if (t2 == NULL) { + eprogname(); + fprintf(stderr, _("invalid target '%s'\n"), target2); + exit(EXIT_FAILURE); + } + if (t->matcht != t2->matcht) { + eprogname(); + fprintf(stderr, _("unrelated targets %s,%s\n"), + target1, target2); + exit(EXIT_FAILURE); + } + mask2 = t2->mask; + } else { + mask2 = 1; + } + + if ((ilist = gen_list(t, mask2, delta)) == NULL) { + nomem("gen_list"); + } + + if ((itable = sort_list(ilist)) == NULL) { + nomem("sort_list"); + } + + print_itable(itable, f); + + /* We don't free ilist nor itable for now, since this is called + * from main and then the program terminated. + */ +} + diff --git a/Tools/unix/uz80as/prtable.h b/Tools/unix/uz80as/prtable.h new file mode 100644 index 00000000..41eb646a --- /dev/null +++ b/Tools/unix/uz80as/prtable.h @@ -0,0 +1,11 @@ +#ifndef PRTABLE_H +#define PRTABLE_H + +#ifndef STDIO_H +#define STDIO_H +#include +#endif + +void print_table(FILE *f, const char *target_id); + +#endif diff --git a/Tools/unix/uz80as/sym.c b/Tools/unix/uz80as/sym.c new file mode 100644 index 00000000..53fc2625 --- /dev/null +++ b/Tools/unix/uz80as/sym.c @@ -0,0 +1,103 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Symbol table for labels. + * =========================================================================== + */ + +#include "config.h" +#include "sym.h" +#include "utils.h" +#include "err.h" + +#ifndef STDIO_H +#include +#endif + +#ifndef STDLIB_H +#include +#endif + +/* + * Maximum number of symbols (labels) allowed. + * Must not be more than 64K. + */ +#define NSYMS 15000 + +/* Closest prime to NSYMS / 4. */ +#define SYMTABSZ 3739 + +/* + * Nodes for the s_symtab hash table. + * The symbol at index 0 is never used. + */ +static struct sym s_symlist[NSYMS]; + +/* + * Hash table of indexes into s_symlist. + * 0 means that the bucket is empty. + */ +static unsigned short s_symtab[SYMTABSZ]; + +/* Next free symbol in s_symlist. Note: 0 not used. */ +static int s_nsyms = 1; + +/* + * Lookups the string in [p, q[ in s_symtab. + * If !insert, returns the symbol or NULL if it is not in the table. + * If insert, inserts the symbol in the table if it is not there, and + * sets its .val to 'pc'. + */ +struct sym *lookup(const char *p, const char *q, int insert, int pc) +{ + int h, k; + struct sym *nod; + + if (q - p > SYMLEN - 1) { + /* Label too long, don't add. */ + eprint(_("label too long")); + epchars(p, q); + enl(); + newerr(); + /* + * This would truncate: + * q = p + (SYMLEN - 1); + */ + return NULL; + } + + h = hash(p, q, SYMTABSZ); + for (k = s_symtab[h]; k != 0; k = s_symlist[k].next) { + if (scmp(p, q, s_symlist[k].name) == 0) { + if (insert) { + if (!s_symlist[k].isequ) { + wprint("duplicate label (%s)\n", + s_symlist[k].name); + } + } + return &s_symlist[k]; + } + } + + if (insert) { + if (s_nsyms == NSYMS) { + eprint(_("maximum number of labels exceeded (%d)\n"), + NSYMS); + exit(EXIT_FAILURE); + } + + nod = &s_symlist[s_nsyms]; + nod->next = s_symtab[h]; + s_symtab[h] = (unsigned short) s_nsyms; + s_nsyms++; + + k = 0; + while (p != q && k < SYMLEN - 1) + nod->name[k++] = *p++; + nod->name[k] = '\0'; + nod->val = pc; + return nod; + } + + return NULL; +} diff --git a/Tools/unix/uz80as/sym.h b/Tools/unix/uz80as/sym.h new file mode 100644 index 00000000..43108c68 --- /dev/null +++ b/Tools/unix/uz80as/sym.h @@ -0,0 +1,23 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Symbol table for labels. + * =========================================================================== + */ + +#ifndef SYM_H +#define SYM_H + +/* Max symbol length + '\0'. */ +#define SYMLEN 32 + +struct sym { + char name[SYMLEN]; /* null terminated string */ + int val; /* value of symbol */ + unsigned short next; /* index into symlist; 0 is no next */ + unsigned char isequ; /* if val comes from EQU */ +}; + +struct sym *lookup(const char *p, const char *q, int insert, int pc); + +#endif diff --git a/Tools/unix/uz80as/targets.c b/Tools/unix/uz80as/targets.c new file mode 100644 index 00000000..742ef674 --- /dev/null +++ b/Tools/unix/uz80as/targets.c @@ -0,0 +1,96 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Target list. + * =========================================================================== + */ + +#include "targets.h" +#include "uz80as.h" + +#ifndef STRING_H +#include +#endif + +extern const struct target s_target_z80; +extern const struct target s_target_hd64180; +extern const struct target s_target_gbcpu; +extern const struct target s_target_dp2200; +extern const struct target s_target_dp2200ii; +extern const struct target s_target_i4004; +extern const struct target s_target_i4040; +extern const struct target s_target_i8008; +extern const struct target s_target_i8021; +extern const struct target s_target_i8022; +extern const struct target s_target_i8041; +extern const struct target s_target_i8048; +extern const struct target s_target_i8051; +extern const struct target s_target_i8080; +extern const struct target s_target_i8085; +extern const struct target s_target_mos6502; +extern const struct target s_target_r6501; +extern const struct target s_target_g65sc02; +extern const struct target s_target_r65c02; +extern const struct target s_target_r65c29; +extern const struct target s_target_w65c02s; +extern const struct target s_target_mc6800; +extern const struct target s_target_mc6801; +extern const struct target s_target_m68hc11; + +static const struct target *s_targets[] = { + &s_target_z80, + &s_target_hd64180, + &s_target_gbcpu, + &s_target_dp2200, + &s_target_dp2200ii, + &s_target_i4004, + &s_target_i4040, + &s_target_i8008, + &s_target_i8021, + &s_target_i8022, + &s_target_i8041, + &s_target_i8048, + &s_target_i8051, + &s_target_i8080, + &s_target_i8085, + &s_target_mos6502, + &s_target_r6501, + &s_target_g65sc02, + &s_target_r65c02, + &s_target_r65c29, + &s_target_w65c02s, + &s_target_mc6800, + &s_target_mc6801, + &s_target_m68hc11, + NULL, +}; + +static int s_index; + +const struct target *find_target(const char *id) +{ + const struct target **p; + + for (p = s_targets; *p != NULL; p++) { + if (strcmp(id, (*p)->id) == 0) { + return *p; + } + } + + return NULL; +} + +const struct target *first_target(void) +{ + s_index = 0; + return next_target(); +} + +const struct target *next_target(void) +{ + if (s_targets[s_index] != NULL) { + return s_targets[s_index++]; + } else { + return NULL; + } +} diff --git a/Tools/unix/uz80as/targets.h b/Tools/unix/uz80as/targets.h new file mode 100644 index 00000000..12b6f64c --- /dev/null +++ b/Tools/unix/uz80as/targets.h @@ -0,0 +1,18 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Target list. + * =========================================================================== + */ + +#ifndef TARGETS_H +#define TARGETS_H + +struct target; + +const struct target *find_target(const char *id); + +const struct target *first_target(void); +const struct target *next_target(void); + +#endif diff --git a/Tools/unix/uz80as/test.asm b/Tools/unix/uz80as/test.asm new file mode 100644 index 00000000..28fdff3b --- /dev/null +++ b/Tools/unix/uz80as/test.asm @@ -0,0 +1,8 @@ + .org 0x100 +beep: .text "\"\"\"" ; comment + .align 4 + .text "foo" + .align 16 + .text "bar" + .db $45,0x67,'7' + .end diff --git a/Tools/unix/uz80as/test1.asm b/Tools/unix/uz80as/test1.asm new file mode 100644 index 00000000..1133b01f --- /dev/null +++ b/Tools/unix/uz80as/test1.asm @@ -0,0 +1,3 @@ + .org $100 +beep: .text "x" ; comment + .end diff --git a/Tools/unix/uz80as/utils.c b/Tools/unix/uz80as/utils.c new file mode 100644 index 00000000..a361f2fc --- /dev/null +++ b/Tools/unix/uz80as/utils.c @@ -0,0 +1,137 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Generic functions. + * =========================================================================== + */ + +#include "config.h" +#include "utils.h" + +#ifndef CTYPE_H +#include +#endif + +#ifndef LIMITS_H +#include +#endif + +/* + * Copy [p, q[ to dst and null terminate dst. + */ +void copychars(char *dst, const char *p, const char *q) +{ +// int i = 0; +// printf("copychars %x->%x to %x \'", p, q, dst); + while (p != q) { +// printf("%c", *p); + *dst++ = *p++; +// i++; + } + *dst = '\0'; +// printf("\' %d %x %d\n", *dst, dst, i); +} + +/* Skip space. */ +const char *skipws(const char *p) +{ + while (isspace(*p)) + p++; + return p; +} + +/* Return 1 if *p is a valid start character for an identifier. */ +int isidc0(char c) +{ + return (c == '_') || isalpha(c); +} + +/* + * Return 1 if *p is a valid character for an identifier. + * Don't use for the first character. + */ +int isidc(char c) +{ + return (c == '_') || (c == '.') || isalnum(c); +} + +/* Hash the string in [p, q[ to give a bucket in symtab. */ +int hash(const char *p, const char *q, unsigned int tabsz) +{ + unsigned int h; + + h = 0; + while (p != q) { + h = 31 * h + (unsigned char) *p; + p++; + } + + return h % tabsz; +} + +/* + * Compare the string in [p, q[ with the null-terminated string s. + * Return 0 if equal. + */ +int scmp(const char *p, const char *q, const char *s) +{ + while (p < q) { + if (*p == *s) { + p++; + s++; + } else if (*s == '\0') { + return 1; + } else if (*p < *s) { + return -1; + } else { + return 1; + } + } + + if (*s == '\0') + return 0; + else + return -1; +} +/* + * Given a hexadecimal character (in upper case), returns its integer value. + * Returns -1 if c is not a hexadecimal character. + */ +int hexvalu(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'F') + return (c - 'A') + 10; + else + return -1; +} + +/* + * Given a hexadecimal character, returns its integer value. + * Returns -1 if c is not a hexadecimal character. + */ +int hexval(char c) +{ + if (c >= 'a' && c <= 'f') + return (c - 'a') + 10; + else + return hexvalu(c); +} + +int int_precission(void) +{ + static int bits = 0; + unsigned int i; + + if (bits > 0) + return bits; + + i = INT_MAX; + bits = 0; + while (i) { + bits++; + i >>= 1; + } + return bits; +} diff --git a/Tools/unix/uz80as/utils.h b/Tools/unix/uz80as/utils.h new file mode 100644 index 00000000..2d22db6a --- /dev/null +++ b/Tools/unix/uz80as/utils.h @@ -0,0 +1,26 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Generic functions. + * =========================================================================== + */ + +#ifndef UTILS_H +#define UTILS_H + +#define NELEMS(a) (sizeof(a)/sizeof(a[0])) + +#define XSTR(n) STR(n) +#define STR(n) #n + +void copychars(char *dst, const char *p, const char *q); +int hash(const char *p, const char *q, unsigned int tabsz); +int isidc0(char c); +int isidc(char c); +int scmp(const char *p, const char *q, const char *s); +const char *skipws(const char *p); +int hexvalu(char c); +int hexval(char c); +int int_precission(void); + +#endif diff --git a/Tools/unix/uz80as/uz80as.c b/Tools/unix/uz80as/uz80as.c new file mode 100644 index 00000000..35c13ac0 --- /dev/null +++ b/Tools/unix/uz80as/uz80as.c @@ -0,0 +1,1240 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Assembler. + * =========================================================================== + */ + +#include "config.h" +#include "uz80as.h" +#include "options.h" +#include "utils.h" +#include "err.h" +#include "incl.h" +#include "sym.h" +#include "expr.h" +#include "exprint.h" +#include "pp.h" +#include "list.h" +#include "targets.h" + +#ifndef ASSERT_H +#include +#endif + +#ifndef CTYPE_H +#include +#endif + +#ifndef STDIO_H +#include +#endif + +#ifndef STDLIB_H +#include +#endif + +#ifndef STRING_H +#include +#endif + +static void output(); + +static const char *d_align(const char *); +static const char *d_null(const char *); +static const char *d_block(const char *); +static const char *d_byte(const char *); +static const char *d_chk(const char *); +static const char *d_codes(const char *); +static const char *d_echo(const char *); +static const char *d_eject(const char *); +static const char *d_export(const char *); +static const char *d_end(const char *); +static const char *d_equ(const char *); +static const char *d_fill(const char *); +static const char *d_ds(const char *); +static const char *d_list(const char *); +static const char *d_lsfirst(const char *); +static const char *d_module(const char *); +static const char *d_msfirst(const char *); +static const char *d_nocodes(const char *); +static const char *d_nolist(const char *); +static const char *d_org(const char *); +static const char *d_set(const char *); +static const char *d_text(const char *); +static const char *d_title(const char *); +static const char *d_word(const char *); + +/* + * Directives. + * This table must be sorted, to allow for binary search. + */ +static struct direc { + const char *name; + const char *(*fun)(const char *); +} s_directab[] = { + { "ALIGN", d_align }, + { "BLOCK", d_block }, + { "BYTE", d_byte }, + { "CHK", d_chk }, + { "CODES", d_codes }, + { "DB", d_byte }, + { "DS", d_ds }, + { "DW", d_word }, + { "ECHO", d_echo }, + { "EJECT", d_eject }, + { "END", d_end }, + { "EQU", d_equ }, + { "EXPORT", d_export }, + { "FILL", d_fill }, + { "GLOBAL", d_export }, + { "LIST", d_list }, + { "LSFIRST", d_lsfirst }, + { "MODULE", d_module }, + { "MSFIRST", d_msfirst }, + { "NOCODES", d_nocodes }, + { "NOLIST", d_nolist }, + { "NOPAGE", d_null }, + { "ORG", d_org }, + { "PAGE", d_null }, + { "SECTION", d_null }, + { "SET", d_set }, + { "TEXT", d_text }, + { "TITLE", d_title }, + { "WORD", d_word }, +}; + +/* binary output file */ +FILE *fout; + +/* output in source order */ +int b_flag = 1; + +/* The target. */ +const struct target *s_target; + +/* The z80 addressable memory. The object code. */ +static unsigned char s_mem[64 * 1024]; + +/* Program counter min and max ([s_minpc, s_maxpc[). */ +static int s_minpc, s_maxpc; + +/* Original input line. */ +static char s_line[LINESZ]; + +/* Label defined on this line. */ +static struct sym *s_lastsym; + +/* Output words the most significant byte first */ +static int s_msbword; + +/* If we have seen the .END directive. */ +static int s_end_seen; + +/* We have issued the error of generating things after an .END. */ +static int s_gen_after_end; + +/* The empty line, to pass to listing, for compatibility with TASM. */ +static const char *s_empty_line = ""; + +/* Pointer in s_pline for error reporting. */ +const char *s_pline_ep; + +/* We skip characters until endline or backslash or comment. */ +static const char *sync(const char *p) +{ + while (*p != '\0' && *p != '\\' && *p != ';') + p++; + return p; +} + +/* the written bitmap */ +unsigned char membit[65536 / 8]; + +void +setbit(int pc) +{ + membit[pc / 8] |= (1 << (pc % 8)); +} + +int +isset(int pc) +{ + return membit[pc / 8] & (1 << (pc % 8)); +} + +void +open_output() +{ + fout = efopen(s_objfname, "wb"); +} + +void +close_output() +{ + if (fclose(fout) == EOF) { + eprint(_("cannot close file %s\n"), s_objfname); + } +} + +/* + * Generates a byte to the output and updates s_pc, s_minpc and s_maxpc. + * Will issue a fatal error if we write beyong 64k. + */ +void genb(int b, const char *ep) +{ + if (s_pass == 0 && s_end_seen && !s_gen_after_end) { + s_gen_after_end = 1; + eprint(_("generating code after .END\n")); + eprcol(s_pline, ep); + newerr(); + } + if (s_minpc < 0) + s_minpc = s_pc; + if (s_pc >= 65536) { + eprint(_("generating code beyond address 65535\n")); + eprcol(s_pline, ep); + exit(EXIT_FAILURE); + } + s_mem[s_pc] = (unsigned char) b; + setbit(s_pc); + + if (s_pass == 1) { + list_genb(b); + if (b_flag) { + fwrite(&s_mem[s_pc], 1, 1, fout); + } + } + + if (s_pc < s_minpc) + s_minpc = s_pc; + s_pc++; + if (s_pc > s_maxpc) + s_maxpc = s_pc; +} + +/* + * Generate 'n' as a 16 bit word, little endian or big endian depending on + * s_msbword. + */ +static void genw(int n, const char *ep) +{ + if (s_msbword) + genb(n >> 8, ep); + genb(n, ep); + if (!s_msbword) + genb(n >> 8, ep); +} + +/* + * We have matched an instruction in the table. + * Generate the machine code for the instruction using the generation + * pattern 'p. 'vs are the arguments generated during the matching process. + */ +static void gen(const char *p, const int *vs) +{ + // int w, b, i, savepc; + int b, i, savepc; + const char *p_orig; + + savepc = s_pc; + p_orig = p; + b = 0; +loop: + i = hexvalu(*p); + if (i >= 0) { + p++; + b = (i << 4) | hexval(*p); + } else if (*p == '.') { + genb(b, s_pline_ep); + b = 0; + } else if (*p == '\0') { + return; + } else { + i = *(p + 1) - '0'; + switch (*p) { + case 'b': b |= (vs[i] << 3); break; + case 'c': b |= vs[i]; break; + case 'd': b = vs[i]; break; + case 'e': genb(vs[i] & 0xff, s_pline_ep); + genb(vs[i] >> 8, s_pline_ep); + break; + default: + if (s_target->genf(&b, *p, vs, i, savepc) == -1) { + eprogname(); + fprintf(stderr, + _("fatal: bad pattern %s ('%c')"), + p_orig, *p); + enl(); + exit(EXIT_FAILURE); + } + } + p++; + } + p++; + goto loop; +} + +/* + * Tries to match *p with any of the strings in list. + * If matched, returns the index in list and r points to the position + * in p past the matched string. + */ +int mreg(const char *p, const char *const list[], const char **r) +{ + const char *s; + const char *q; + int i; + + i = 0; + while ((s = list[i++]) != NULL) { + if (*s == '\0') + continue; + q = p; + while (toupper(*q++) == *s++) { + if (*s == '\0') { + if (!isalnum(*q)) { + *r = q; + return i - 1; + } else { + break; + } + } + } + } + return -1; +} + +static int isoctal(int c) +{ + return c >= '0' && c <= '7'; +} + +/* + * Read an octal of 3 digits, being the maximum value 377 (255 decimal); + * Return -1 if there is an error in the syntax. + */ +static int readoctal(const char *p) +{ + int n; + const char *q; + + if (*p >= '0' && *p <= '3' && isoctal(*(p + 1)) && isoctal(*(p + 2))) { + n = 0; + q = p + 3; + while (p < q) { + n *= 8; + n += (*p - '0'); + p++; + } + return n; + } + + return -1; +} + +enum strmode { + STRMODE_ECHO, + STRMODE_NULL, + STRMODE_BYTE, + STRMODE_WORD +}; + +/* + * Generate the string bytes until double quote or null char. + * Return a pointer to the ending double quote character or '\0'. + * 'p must point to the starting double quote. + * If mode: + * STRMODE_ECHO only echo to stderr the characters. + * STRMODE_NULL only parses the string. + * STRMODE_BYTE generate the characters in the binary file as bytes. + * STRMODE_WORD generate the characters in the binary file as words. + */ +static const char *genstr(const char *p, enum strmode mode) +{ + int c; + + for (p = p + 1; *p != '\0' && *p != '\"'; p++) { + c = *p; + if (c == '\\') { + p++; + switch (*p) { + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 'b': c = '\b'; break; + case 't': c = '\t'; break; + case 'f': c = '\f'; break; + case '\\': c = '\\'; break; + case '\"': c = '\"'; break; + default: + c = readoctal(p); + if (c < 0) { + eprint(_("bad character escape " + "sequence\n")); + eprcol(s_pline, p - 1); + newerr(); + p--; + } else { + p += 2; + } + } + } + switch (mode) { + case STRMODE_ECHO: fputc(c, stderr); break; + case STRMODE_NULL: break; + case STRMODE_BYTE: genb(c, p); break; + case STRMODE_WORD: genw(c, p); break; + } + } + + return p; +} + +/* Match an instruction. + * If no match returns NULL; else returns one past end of match. + * p should point to no whitespace. + */ +static const char *match(const char *p) +{ + const struct matchtab *mtab; + const char *s, *pp, *q; + int v, n, vi, linepc; + int vs[4]; + + assert(!isspace(*p)); + + mtab = s_target->matcht; + linepc = s_pc; + pp = p; + n = -1; +next: + n++; + s = mtab[n].pat; + if (s == NULL) { + return NULL; + } else if ((s_target->mask & mtab[n].mask) == 0) { + goto next; + } else if (!s_undocumented_op && (s_target->mask & mtab[n].undoc)) { + goto next; + } + p = pp; + vi = 0; +loop: + if (*s == '\0') { + p = skipws(p); + if (*p != ';' && *p != '\0' && *p != '\\') + goto next; + else + goto found; + } else if (*s == ' ') { + if (!isspace(*p)) + goto next; + p = skipws(p); + } else if ((*s == ',' || *s == '(' || *s == ')') && isspace(*p)) { + p = skipws(p); + if (*s != *p) + goto next; + p = skipws(p + 1); + } else if (*s == 'a') { + p = expr(p, &v, linepc, s_pass == 0, NULL, NULL); + if (p == NULL) + return NULL; + vs[vi++] = v; + } else if (*s >= 'b' && *s <= 'z') { + v = s_target->matchf(*s, p, &q); + goto reg; + } else if (*p == *s && *p == ',') { + p = skipws(p + 1); + } else if (toupper(*p) == *s) { + p++; + } else { + goto next; + } +freg: + s++; + goto loop; +reg: + if (v < 0) { + goto next; + } else { + assert(vi < sizeof(vs)); + vs[vi++] = v; + p = q; + } + goto freg; +found: + // printf("%s\n", s_matchtab[n].pat); + gen(mtab[n].gen, vs); + return p; +} + +static const char * +d_null(const char *p) +{ + p = sync(p); + while (*p != '\0' && *p != '\\') { + if (!isspace(*p)) { + wprint(_("invalid characters after directive\n")); + eprcol(s_pline, p); + return sync(p); + } else { + p++; + } + } + return p; +} + +static const char *d_end(const char *p) +{ + enum expr_ecode ecode; + const char *q; + const char *ep; + + if (s_pass == 0) { + if (s_end_seen) { + eprint(_("duplicate .END\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + } else { + s_end_seen = 1; + } + } + + q = expr(p, NULL, s_pc, s_pass == 0, &ecode, &ep); + if (q == NULL && ecode == EXPR_E_NO_EXPR) { + return p; + } else if (q == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } else { + return q; + } +} + +static const char *d_codes(const char *p) +{ + s_codes = 1; + return p; +} + +static const char *d_module(const char *p) +{ + p = sync(p); + while (*p != '\0' && *p != '\\') { + if (!isspace(*p)) { + wprint(_("invalid characters after directive\n")); + eprcol(s_pline, p); + return sync(p); + } else { + p++; + } + } + return p; +} + +static const char *d_nocodes(const char *p) +{ + s_codes = 0; + return p; +} + +static const char *d_list(const char *p) +{ + s_list_on = 1; + return p; +} + +static const char *d_nolist(const char *p) +{ + s_list_on = 0; + return p; +} + +static const char *d_eject(const char *p) +{ + list_eject(); + return p; +} + +static const char *d_echo(const char *p) +{ + int n; + int mode; + enum expr_ecode ecode; + const char *ep; + + mode = (s_pass == 0) ? STRMODE_NULL : STRMODE_ECHO; + if (*p == '\"') { + p = genstr(p, mode); + if (*p == '\"') { + p++; + } else if (s_pass == 0) { + wprint(_("no terminating quote\n")); + eprcol(s_pline, p); + } + } else if (*p != '\0') { + p = expr(p, &n, s_pc, s_pass == 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + if (mode == STRMODE_ECHO) { + fprintf(stderr, "%d", n); + } + } + return p; +} + +static const char *d_equ(const char *p) +{ + int n; + enum expr_ecode ecode; + const char *ep; + + p = expr(p, &n, s_pc, 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + + if (s_lastsym == NULL) { + eprint(_(".EQU without label\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + } else { + /* TODO: check label misalign? */ + s_lastsym->val = n; + s_lastsym->isequ = 1; + } + return p; +} + +static const char *d_set(const char *p) +{ + int n; + enum expr_ecode ecode; + const char *ep; + + p = expr(p, &n, s_pc, 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + + if (s_lastsym == NULL) { + eprint(_(".EQU without label\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + } else { + /* TODO: check label misalign? */ + s_lastsym->val = n; + s_lastsym->isequ = 1; + } + return p; +} + +static const char *d_export(const char *p) +{ + /* TODO */ + return NULL; +} + +static const char *d_fill(const char *p) +{ + int n, v, er; + const char *q; + enum expr_ecode ecode; + const char *ep, *eps; + + eps = p; + er = 0; + p = expr(p, &n, s_pc, 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + + if (n < 0) { + eprint(_("number of positions to fill is negative (%d)\n"), n); + eprcol(s_pline, eps); + exit(EXIT_FAILURE); + } + + v = 255; + p = skipws(p); + if (*p == ',') { + p = skipws(p + 1); + q = expr(p, &v, s_pc, s_pass == 0, &ecode, &ep); + if (q == NULL) { + er = 1; + exprint(ecode, s_pline, ep); + newerr(); + } else { + p = q; + } + } + + while (n--) + genb(v, eps); + + if (er) + return NULL; + else + return p; +} + +static const char *d_ds(const char *p) +{ + int n, v, er; + const char *q; + enum expr_ecode ecode; + const char *ep, *eps; + + eps = p; + er = 0; + p = expr(p, &n, s_pc, 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + + if (n < 0) { + eprint(_("number of positions to space over is negative (%d)\n"), n); + eprcol(s_pline, eps); + exit(EXIT_FAILURE); + } + + v = 255; + p = skipws(p); + if (*p == ',') { + p = skipws(p + 1); + q = expr(p, &v, s_pc, s_pass == 0, &ecode, &ep); + if (q == NULL) { + er = 1; + exprint(ecode, s_pline, ep); + newerr(); + } else { + p = q; + } + } + + s_pc += n; + + if (er) + return NULL; + else + return p; +} + +static const char *d_lsfirst(const char *p) +{ + s_msbword = 0; + return p; +} + +static const char *d_msfirst(const char *p) +{ + s_msbword = 1; + return p; +} + +static const char *d_org(const char *p) +{ + int n; + enum expr_ecode ecode; + const char *ep, *eps; + + eps = p; + p = expr(p, &n, s_pc, 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + + if (n < 0 || n > 65536) { + eprint(_(".ORG address (%d) is not in range [0, 65536]\n"), n); + eprcol(s_pline, eps); + exit(EXIT_FAILURE); + } + + s_pc = n; + + /* Change the listing PC so in orgs we print the changed PC. */ + if (s_pass > 0) + list_setpc(s_pc); + + if (s_lastsym != NULL) { + /* TODO: check label misalign? */ + s_lastsym->val = s_pc; + s_lastsym->isequ = 1; + } + + return p; +} + +static const char *d_lst(const char *p, int w) +{ + enum strmode mode; + int n, linepc; + enum expr_ecode ecode; + const char *ep, *eps; + + if (w) + mode = STRMODE_WORD; + else + mode = STRMODE_BYTE; + + linepc = s_pc; +dnlst: + if (*p == '\"') { + p = genstr(p, mode); + if (*p == '\"') { + p++; + } else { + wprint(_("no terminating quote\n")); + eprcol(s_pline, p); + } + } else { + eps = p; + p = expr(p, &n, linepc, s_pass == 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + if (w) + genw(n, eps); + else + genb(n, eps); + } + p = skipws(p); + if (*p == ',') { + p++; + p = skipws(p); + goto dnlst; + } + return p; +} + +static const char *d_align(const char *p) +{ + int n, v, er; + const char *q; + enum expr_ecode ecode; + const char *ep, *eps; + + eps = p; + er = 0; + p = expr(p, &n, s_pc, 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + + if (n < 0) { + eprint(_("align is negative (%d)\n"), n); + eprcol(s_pline, eps); + exit(EXIT_FAILURE); + } + + while (s_pc % n) { + genb(0, eps); + } + + if (er) + return NULL; + else + return p; +} + +static const char *d_byte(const char *p) +{ + return d_lst(p, 0); +} + +static const char *d_word(const char *p) +{ + return d_lst(p, 1); +} + +static const char *d_text(const char *p) +{ + if (*p == '\"') { + p = genstr(p, STRMODE_BYTE); + if (*p == '\"') { + p++; + } else { + wprint(_("no terminating quote\n")); + eprcol(s_pline, p); + } + return p; + } else { + eprint(_(".TEXT directive needs a quoted string argument\n")); + eprcol(s_pline, p); + newerr(); + return NULL; + } +} + +static const char *d_title(const char *p) +{ + return NULL; +} + +static const char *d_block(const char *p) +{ + int n; + enum expr_ecode ecode; + const char *ep, *eps; + + eps = p; + p = expr(p, &n, s_pc, 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + return NULL; + } + + s_pc += n; + if (s_pc < 0 || s_pc > 65536) { + eprint(_("address (%d) set by .BLOCK is not in range " + "[0, 65536]\n"), s_pc); + eprcol(s_pline, eps); + exit(EXIT_FAILURE); + } + + return p; +} + +/* a must be < b. */ +static int checksum(int a, int b) +{ + int n; + + assert(a < b); + + n = 0; + while (a < b) + n += s_mem[a++]; + + return n; +} + +static const char *d_chk(const char *p) +{ + int n; + enum expr_ecode ecode; + const char *ep, *eps; + + eps = p; + p = expr(p, &n, s_pc, s_pass == 0, &ecode, &ep); + if (p == NULL) { + exprint(ecode, s_pline, ep); + newerr(); + genb(0, eps); + return NULL; + } + + if (s_pass == 0) { + genb(0, s_pline_ep); + } else if (n < 0 || n >= s_pc) { + eprint(_(".CHK address (%d) is not in range [0, %d[\n"), n, + s_pc); + eprcol(s_pline, eps); + newerr(); + genb(0, eps); + } else { + genb(checksum(n, s_pc), eps); + } + + return p; +} + +/* Parses an internal directive (those that start with '.'). + * Returns NULL on error; + * If no error returns position past the parsed directive and arguments. */ +static const char *parse_direc(const char *cp) +{ + const char *cq, *p; + int a, b, m = 0; + + a = 0; + b = NELEMS(s_directab) - 1; + while (a <= b) { + m = (a + b) / 2; + cq = cp; + p = s_directab[m].name; + while (*p != '\0' && toupper(*cq) == *p) { + p++; + cq++; + } + if (*p == '\0' && (*cq == '\0' || isspace(*cq))) + break; + else if (toupper(*cq) < *p) + b = m - 1; + else + a = m + 1; + } + + if (a <= b) { + cq = skipws(cq); + return s_directab[m].fun(cq); + } else { + eprint(_("unrecognized directive\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + return NULL; + } +} + +static void parselin(const char *cp) +{ + int col0, alloweq; + const char *q; + + s_pline_ep = cp; +start: s_lastsym = NULL; + alloweq = 0; + col0 = 1; +loop: + if (*cp == '\0' || *cp == ';') { + return; + } else if (*cp == '\\') { + if (s_pass == 1) { + list_endln(); + list_startln(s_empty_line, curfile()->linenum, s_pc, + nfiles()); + } + cp++; + goto start; + } else if (*cp == '.') { + s_pline_ep = cp; + cp++; + q = parse_direc(cp); + if (q == NULL) { + cp = sync(cp); + } else { + cp = d_null(q); + } + } else if ((*cp == '$' || *cp == '*') && cp[1] == '=') { + /* Alternative form of .ORG: *= or $= */ + cp += 2; + q = d_org(cp); + if (q == NULL) { + cp = sync(cp); + } else { + cp = d_null(q); + } + } else if (*cp == '=' && alloweq) { + /* equ */ + s_pline_ep = cp; + cp++; + q = d_equ(cp); + if (q == NULL) { + cp = sync(cp); + } else { + cp = d_null(q); + } + } else if (isidc0(*cp)) { + if (col0 && *cp != '.') { + /* take label */ + s_pline_ep = cp; + q = cp; + col0 = 0; + while (isidc(*cp)) + cp++; + s_lastsym = lookup(q, cp, s_pass == 0, s_pc); + if (*cp == ':' || isspace(*cp)) { + alloweq = 1; + cp++; + } else if (*cp == '=') { + alloweq = 1; + } + if (s_pass == 1 && !s_lastsym->isequ && + s_lastsym->val != s_pc) + { + eprint(_("misaligned label %s\n"), + s_lastsym->name); + fprintf(stderr, _(" Previous value was %XH, " + "new value %XH."), s_lastsym->val, + s_pc); + eprcol(s_pline, s_pline_ep); + newerr(); + } + } else { + cp = skipws(cp); + s_pline_ep = cp; + q = match(cp); + if (q == NULL) { + eprint(_("syntax error\n")); + newerr(); + cp = sync(cp); + } else { + cp = d_null(q); + } + } + } else if (isspace(*cp)) { + col0 = 0; + while (isspace(*cp)) + cp++; + } else { + eprint(_("unexpected character (%c)\n"), *cp); + eprcol(s_pline, cp); + newerr(); + cp = sync(cp + 1); + } + goto loop; +} + +/* + * Gets a new line into 's_line from 'fin. + * Terminates the line with '\0'. + * Does not read more than LINESZ - 1 characters. + * Does not add a '\n' character, thus a line of length 0 it's possible. + * Always advances to the next line. + * Returns -1 for EOF or the line length. + */ +static int getlin(FILE *fin) +{ + int i, c; + + c = EOF; + i = 0; + while (i < LINESZ - 1) { + c = getc(fin); + if (c == EOF || c == '\n') + break; + s_line[i++] = (char) c; + } + if (c != EOF && c != '\n') { + wprint(_("line too long, truncated to %d characters\n"), + LINESZ); + } + while (c != EOF && c != '\n') + c = getc(fin); + if (i == 0 && c == EOF) + return -1; + s_line[i] = '\0'; + return i; +} + +/* Preinstall the macros defined in the command line. */ +static void install_predefs(void) +{ + struct predef *pdef; + + for (pdef = s_predefs; pdef != NULL; pdef = pdef->next) + pp_define(pdef->name); +} + +/* Do a pass through the source. */ +static void dopass(const char *fname) +{ + /* Fill memory with default value. */ + if ((s_pass == 0 && s_mem_fillval != 0) || s_pass > 0) { + memset(s_mem, s_mem_fillval, sizeof(s_mem)); + } + + if (s_pass > 0) { + pp_reset(); + list_open(s_lstfname); + s_codes = 1; + s_list_on = 1; + } + + + install_predefs(); + s_minpc = -1; + s_maxpc = -1; + s_pc = 0; + s_lastsym = NULL; + s_msbword = 0; + + pushfile(fname, fname + strlen(fname)); + while (nfiles() > 0) { + curfile()->linenum++; + if (getlin(curfile()->fin) >= 0) { + if (s_pass == 1) { + list_startln(s_line, curfile()->linenum, s_pc, + nfiles()); + } + pp_line(s_line); + if (s_pass == 1) + list_skip(s_skipon); + parselin(s_pline); + if (s_pass == 1) + list_endln(); + } else { + popfile(); + } + } + + if (s_pass > 0) { + list_close(); + } +} + +/* + * Write the object file in memory order + */ +static void output() +{ + int i; + + // fprintf(stderr, "output: min: %x max: %x\n", s_minpc, s_maxpc); + + if (s_minpc < 0) + s_minpc = 0; + if (s_maxpc < 0) + s_maxpc = 0; + + for (i = s_minpc; i < s_maxpc; i++) { + if (isset(i)) { + fwrite(&s_mem[i], 1, 1, fout); + } + } + if (ferror(fout)) { + eprint(_("cannot write to file %s\n"), s_objfname); + clearerr(fout); + } +} + +/* Start the assembly using the config in options.c. */ +void uz80as(void) +{ + s_target = find_target(s_target_id); + if (s_target == NULL) { + eprint(_("target '%s' not supported\n"), s_target_id); + exit(EXIT_FAILURE); + } + + for (s_pass = 0; s_nerrors == 0 && s_pass < 2; s_pass++) { + if ((s_pass > 0) && (s_nerrors == 0)) { + open_output(); + } + dopass(s_asmfname); + if (s_pass == 0 && !s_end_seen) { + wprint(_("no .END statement in the source\n")); + } + if (s_nerrors == 0) { + if (verbose) printf("Pass %d completed.\n", s_pass + 1); + } + } + + if (s_nerrors > 0) { + exit(EXIT_FAILURE); + } + + if (!b_flag) { + output(); + } + close_output(); +} diff --git a/Tools/unix/uz80as/uz80as.h b/Tools/unix/uz80as/uz80as.h new file mode 100644 index 00000000..511da085 --- /dev/null +++ b/Tools/unix/uz80as/uz80as.h @@ -0,0 +1,63 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Assembler. + * =========================================================================== + */ + +#ifndef UZ80AS_H +#define UZ80AS_H + +int verbose; + +/* matchtab.flags */ +enum { + MATCH_F_UNDOC = 1, + MATCH_F_EXTEN = 2, +}; + +/* pat: + * a: expr + * b - z: used by target + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f - z: used by target + * + * pr: + * 8: e8 + * f: e16 + * r: relative jump + */ + +struct matchtab { + const char *pat; + const char *gen; + unsigned char mask; + unsigned char undoc; + const char *pr; +}; + +struct target { + const char *id; + const char *descr; + const struct matchtab *matcht; + int (*matchf)(char c, const char *p, const char **q); + int (*genf)(int *eb, char p, const int *vs, int i, int savepc); + void (*pat_char_rewind)(int c); + const char * (*pat_next_str)(void); + unsigned char mask; +}; + +extern const char *s_pline_ep; + +void genb(int b, const char *ep); +int mreg(const char *p, const char *const list[], const char **r); + +void uz80as(void); + +#endif diff --git a/Tools/unix/uz80as/z80.c b/Tools/unix/uz80as/z80.c new file mode 100644 index 00000000..4e2db514 --- /dev/null +++ b/Tools/unix/uz80as/z80.c @@ -0,0 +1,362 @@ +/* =========================================================================== + * uz80as, an assembler for the Zilog Z80 and several other microprocessors. + * + * Zilog Z80 CPU. + * =========================================================================== + */ + +#include "pp.h" +#include "err.h" +#include "options.h" +#include "uz80as.h" +#include + +/* pat: + * a: expr + * b: B,C,D,E,H,L,A + * c: IX,IY (must be followed by + or -) + * d: BC,DE,HL,SP + * e: IX,IY + * f: BC,DE,HL,AF + * g: ADD,ADC,SUB,SBC,AND,XOR,OR,CP + * h: INC,DEC + * i: BC,DE,IX,SP + * j: BC,DE,IY,SP + * k: RLC,RRC,RL,RR,SLA,SRA,SRL + * l: BIT,RES,SET + * m: NZ,Z,NC,C,PO,PE,P,M + * n: NZ,Z,NC,C + * o: * + * p: B,C,D,E,IXH,IXL,A + * q: B,C,D,E,IYH,IYL,A + * + * gen: + * .: output lastbyte + * b: (op << 3) | lastbyte + * c: op | lastbyte + * d: lastbyte = op as 8 bit value + * e: output op as word (no '.' should follow) + * f: (op << 4) | lastbyte + * g: (op << 6) | lastbyte + * h: * + * i: relative jump to op + * j: possible value to RST + * k: possible value to IM + * m: check arithmetic used with A register + * n: check arithmetic used without A register + */ + +static const struct matchtab s_matchtab_z80[] = { + { "LD b,b", "40b0c1.", 3, 0 }, + { "LD p,p", "DD.40b0c1.", 1, 1 }, + { "LD q,q", "FD.40b0c1.", 1, 1 }, + { "LD b,(HL)", "46b0.", 3, 0 }, + { "LD b,(e)", "d1.46b0.00.", 3, 0, "ii" }, + { "LD b,(ca)", "d1.46b0.d2.", 3, 0, "ii" }, + { "LD A,I", "ED.57.", 3, 0 }, + { "LD A,R", "ED.5F.", 3, 0 }, + { "LD A,(BC)", "0A.", 3, 0 }, + { "LD A,(DE)", "1A.", 3, 0 }, + { "LD A,(a)", "3A.e0", 3, 0 }, + { "LD b,a", "06b0.d1.", 3, 0, "e8" }, + { "LD p,a", "DD.06b0.d1.", 1, 1, "e8" }, + { "LD q,a", "FD.06b0.d1.", 1, 1, "e8" }, + { "LD I,A", "ED.47.", 3, 0 }, + { "LD R,A", "ED.4F.", 3, 0 }, + { "LD SP,HL", "F9.", 3, 0 }, + { "LD SP,e", "d0.F9.", 3, 0 }, + { "LD HL,(a)", "2A.e0", 3, 0 }, + { "LD d,(a)", "ED.4Bf0.e1", 3, 0 }, + { "LD d,a", "01f0.e1", 3, 0 }, + { "LD e,(a)", "d0.2A.e1", 3, 0 }, + { "LD e,a", "d0.21.e1", 3, 0 }, + { "LD (HL),b", "70c0.", 3, 0 }, + { "LD (HL),a", "36.d0.", 3, 0, "e8" }, + { "LD (BC),A", "02.", 3, 0 }, + { "LD (DE),A", "12.", 3, 0 }, + { "LD (e),b", "d0.70c1.00.", 3, 0, "ii" }, + { "LD (ca),b", "d0.70c2.d1.", 3, 0, "ii" }, + { "LD (e),a", "d0.36.00.d1.", 3, 0, "iie8" }, + { "LD (ca),a", "d0.36.d1.d2.", 3, 0, "iie8" }, + { "LD (a),A", "32.e0", 3, 0 }, + { "LD (a),HL", "22.e0", 3, 0 }, + { "LD (a),d", "ED.43f1.e0", 3, 0 }, + { "LD (a),e", "d1.22.e0", 3, 0 }, + { "PUSH f", "C5f0.", 3, 0 }, + { "PUSH e", "d0.E5.", 3, 0 }, + { "POP f", "C1f0.", 3, 0 }, + { "POP e", "d0.E1.", 3, 0 }, + { "EX DE,HL", "EB.", 3, 0 }, + { "EX AF,AF'", "08.", 3, 0 }, + { "EX (SP),HL", "E3.", 3, 0 }, + { "EX (SP),e", "d0.E3.", 3, 0 }, + { "EXX", "D9.", 3, 0 }, + { "LDI", "ED.A0.", 3, 0 }, + { "LDIR", "ED.B0.", 3, 0 }, + { "LDD", "ED.A8.", 3, 0 }, + { "LDDR", "ED.B8.", 3, 0 }, + { "CPI", "ED.A1.", 3, 0 }, + { "CPIR", "ED.B1.", 3, 0 }, + { "CPD", "ED.A9.", 3, 0 }, + { "CPDR", "ED.B9.", 3, 0 }, + { "ADD HL,d", "09f0.", 3, 0 }, + { "ADD IX,i", "DD.09f0.", 3, 0 }, + { "ADD IY,j", "FD.09f0.", 3, 0 }, + { "ADC HL,d", "ED.4Af0.", 3, 0 }, + { "SBC HL,d", "ED.42f0.", 3, 0 }, + { "g A,b", "m080b0c1.", 3, 0 }, + { "g A,p", "DD.m080b0c1.", 1, 1 }, + { "g A,q", "FD.m080b0c1.", 1, 1 }, + { "g A,(HL)", "m086b0.", 3, 0 }, + { "g A,(ca)", "m0d1.86b0.d2.", 3, 0, "ii" }, + { "g A,a", "m0C6b0.d1.", 3, 0, "e8" }, + { "g b", "n080b0c1.", 3, 0 }, + { "g p", "DD.n080b0c1.", 1, 1 }, + { "g q", "FD.n080b0c1.", 1, 1 }, + { "g (HL)", "n086b0.", 3, 0 }, + { "g (ca)", "n0d1.86b0.d2.", 3, 0, "ii" }, + { "g a", "n0C6b0.d1.", 3, 0, "e8" }, + { "h b", "04b1c0.", 3, 0 }, + { "h p", "DD.04b1c0.", 1, 1 }, + { "h q", "FD.04b1c0.", 1, 1 }, + { "h (HL)", "34c0.", 3, 0 }, + { "h (ca)", "d1.34c0.d2.", 3, 0, "ii" }, + { "h (e)", "d1.34c0.00.", 3, 0, "ii" }, + { "INC d", "03f0.", 3, 0 }, + { "INC e", "d0.23.", 3, 0 }, + { "DEC d", "0Bf0.", 3, 0 }, + { "DEC e", "d0.2B.", 3, 0 }, + { "DAA", "27.", 3, 0 }, + { "CPL", "2F.", 3, 0 }, + { "NEG", "ED.44.", 3, 0 }, + { "CCF", "3F.", 3, 0 }, + { "SCF", "37.", 3, 0 }, + { "NOP", "00.", 3, 0 }, + { "HALT", "76.", 3, 0 }, + { "DI", "F3.", 3, 0 }, + { "EI", "FB.", 3, 0 }, + { "IM a", "ED.k0.", 3, 0, "tt" }, + { "RLCA", "07.", 3, 0 }, + { "RLA", "17.", 3, 0 }, + { "RRCA", "0F.", 3, 0 }, + { "RRA", "1F.", 3, 0 }, + { "SLL b", "CB.30c0.", 1, 1 }, + { "SLL (HL)", "CB.36.", 1, 1 }, + { "SLL (ca)", "d0.CB.d1.36.", 1, 1, "ii" }, + { "SLL (ca),b", "d0.CB.d1.30c2.", 1, 1, "ii" }, + { "k b", "CB.00b0c1.", 3, 0 }, + { "k (HL)", "CB.06b0.", 3, 0 }, + { "k (ca)", "d1.CB.d2.06b0.", 3, 0, "ii" }, + { "k (ca),b", "d1.CB.d2.00b0c3.", 1, 1, "ii" }, + { "RLD", "ED.6F.", 3, 0 }, + { "RRD", "ED.67.", 3, 0 }, + { "l a,b", "CB.00g0b1c2.", 3, 0, "b3" }, + { "l a,(HL)", "CB.06g0b1.", 3, 0, "b3" }, + { "l a,(ca)", "d2.CB.d3.06g0b1.", 3, 0, "b3ii" }, + { "RES a,(ca),b", "d1.CB.d2.80b0c3.", 1, 1, "b3ii" }, + { "SET a,(ca),b", "d1.CB.d2.C0b0c3.", 1, 1, "b3ii" }, + { "JP (HL)", "E9.", 3, 0 }, + { "JP (e)", "d0.E9.", 3, 0 }, + { "JP m,a", "C2b0.e1", 3, 0 }, + { "JP a", "C3.e0", 3, 0 }, + { "JR n,a", "20b0.i1.", 3, 0, "r8" }, + { "JR a", "18.i0.", 3, 0, "r8" }, + { "DJNZ a", "10.i0.", 3, 0, "r8" }, + { "CALL m,a", "C4b0.e1", 3, 0 }, + { "CALL a", "CD.e0", 3, 0 }, + { "RETI", "ED.4D.", 3, 0 }, + { "RETN", "ED.45.", 3, 0 }, + { "RET m", "C0b0.", 3, 0 }, + { "RET", "C9.", 3, 0 }, + { "RST a", "C7j0.", 3, 0, "ss" }, + { "IN b,(C)", "ED.40b0.", 3, 0 }, + { "IN A,(a)", "DB.d0.", 3, 0, "e8" }, + { "IN F,(a)", "ED.70.", 3, 0 }, + { "IN (C)", "ED.70.", 1, 1 }, + { "INI", "ED.A2.", 3, 0 }, + { "INIR", "ED.B2.", 3, 0 }, + { "IND", "ED.AA.", 3, 0 }, + { "INDR", "ED.BA.", 3, 0 }, + { "OUT (C),0", "ED.71.", 1, 1 }, + { "OUT (C),b", "ED.41b0.", 3, 0 }, + { "OUT (a),A", "D3.d0.", 3, 0, "e8" }, + { "OUTI", "ED.A3.", 3, 0 }, + { "OTIR", "ED.B3.", 3, 0 }, + { "OUTD", "ED.AB.", 3, 0 }, + { "OTDR", "ED.BB.", 3, 0 }, + /* hd64180 added instructions */ + { "IN0 b,(a)", "ED.00b0.d1.", 2, 0, "e8" }, + { "OUT0 (a),b", "ED.01b1.d0.", 2, 0, "e8" }, + { "OTDM", "ED.8B.", 2, 0 }, + { "OTDMR", "ED.9B.", 2, 0 }, + { "OTIM", "ED.83.", 2, 0 }, + { "OTIMR", "ED.93.", 2, 0 }, + { "MLT d", "ED.4Cf0.", 2, 0 }, + { "SLP", "ED.76.", 2, 0 }, + { "TST b", "ED.04b0.", 2, 0 }, + { "TST (HL)", "ED.34.", 2, 0 }, + { "TST a", "ED.64.d0.", 2, 0, "e8" }, + { "TSTIO a", "ED.74.d0.", 2, 0, "e8" }, + { NULL, NULL }, +}; + +static const char *const bval[] = { "B", "C", "D", "E", + "H", "L", "", "A", NULL }; +static const char *const cval[] = { "IX", "IY", NULL }; +static const char *const dval[] = { "BC", "DE", "HL", "SP", NULL }; +static const char *const fval[] = { "BC", "DE", "HL", "AF", NULL }; +static const char *const gval[] = { "ADD", "ADC", "SUB", "SBC", + "AND", "XOR", "OR", "CP", NULL }; +static const char *const hval[] = { "INC", "DEC", NULL }; +static const char *const ival[] = { "BC", "DE", "IX", "SP", NULL }; +static const char *const jval[] = { "BC", "DE", "IY", "SP", NULL }; +static const char *const kval[] = { "RLC", "RRC", "RL", "RR", + "SLA", "SRA", "", "SRL", NULL }; +static const char *const lval[] = { "", "BIT", "RES", "SET", NULL }; +static const char *const mval[] = { "NZ", "Z", "NC", "C", + "PO", "PE", "P", "M", NULL }; +static const char *const nval[] = { "NZ", "Z", "NC", "C", NULL }; +static const char *const pval[] = { "B", "C", "D", "E", + "IXH", "IXL", "", "A", NULL }; +static const char *const qval[] = { "B", "C", "D", "E", + "IYH", "IYL", "", "A", NULL }; +static const char *const nullv[] = { NULL }; + +static const char *const *const valtab[] = { + bval, cval, dval, dval, fval, + gval, hval, ival, jval, kval, + lval, mval, nval, nullv, pval, + qval +}; + +static int indval(const char *p, int disp, const char **q) +{ + int v; + const char *r; + + v = mreg(p, cval, &r); + if (v >= 0) { + v = (v == 0) ? 0xDD : 0xFD; + while (*r == ' ') r++; + if (!disp || *r == '+' || *r == '-') { + *q = r; + return v; + } + } + return -1; +} + +static int match_z80(char c, const char *p, const char **q) +{ + int v; + + if (c == 'c' || c == 'e') { + v = indval(p, c == 'c', q); + } else if (c <= 'q') { + v = mreg(p, valtab[(int) (c - 'b')], q); + } else { + v = -1; + } + + return v; +} + +static int gen_z80(int *eb, char p, const int *vs, int i, int savepc) +{ + int b; + + b = *eb; + switch (p) { + case 'f': b |= (vs[i] << 4); break; + case 'g': b |= (vs[i] << 6); break; + case 'i': b = (vs[i] - savepc - 2); break; + case 'j': if (s_pass > 0 && (vs[i] & ~56) != 0) { + eprint(_("invalid RST argument (%d)\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b |= vs[i]; + break; + case 'k': if (s_pass > 0 && (vs[i] < 0 || vs[i] > 2)) { + eprint(_("invalid IM argument (%d)\n"), + vs[i]); + eprcol(s_pline, s_pline_ep); + newerr(); + } + b = 0x46; + if (vs[i] == 1) + b = 0x56; + else if (vs[i] == 2) + b = 0x5E; + break; + case 'm': if (s_pass == 0 && !s_extended_op) { + if (vs[i] != 0 && vs[i] != 1 && vs[i] != 3) { + eprint(_("unofficial syntax\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + } + } + break; + case 'n': if (s_pass == 0 && !s_extended_op) { + if (vs[i] == 0 || vs[i] == 1 || vs[i] == 3) { + eprint(_("unofficial syntax\n")); + eprcol(s_pline, s_pline_ep); + newerr(); + } + } + break; + default: + return -1; + } + + *eb = b; + return 0; +} + +static int s_pat_char = 'b'; +static int s_pat_index; + +static void pat_char_rewind_z80(int c) +{ + s_pat_char = c; + s_pat_index = 0; +}; + +static const char *pat_next_str_z80(void) +{ + const char *s; + + if (s_pat_char >= 'b' && s_pat_char <= 'q') { + s = valtab[(int) (s_pat_char - 'b')][s_pat_index]; + if (s != NULL) { + s_pat_index++; + } + } else { + s = NULL; + } + + return s; +}; + +const struct target s_target_z80 = { + .id = "z80", + .descr = "Zilog Z80", + .matcht = s_matchtab_z80, + .matchf = match_z80, + .genf = gen_z80, + .pat_char_rewind = pat_char_rewind_z80, + .pat_next_str = pat_next_str_z80, + .mask = 1 +}; + +const struct target s_target_hd64180 = { + .id = "hd64180", + .descr = "Hitachi HD64180", + .matcht = s_matchtab_z80, + .matchf = match_z80, + .genf = gen_z80, + .pat_char_rewind = pat_char_rewind_z80, + .pat_next_str = pat_next_str_z80, + .mask = 2 +}; diff --git a/Tools/unix/zx/Makefile b/Tools/unix/zx/Makefile new file mode 100644 index 00000000..995d1243 --- /dev/null +++ b/Tools/unix/zx/Makefile @@ -0,0 +1,37 @@ +# +# hacked up brute force makefile for linux and osx +# +UNAME := $(shell uname) +ifeq ($(UNAME), Linux) + SUFFIX=linux +endif +ifeq ($(UNAME), Darwin) + SUFFIX=darwin +endif + +DEST = ../../$(UNAME) +CFLAGS = -g # -DDEBUG + +OBJECTS = zx.o cpmdrv.o cpmglob.o cpmparse.o cpmredir.o \ + drdos.o util.o xlt.o zxbdos.o zxcbdos.o zxdbdos.o z80.o +UNUSED = dirent.o + +all: zx $(DEST) + cp bios.bin zx $(DEST) + +$(DEST): + mkdir -p $(DEST) + +clean: + -rm -f $(OBJECTS) config.h + +clobber: clean + -rm -f $(DEST)/zx $(DEST)/bios.bin zx + +$(OBJECTS): config.h + +zx: $(OBJECTS) + $(CC) -o zx $(OBJECTS) + +config.h: config.h.$(SUFFIX) + cp config.h.$(SUFFIX) config.h diff --git a/Tools/unix/zx/bios.bin b/Tools/unix/zx/bios.bin new file mode 100644 index 00000000..b20e62d9 Binary files /dev/null and b/Tools/unix/zx/bios.bin differ diff --git a/Tools/unix/zx/cbops.h b/Tools/unix/zx/cbops.h new file mode 100644 index 00000000..47b69da0 --- /dev/null +++ b/Tools/unix/zx/cbops.h @@ -0,0 +1,172 @@ +/* Emulations of the CB operations of the Z80 instruction set. + * Copyright (C) 1994 Ian Collier. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define var_t unsigned char t +#define rlc(x) (x=(x<<1)|(x>>7),rflags(x,x&1)) +#define rrc(x) do{var_t=x&1;x=(x>>1)|(t<<7);rflags(x,t);}while(0) +#define rl(x) do{var_t=x>>7;x=(x<<1)|(f&1);rflags(x,t);}while(0) +#define rr(x) do{var_t=x&1;x=(x>>1)|(f<<7);rflags(x,t);}while(0) +#define sla(x) do{var_t=x>>7;x<<=1;rflags(x,t);}while(0) +#define sra(x) do{var_t=x&1;x=((signed char)x)>>1;rflags(x,t);}while(0) +#define sll(x) do{var_t=x>>7;x=(x<<1)|1;rflags(x,t);}while(0) +#define srl(x) do{var_t=x&1;x>>=1;rflags(x,t);}while(0) + +#define rflags(x,c) (f=(c)|(x&0xa8)|((!x)<<6)|parity(x)) + +#define bit(n,x) (f=(f&1)|((x&(1<>3)&7; + switch(op&0xc7){ + case 0x40: bit(n,b); break; + case 0x41: bit(n,c); break; + case 0x42: bit(n,d); break; + case 0x43: bit(n,e); break; + case 0x44: bit(n,h); break; + case 0x45: bit(n,l); break; + case 0x46: tstates+=4;val=fetch(addr);bit(n,val);store(addr,val);break; + case 0x47: bit(n,a); break; + case 0x80: res(n,b); break; + case 0x81: res(n,c); break; + case 0x82: res(n,d); break; + case 0x83: res(n,e); break; + case 0x84: res(n,h); break; + case 0x85: res(n,l); break; + case 0x86: tstates+=4;val=fetch(addr);res(n,val);store(addr,val);break; + case 0x87: res(n,a); break; + case 0xc0: set(n,b); break; + case 0xc1: set(n,c); break; + case 0xc2: set(n,d); break; + case 0xc3: set(n,e); break; + case 0xc4: set(n,h); break; + case 0xc5: set(n,l); break; + case 0xc6: tstates+=4;val=fetch(addr);set(n,val);store(addr,val);break; + case 0xc7: set(n,a); break; + } + } + if(ixoriy)switch(reg){ + case 0:b=val; break; + case 1:c=val; break; + case 2:d=val; break; + case 3:e=val; break; + case 4:h=val; break; + case 5:l=val; break; + case 7:a=val; break; + } +} + +#undef var_t +#undef rlc +#undef rrc +#undef rl +#undef rr +#undef sla +#undef sra +#undef sll +#undef srl +#undef rflags +#undef bit +#undef set +#undef res diff --git a/Tools/unix/zx/config.h.darwin b/Tools/unix/zx/config.h.darwin new file mode 100644 index 00000000..cc4b425c --- /dev/null +++ b/Tools/unix/zx/config.h.darwin @@ -0,0 +1,16 @@ +//#define HAVE_WINDOWS_H +#define HAVE_DIRENT_H +#define HAVE_UTIME_H +#define HAVE_FCNTL_H +#define HAVE_UNISTD_H +#define BINDIR80 getenv("ZXBINDIR") +#define LIBDIR80 getenv("ZXLIBDIR") +#define INCDIR80 getenv("ZXINCDIR") +#define DARWIN +#include +#include +#define _S_IFDIR S_IFDIR +#define strcmpi(a,b) strcasecmp(a,b) +//#define WIN32 +//#define WINVER 0x0501 // target Windows XP +//#define _WIN32_WINNNT 0x0501 // target Windows XP diff --git a/Tools/unix/zx/config.h.linux b/Tools/unix/zx/config.h.linux new file mode 100644 index 00000000..a015591c --- /dev/null +++ b/Tools/unix/zx/config.h.linux @@ -0,0 +1,17 @@ +//#define HAVE_WINDOWS_H +#define HAVE_DIRENT_H +#define HAVE_UTIME_H +#define HAVE_FCNTL_H +#define HAVE_SYS_VFS_H +#define HAVE_UNISTD_H +#define BINDIR80 getenv("ZXBINDIR") +#define LIBDIR80 getenv("ZXLIBDIR") +#define INCDIR80 getenv("ZXINCDIR") +#define LINUX +#include +#include +#define _S_IFDIR S_IFDIR +#define strcmpi(a,b) strcasecmp(a,b) +//#define WIN32 +//#define WINVER 0x0501 // target Windows XP +//#define _WIN32_WINNNT 0x0501 // target Windows XP diff --git a/Tools/unix/zx/config.h.windows b/Tools/unix/zx/config.h.windows new file mode 100644 index 00000000..8186c485 --- /dev/null +++ b/Tools/unix/zx/config.h.windows @@ -0,0 +1,9 @@ +#define HAVE_WINDOWS_H +//#define HAVE_DIRENT_H +#define HAVE_FCNTL_H +#define BINDIR80 getenv("ZXBINDIR") +#define LIBDIR80 getenv("ZXLIBDIR") +#define INCDIR80 getenv("ZXINCDIR") +#define WIN32 +#define WINVER 0x0501 // target Windows XP +#define _WIN32_WINNNT 0x0501 // target Windows XP diff --git a/Tools/unix/zx/cpm/bios.bin b/Tools/unix/zx/cpm/bios.bin new file mode 100644 index 00000000..b20e62d9 Binary files /dev/null and b/Tools/unix/zx/cpm/bios.bin differ diff --git a/Tools/unix/zx/cpm/bios.com b/Tools/unix/zx/cpm/bios.com new file mode 100644 index 00000000..b20e62d9 Binary files /dev/null and b/Tools/unix/zx/cpm/bios.com differ diff --git a/Tools/unix/zx/cpm/bios.lst b/Tools/unix/zx/cpm/bios.lst new file mode 100644 index 00000000..d3e97405 --- /dev/null +++ b/Tools/unix/zx/cpm/bios.lst @@ -0,0 +1,106 @@ +Z80ASM SuperFast Relocating Macro Assembler Z80ASM 1.30 Page 1 +BIOS Z80 + + 1 ; BIOS / BDOS for the ZXCC environment. + 2 ; + 3 FE00 org 0FE00h + 4 FE00 5A 58 43 43 DEFB 'ZXCC04' ;Serial number + 5 ; + 6 ; Some CP/M programs expect a jump at the start of BDOS, so here it is. + 7 ; + 8 FE06 C3 FE09 BDOS0: JP BDOS1 + 9 + 10 FE09 3E C0 BDOS1: LD A,0C0h + 11 FE0B ED FE DEFB 0EDh,0FEh + 12 FE0D C9 RET + 13 ; + 14 ; Hack for ZMAC. ZMAC is using contents of 0FE22H to establish a memory pointer. + 15 ; It makes no sense. We stuff 04F4CH here because it is known to work... + 16 ; + 17 FE22 org 0FE22H + 18 FE22 4F4C DEFW 04F4CH + 19 ; + 20 ;This is not a real BIOS, so let its code live below the BIOS jumpblock. + 21 ; + 22 FE24 DD 22 FE33 UBIOS: LD (XIX),IX + 23 FE28 DD E1 POP IX ;IX = address of UBIOS function + 24 FE2A 3E C3 LD A,0C3h ;C3h = BIOS call + 25 FE2C ED FE DEFB 0EDh,0FEh + 26 FE2E DD 2A FE33 LD IX,(XIX) + 27 FE32 C9 RET + 28 ; + 29 FE33 0000 XIX: DEFW 0 + 30 + 31 FE35 21 FF03 CBOOT: LD HL,WBOOT0 + 32 FE38 22 0001 LD (1),HL + 33 FE3B 21 FE06 LD HL,BDOS0 + 34 FE3E 22 0006 LD (6),HL + 35 FE41 3E C3 LD A,0C3h + 36 FE43 32 0000 LD (0),A + 37 FE46 32 0005 LD (5),A + 38 FE49 3E C9 LD A,0C9h + 39 FE4B 32 0038 LD (038h),A + 40 FE4E 3E C1 LD A,0C1h ;C1h = program load + 41 FE50 ED FE DEFB 0EDh,0FEh + 42 FE52 21 0000 LD HL,0 + 43 FE55 E5 PUSH HL ;In case called program tries to RET + 44 FE56 C3 0100 JP 0100h + 45 ; + 46 FE59 3E C3 WBOOT: LD A,0C3h ;Program termination + 47 FE5B DD 21 0006 LD IX,6 ;BIOS call 1 + 48 FE5F ED FE DEFB 0EDh,0FEh + 49 FE61 76 HALT + 50 FE62 C3 FE62 JP $ + 51 ; + 52 FEEC org 0FEECh + 53 FEEC FF tmpdrv: defb 0FFh ;Temp drive = current + 54 + 55 + 56 ; + 57 ;TODO: SCB at FE9Ch + 58 ; + Z80ASM SuperFast Relocating Macro Assembler Z80ASM 1.30 Page 2 +BIOS Z80 + + 59 FF00 org 0FF00h + 60 FF00 C3 FE35 JP CBOOT ;FF00 + 61 FF03 C3 FE59 WBOOT0: JP WBOOT ;03 + 62 FF06 CD FE24 CALL UBIOS ;06 + 63 FF09 CD FE24 CALL UBIOS ;09 + 64 FF0C CD FE24 CALL UBIOS ;0C + 65 FF0F CD FE24 CALL UBIOS ;0F + 66 FF12 CD FE24 CALL UBIOS ;12 + 67 FF15 CD FE24 CALL UBIOS ;15 + 68 FF18 CD FE24 CALL UBIOS ;18 + 69 FF1B CD FE24 CALL UBIOS ;1B + 70 FF1E CD FE24 CALL UBIOS ;1E + 71 FF21 CD FE24 CALL UBIOS ;21 + 72 FF24 CD FE24 CALL UBIOS ;24 + 73 FF27 CD FE24 CALL UBIOS ;27 + 74 FF2A CD FE24 CALL UBIOS ;2A + 75 FF2D CD FE24 CALL UBIOS ;2D + 76 FF30 CD FE24 CALL UBIOS ;30 + 77 FF33 CD FE24 CALL UBIOS ;33 + 78 FF36 CD FE24 CALL UBIOS ;36 + 79 FF39 CD FE24 CALL UBIOS ;39 + 80 FF3C CD FE24 CALL UBIOS ;42 + 81 FF3F CD FE24 CALL UBIOS ;45 + 82 FF42 CD FE24 CALL UBIOS ;48 + 83 FF45 CD FE24 CALL UBIOS ;4B + 84 FF48 CD FE24 CALL UBIOS ;4E + 85 FF4B CD FE24 CALL UBIOS ;51 + 86 FF4E CD FE24 CALL UBIOS ;54 + 87 FF51 CD FE24 CALL UBIOS ;57 + 88 FF54 CD FE24 CALL UBIOS ;5A, USERF + 89 FF57 CD FE24 CALL UBIOS ;5D, RESERV1 + 90 FF5A CD FE24 CALL UBIOS ;60, RESERV2 + 91 ; + 92 FFC0 org 0FFC0h ;Space for DPB + 93 FFC0 0020 dpb: defs 20h + 94 + 95 END + 0 Error(s) Detected. + 480 Absolute Bytes. 9 Symbols Detected. +  UBIOS ;27 + 74 FF2A CD FE24 CALL UBIOS ;2A + 75 FF2D CD FE24 \ No newline at end of file diff --git a/Tools/unix/zx/cpm/bios.z80 b/Tools/unix/zx/cpm/bios.z80 new file mode 100644 index 00000000..fb64e891 --- /dev/null +++ b/Tools/unix/zx/cpm/bios.z80 @@ -0,0 +1,96 @@ +; BIOS / BDOS for the ZXCC environment. +; + org 0FE00h + DEFB 'ZXCC04' ;Serial number +; +; Some CP/M programs expect a jump at the start of BDOS, so here it is. +; +BDOS0: JP BDOS1 + +BDOS1: LD A,0C0h + DEFB 0EDh,0FEh + RET +; +; Hack for ZMAC. ZMAC is using contents of 0FE22H to establish a memory pointer. +; It makes no sense. We stuff 04F4CH here because it is known to work... +; + org 0FE22H + DEFW 04F4CH +; +;This is not a real BIOS, so let its code live below the BIOS jumpblock. +; +UBIOS: LD (XIX),IX + POP IX ;IX = address of UBIOS function + LD A,0C3h ;C3h = BIOS call + DEFB 0EDh,0FEh + LD IX,(XIX) + RET +; +XIX: DEFW 0 + +CBOOT: LD HL,WBOOT0 + LD (1),HL + LD HL,BDOS0 + LD (6),HL + LD A,0C3h + LD (0),A + LD (5),A + LD A,0C9h + LD (038h),A + LD A,0C1h ;C1h = program load + DEFB 0EDh,0FEh + LD HL,0 + PUSH HL ;In case called program tries to RET + JP 0100h +; +WBOOT: LD A,0C3h ;Program termination + LD IX,6 ;BIOS call 1 + DEFB 0EDh,0FEh + HALT + JP $ +; + org 0FEECh +tmpdrv: defb 0FFh ;Temp drive = current + + +; +;TODO: SCB at FE9Ch +; + org 0FF00h + JP CBOOT ;FF00 +WBOOT0: JP WBOOT ;03 + CALL UBIOS ;06 + CALL UBIOS ;09 + CALL UBIOS ;0C + CALL UBIOS ;0F + CALL UBIOS ;12 + CALL UBIOS ;15 + CALL UBIOS ;18 + CALL UBIOS ;1B + CALL UBIOS ;1E + CALL UBIOS ;21 + CALL UBIOS ;24 + CALL UBIOS ;27 + CALL UBIOS ;2A + CALL UBIOS ;2D + CALL UBIOS ;30 + CALL UBIOS ;33 + CALL UBIOS ;36 + CALL UBIOS ;39 + CALL UBIOS ;42 + CALL UBIOS ;45 + CALL UBIOS ;48 + CALL UBIOS ;4B + CALL UBIOS ;4E + CALL UBIOS ;51 + CALL UBIOS ;54 + CALL UBIOS ;57 + CALL UBIOS ;5A, USERF + CALL UBIOS ;5D, RESERV1 + CALL UBIOS ;60, RESERV2 +; + org 0FFC0h ;Space for DPB +dpb: defs 20h + + END + diff --git a/Tools/unix/zx/cpmdrv.c b/Tools/unix/zx/cpmdrv.c new file mode 100644 index 00000000..1faff21b --- /dev/null +++ b/Tools/unix/zx/cpmdrv.c @@ -0,0 +1,275 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998,2003 John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file deals with drive-based functions. +*/ + +#include "cpmint.h" + +#ifdef WIN32 + +static char *drive_to_hostdrive(int cpm_drive) +{ + static char prefix[CPM_MAXPATH]; + char *lpfp; + DWORD dw; + + if (!redir_drive_prefix[cpm_drive]) return NULL; + dw = GetFullPathName(redir_drive_prefix[cpm_drive], sizeof(prefix), + prefix, &lpfp); + + if (!dw) return NULL; + if (prefix[1] == ':') /* If path starts with a drive, limit it */ + { /* to just that drive */ + prefix[2] = '/'; + prefix[3] = 0; + } + return prefix; +} +#endif + + +cpm_byte fcb_reset(void) +{ +#ifdef __MSDOS__ + bdos(0x0D, 0, 0); +#endif + + redir_l_drives = 0; + redir_cpmdrive = 0; /* A reset forces current drive to A: */ +/* redir_ro_drives = 0; Software write protect not revoked by func 0Dh. + * + * This does not follow true CP/M, but does match many 3rd-party replacements. + */ + return 0; +} + + +cpm_word fcb_drive (cpm_byte drv) +{ + if (redir_drive_prefix[drv][0]) + { + redir_cpmdrive = drv; + redir_log_drv(drv); + return 0; + } + else return 0x04FF; /* Drive doesn't exist */ +} + +cpm_byte fcb_getdrv(void) +{ + return redir_cpmdrive; +} + + +cpm_byte fcb_user (cpm_byte usr) +{ + if (usr != 0xFF) redir_cpmuser = usr % 16; + + redir_Msg("User: parameter %d returns %d\r\n", usr, redir_cpmuser); + + return redir_cpmuser; +} + + + +cpm_word fcb_logvec(void) +{ + return redir_l_drives; +} + + +cpm_word fcb_rovec(void) +{ + return redir_ro_drives; +} + + +cpm_word fcb_rodisk(void) +{ + cpm_word mask = 1; + + if (redir_cpmdrive) mask = mask << redir_cpmdrive; + + redir_ro_drives |= mask; + return 0; +} + + +cpm_word fcb_resro(cpm_word bitmap) +{ + redir_ro_drives &= ~bitmap; + + return 0; +} + + +cpm_word fcb_sync(cpm_byte flag) +{ +#ifdef WIN32 + return 0; +#else + sync(); return 0; /* Apparently some sync()s are void not int */ +#endif +} + + +cpm_word fcb_purge() +{ +#ifdef WIN32 + return 0; +#else + sync(); return 0; /* Apparently some sync()s are void not int */ +#endif +} + + +static cpm_byte exdpb[0x11] = { + 0x80, 0, /* 128 records/track */ + 0x04, 0x0F, /* 2k blocks */ + 0x00, /* 16k / extent */ + 0xFF, 0x0F, /* 4095 blocks */ + 0xFF, 0x03, /* 1024 dir entries */ + 0xFF, 0xFF, /* 16 directory blocks */ + 0x00, 0x80, /* Non-removable media */ + 0x00, 0x00, /* No system tracks */ + 0x02, 0x03 /* 512-byte sectors */ +}; + +cpm_word fcb_getdpb(cpm_byte *dpb) +{ +#ifdef WIN32 + DWORD spc, bps, fc, tc; + unsigned bsh, blm, psh, phm; + char *hostd = drive_to_hostdrive(redir_cpmdrive); + + if (!hostd) return 0x01FF; /* Can't select */ + + if (!GetDiskFreeSpace(hostd, &spc, &bps, &fc, &tc)) + return 0x01FF; /* Can't select */ + + /* Store total clusters */ + //if (tc > 0x10000L) tc = 0x10000L; + if (tc > 0xFFFFL) tc = 0xFFFFL; + + psh = 0; phm = 0; + + while (bps > 128) /* Get sector size */ + { + bps /= 2; + psh++; + phm = (phm << 1) | 1; + } + bsh = psh; blm = phm; + while (spc > 1) /* Get cluster size */ + { + spc /= 2; + bsh++; + blm = (blm << 1) | 1; + } + + + exdpb[2] = bsh; + exdpb[3] = blm; + exdpb[5] = tc & 0xFF; + exdpb[6] = tc >> 8; + + exdpb[15] = psh; + exdpb[16] = phm; +#else + struct statfs buf; + cpm_word nfiles; + + /* Get DPB for redir_cpmdrive. Currently just returns a dummy. */ + if (!statfs(redir_drive_prefix[redir_cpmdrive], &buf)) + { + /* Store correct directory entry count */ + + if (buf.f_files >= 0x10000L) nfiles = 0xFFFF; + else nfiles = buf.f_files; + + exdpb[7] = nfiles & 0xFF; + exdpb[8] = nfiles >> 8; + } +#endif + + memcpy(dpb, &exdpb, 0x11); + return 0x11; +} + + +/* Create an entirely bogus ALV + * TODO: Make it a bit better */ + +cpm_word fcb_getalv(cpm_byte *alv, cpm_word max) +{ + if (max > 1024) max = 1024; + + memset(alv, 0xFF, max / 2); + memset(alv + (max / 2), 0, max / 2); + + return max; +} + +/* Get disk free space */ + +cpm_word fcb_dfree (cpm_byte drive, cpm_byte *dma) +{ +#ifdef WIN32 + DWORD spc, bps, fc, tc; + DWORD freerec; + char *hostd = drive_to_hostdrive(drive); + + if (!hostd) return 0x01FF; + if (!hostd) return 0x01FF; /* Can't select */ + + if (!GetDiskFreeSpace(hostd, &spc, &bps, &fc, &tc)) + return 0x01FF; /* Can't select */ + + freerec = fc; /* Free clusters */ + freerec *= spc; /* Free sectors */ + freerec *= (bps / 128); /* Free CP/M records */ + + /* Limit to maximum CP/M drive size */ + if (freerec > 4194303L) freerec = 4194303L; + redir_wr24(dma, freerec); + +#else + struct statfs buf; + long dfree; + + if (!redir_drive_prefix[drive]) return 0x01FF; /* Can't select */ + + if (statfs(redir_drive_prefix[drive], &buf)) return 0x01FF; + + dfree = (buf.f_bavail * (buf.f_bsize / 128)); + + if (dfree < buf.f_bavail || /* Calculation has wrapped round */ + dfree > 4194303L) /* Bigger than max CP/M drive size */ + { + dfree = 4194303L; + } + + redir_wr24(dma, dfree); +#endif + return 0; +} + + + diff --git a/Tools/unix/zx/cpmglob.c b/Tools/unix/zx/cpmglob.c new file mode 100644 index 00000000..20306e09 --- /dev/null +++ b/Tools/unix/zx/cpmglob.c @@ -0,0 +1,579 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file implements those BDOS functions that use wildcard expansion. +*/ + +#include "cpmint.h" + +static cpm_byte *find_fcb; +static int find_n; +static int find_ext = 0; +static int find_xfcb = 0; +static int entryno; +static cpm_byte lastdma[0x80]; +static long lastsize; +static char target_name[CPM_MAXPATH]; + +static char upper(char c) +{ + if (islower(c)) return toupper(c); + return c; +} + +/* + * we need to handle case sensitive filesystems correctly. + * underlying files need to just work, irrespective if the files + * in the native filesystem are mixed, upper or lower. + * the naive code in the distributed zx will not work everywhere. + */ + +/* Does the string "s" match the CP/M FCB? */ +/* pattern[0-10] will become a CP/M name parsed from "s" if it matches. */ +/* If 1st byte of FCB is '?' then anything matches. */ + +static int cpm_match(char *s, cpm_byte *fcb, cpm_byte *pattern) +{ + int n, m; + char *dotpos; + + m = strlen(s); + + /* + * let's cook the filename into something that we can match against + * the fcb. we reject any that can't be valid cp/m filenames, + * normalizing case as we go. all this goes into 'pattern' + */ + for (n = 0; n < 11; n++) pattern[n] = ' '; + + /* The name must have 1 or 0 dots */ + dotpos = strchr(s, '.'); + if (!dotpos) { /* No dot. The name must be at most 8 characters */ + if (m > 8) return 0; + for (n = 0; n < m; n++) { + pattern[n] = upper(s[n]) & 0x7F; + } + } else { /* at least one dot */ + if (strchr(dotpos + 1, '.')) { /* More than 1 dot */ + return 0; + } + if (dotpos == s) { /* Dot right at the beginning */ + return 0; + } + if ((dotpos - s) > 8) { /* "name" > 8 characters */ + return 0; + } + if (strlen(dotpos + 1) > 3) { /* "type" > 3 characters */ + return 0; + } + for (n = 0; n < (dotpos - s); n++) { /* copy filename portion */ + pattern[n] = upper(s[n]) & 0x7F; + } + m = strlen(dotpos + 1); + for (n = 0; n < m; n++) { /* copy extention portion */ + pattern[n + 8] = upper(dotpos[n + 1]) & 0x7F; + } + } + + /* + * handle special case where fcb[0] == '?' or fcb[0] & 0x80 + * this is used to return a full directory list on bdos's + */ + if (((fcb[0] & 0x7F) == '?') || (fcb[0] & 0x80)) { + return 1; + } + + for (n = 0; n < 11; n++) { + if (fcb[n+1] == '?') continue; + if ((pattern[n] & 0x7F) != (fcb[n+1] & 0x7F)) { + return 0; + } + } + return 1; /* Success! */ +} + + +/* Get the next entry from the host's directory matching "fcb" */ + +static struct dirent * next_entry(DIR *dir, cpm_byte *fcb, cpm_byte *pattern, + struct stat *st) +{ + struct dirent *en; + int unsatisfied; + int drv = fcb[0] & 0x7F; + + if (drv == '?') drv = 0; + if (!drv) drv = redir_cpmdrive; + else drv--; + + for (unsatisfied = 1; unsatisfied; ) + { + /* 1. Get the next entry */ + + en = readdir(dir); + if (!en) return NULL; /* No next entry */ + ++entryno; /* 0 for 1st, 1 for 2nd, etc. */ + + /* 2. See if it matches. We do this first (in preference to + seeing if it's a subdirectory first) because it doesn't + require disc access */ + if (!cpm_match(en->d_name, fcb, pattern)) + { + continue; + } + /* 3. Stat it, & reject it if it's a directory */ + + strcpy(target_name, redir_drive_prefix[drv]); + strcat(target_name, en->d_name); + + if (stat(target_name, st)) + { + redir_Msg("Can't stat %s so omitting it.\n", target_name); + continue; /* Can't stat */ + } + //if (S_ISDIR(st->st_mode)) + if ((st->st_mode) & _S_IFDIR) + { + /* Searching for files only */ + if (fcb[0] != '?' && fcb[0] < 0x80) + { + continue; + } + } + unsatisfied = 0; + } + return en; +} + + + +void volume_label(int drv, cpm_byte *dma) +{ + struct stat st; + + memset(dma, 0x20, 12); /* Volume label */ + + /* Get label name */ + redir_get_label(drv, (char *)(dma + 1)); + /* [0x0c] = label byte + * [0x0d] = password byte (=0) + * [0x10-0x17] = password + * [0x18] = label create date + * [0x1c] = label update date + */ +#ifdef __MSDOS__ + dma[0x0c] = 0x21; /* Under DOS, only "update" */ + if (redir_drdos) dma[0x0c] |= 0x80; /* Under DRDOS, passwords allowed */ +#else + dma[0x0c] = 0x61; /* Label exists and time stamps allowed */ +#endif /* (update & access) */ + dma[0x0d] = 0; /* Label not passworded */ + dma[0x0f] = 0x80; /* Non-CP/M media */ + + if (stat(redir_drive_prefix[drv], &st)) + { + redir_Msg("stat() fails on '%s'\n", redir_drive_prefix[drv]); + return; + } + + redir_wr32(dma + 0x18, redir_cpmtime(st.st_atime)); + redir_wr32(dma + 0x1C, redir_cpmtime(st.st_mtime)); +} + + + +cpm_word redir_find(int n, cpm_byte *fcb, cpm_byte *dma) +{ + DIR *hostdir; + int drv, attrib; + long recs; + struct stat st; + struct dirent *de; + cpm_word rights; + + drv = (fcb[0] & 0x7F); + if (!drv || drv == '?') drv = redir_cpmdrive; + else drv--; + + if (find_xfcb) /* Return another extent */ + { + memcpy(dma, lastdma, 0x80); + dma[0] |= 0x10; /* XFCB */ + dma[0x0c] = dma[0x69]; /* Password mode */ + dma[0x0d] = 0x0A; /* Password decode byte */ + memset(dma + 0x10, '*', 7); + dma[0x17] = ' '; /* Encoded password */ + memcpy(lastdma, dma, 0x80); + + find_xfcb = 0; + return 0; + } + + if (find_ext) /* Return another extent */ + { + memcpy(dma, lastdma, 0x80); + dma[0x0c]++; + if (dma[0x0c] == 0x20) + { + dma[0x0c] = 0; + dma[0x0e]++; + } + lastsize -= 0x4000; + recs = (lastsize + 127) / 128; + dma[0x0f] = (recs > 127) ? 0x80 : (recs & 0x7F); + + if (lastsize <= 0x4000) find_ext = 0; + memcpy(lastdma, dma, 0x80); + + return 0; + } + + + memset(dma, 0, 128); /* Zap the buffer */ + + /* + If returning all entries, return a volume label. + */ + if ((fcb[0] & 0x7F) == '?') + { + if (!n) + { + volume_label(drv, dma); + return 0; + } + else --n; + } + + /* Note: This implies that opendir() works on a filename with a + trailing slash. It does under Linux, but that's the only assurance + I can give. + */ + + entryno = -1; + hostdir = opendir(redir_drive_prefix[drv]); + + if (!hostdir) + { + redir_Msg("opendir() fails on '%s'\n", redir_drive_prefix[drv]); + return 0xFF; + } + /* We have a handle to the directory. */ + while (n >= 0) + { + de = next_entry(hostdir, fcb, dma + 1, &st); + if (!de) return 0xFF; + --n; + } + /* Valid entry found & statted. dma+1 holds filename. */ + + dma[0] = redir_cpmuser; /* Uid always matches */ + dma[0x0c] = 0; /* Extent counter, low */ + dma[0x0d] = st.st_size & 0x7F; /* Last record byte count */ + dma[0x0e] = 0; /* Extent counter, high */ + +#ifdef __MSDOS__ + _dos_getfileattr(target_name, (unsigned int *)&attrib); + rights = redir_drdos_get_rights(target_name); + if (rights && ((fcb[0] & 0x7F) == '?')) find_xfcb = 1; +#else + attrib = 0; + rights = 0; +#endif + if (attrib & 1) dma[9] |= 0x80; + if (attrib & 4) dma[10] |= 0x80; + if (!(attrib & 0x20)) dma[11] |= 0x80; + + + +/* TODO: Under Unix, work out correct RO setting */ + + recs = (st.st_size + 127) / 128; + dma[0x0f] = (recs > 127) ? 0x80 : (recs & 0x7F); + dma[0x10] = 0x80; + //if (S_ISDIR(st.st_mode)) dma[0x10] |= 0x40; + if ((st.st_mode) & _S_IFDIR) {dma[0x10] |= 0x40;} + if (attrib & 2) dma[0x10] |= 0x20; + dma[0x10] |= ((entryno & 0x1FFF) >> 8); + dma[0x11] = dma[0x10]; + dma[0x12] = entryno & 0xFF; + + redir_wr32(dma + 0x16, st.st_mtime); /* Modification time. */ + /* TODO: It should be in DOS */ + /* format */ + /* TODO: At 0x1A, 1st cluster */ + redir_wr32(dma + 0x1C, st.st_size); /* True size */ + + if (rights) /* Store password mode. Don't return an XFCB. */ + { + dma[0x69] = redir_cpm_pwmode(rights); + memcpy(lastdma, dma, 0x80); + } + + dma[0x60] = 0x21; /* XFCB */ + redir_wr32(dma + 0x61, redir_cpmtime(st.st_atime)); + redir_wr32(dma + 0x65, redir_cpmtime(st.st_mtime)); + + closedir(hostdir); + + if (st.st_size > 0x4000 && (fcb[0x0C] == '?')) /* All extents? */ + { + lastsize = st.st_size; + find_ext = 1; + memcpy(lastdma, dma, 0x80); + } + return 0; +} + + +#ifdef DEBUG +#define SHOWNAME(func) \ + { \ + char fname[CPM_MAXPATH]; \ + redir_fcb2unix(fcb, fname); \ + redir_Msg(func "(\"%s\")\n", fname); \ + } + +#else + #define SHOWNAME(func) +#endif + +cpm_word fcb_find1 (cpm_byte *fcb, cpm_byte *dma) /* 0x11 */ +{ +#ifdef DEBUG + int rv; +#endif + SHOWNAME("fcb_find1") + + redir_log_fcb(fcb); + + find_n = 0; + find_fcb = fcb; + find_ext = 0; + find_xfcb = 0; +#ifdef DEBUG + rv = redir_find(find_n, fcb, dma); + + if (rv < 4) + { + redir_Msg("Ret: %-11.11s\n", dma + 1); + } + else redir_Msg("Ret: Fail\n"); + return rv; +#else + return redir_find(find_n, find_fcb, dma); +#endif +} + +/* We don't bother with the FCB parameter - it's undocmented, and + * programs that do know about it will just pass in the same parameter + * that they did to function 0x11 */ + +cpm_word fcb_find2 (cpm_byte *fcb, cpm_byte *dma) /* 0x12 */ +{ +#ifdef DEBUG + int rv; + + char fname[CPM_MAXPATH]; + redir_fcb2unix(find_fcb, fname); + redir_Msg("fcb_find2(\"%s\") no. %d\n", fname, find_n); +#endif + ++find_n; +#ifdef DEBUG + rv = redir_find(find_n, find_fcb, dma); + + if (rv < 4) + { + redir_Msg("Ret: %-11.11s\n", dma + 1); + } + else redir_Msg("Ret: Fail\n"); + return rv; +#else + return redir_find(find_n, find_fcb, dma); +#endif +} + +/* Under CP/M, unlinking works with wildcards */ + +cpm_word fcb_unlink(cpm_byte *fcb, cpm_byte *dma) +{ + DIR *hostdir; + int drv; + struct dirent *de; + struct stat st; + int handle = 0; + int unpasswd = 0; + char fname[CPM_MAXPATH]; + + SHOWNAME("fcb_unlink") + + if (fcb[5] & 0x80) unpasswd = 1; /* Remove password rather than file */ + + redir_log_fcb(fcb); + + drv = (fcb[0] & 0x7F); + if (!drv || drv == '?') drv = redir_cpmdrive; + else drv--; + + if (redir_ro_drv(drv)) return 0x02FF; /* Error: R/O drive */ + +#ifdef DEBUG + redir_fcb2unix(fcb, fname); + redir_Msg("fcb_unlink(\"%s\")\n", fname); +#endif + + /* Note: This implies that opendir() works on a filename with a + trailing slash. It does under Linux, but that's the only assurance + I can give. + */ + + hostdir = opendir(redir_drive_prefix[drv]); + + if (!hostdir) + { + redir_Msg("opendir() fails on '%s'\n", redir_drive_prefix[drv]); + return 0xFF; + } + /* We have a handle to the directory. */ + do + { + de = next_entry(hostdir, fcb, (cpm_byte *)fname, &st); + if (de) + { + strcpy(target_name, redir_drive_prefix[drv]); + strcat(target_name, de->d_name); + redir_Msg("Deleting %s\n", de->d_name); + if (unpasswd) + { +#ifdef __MSDOS__ + if (redir_drdos) + { + handle = redir_drdos_put_rights (target_name, dma, 0); + } + else handle = 0; +#endif + } + else if (fcb[0] & 0x80) + { + handle = rmdir(target_name); + if (handle && redir_password_error()) + { + redir_password_append(target_name, dma); + handle = rmdir(target_name); + } + } + else + { + handle = unlink(target_name); + if (handle && redir_password_error()) + { + redir_password_append(target_name, dma); + handle = unlink(target_name); + } + } + + if (handle) de = NULL; /* Delete failed */ + } + } + while (de != NULL); + if (handle) + { + redir_Msg("Ret: -1\n"); + closedir(hostdir); + return 0xFF; + } + redir_Msg("Ret: 0\n"); + closedir(hostdir); + return 0; +} + + + + + +#ifdef __MSDOS__ +cpm_word redir_get_label(cpm_byte drv, char *pattern) +{ + char strs[10]; + struct ffblk fblk; + int done; + char *s; + int n; + + /* We need the drive prefix to be of the form "C:\etc..." */ + + memset(pattern, ' ', 11); + if (!redir_drive_prefix[drv][0] || redir_drive_prefix[drv][1] != ':') + return 0; + + sprintf(strs,"%c:/*.*", redir_drive_prefix[drv][0]); + + done = findfirst(strs, &fblk, FA_LABEL); + while (!done) + { + if ((fblk.ff_attrib & FA_LABEL) && + !(fblk.ff_attrib & (FA_SYSTEM | FA_HIDDEN))) + { + s = strchr(fblk.ff_name, '/'); + if (!s) s = strchr(fblk.ff_name, '\\'); + if (!s) s = strchr(fblk.ff_name, ':'); + if (!s) s = fblk.ff_name; + for (n = 0; n < 11; n++) + { + if (!(*s)) break; + if (*s == '.') + { + n = 7; + ++s; + continue; + } + pattern[n] = upper(*s); + ++s; + } + return 1; + } + done = findnext(&fblk); + } + return 0; +} +#else +cpm_word redir_get_label(cpm_byte drv, char *pattern) +{ + char *dname; + int l, n; + + memset(pattern, ' ', 11); + + dname = strrchr(redir_drive_prefix[drv], '/'); + if (dname) + { + ++dname; + l = strlen(dname); + if (l > 11) l = 11; + for (n = 0; n < l; n++) pattern[n] = upper(dname[l]); + } + else + { + pattern[0] = '.'; + } + return 0; +} + + + +#endif diff --git a/Tools/unix/zx/cpmint.h b/Tools/unix/zx/cpmint.h new file mode 100644 index 00000000..f0ec4237 --- /dev/null +++ b/Tools/unix/zx/cpmint.h @@ -0,0 +1,223 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file holds internal declarations for the library. +*/ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_DIRENT_H +# include +#else +#ifdef __WATCOMC__ +# include +# include +#else +# include "dirent.h" +#endif +#endif +#ifdef HAVE_NDIR_H +# include +#endif +#ifdef HAVE_SYS_DIR_H +# include +#endif +#ifdef HAVE_SYS_NDIR_H +# include +#endif +#ifdef HAVE_WINDOWS_H +# include +#endif +#ifdef HAVE_WINNT_H +# include +#endif +#ifdef HAVE_SYS_VFS_H +# include +#endif +#ifdef HAVE_UTIME_H +# include +#endif +#ifdef HAVE_FCNTL_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif + + +#ifdef __MSDOS__ + #include + #include + #include + #ifdef __GO32__ + #include + #include + #include + #endif +#endif + +#define CASE_SENSITIVE_FILESYSTEM 0 + + +#include "cpmredir.h" + +typedef unsigned long dword; /* Must be at least 32 bits, and + >= sizeof(int) */ +#ifdef CPMDEF + #define EXT + #define INIT(x) =x +#else + #define EXT extern + #define INIT(x) +#endif + +/* The 16 directories to which the 16 CP/M drives are mapped */ + +EXT char redir_drive_prefix[16][CPM_MAXPATH]; + +/* Current drive and user */ + +EXT int redir_cpmdrive; +EXT int redir_cpmuser; + +/* Length of 1 read/write operation, bytes */ + +EXT int redir_rec_len INIT(128); + +/* Same, but in 128-byte records */ +EXT int redir_rec_multi INIT(1); + +/* Using a DRDOS system? */ +EXT int redir_drdos INIT(0); + +/* Default password */ +#ifdef __MSDOS__ +EXT char redir_passwd[8] INIT(""); +#endif + +EXT cpm_word redir_l_drives INIT(0); +EXT cpm_word redir_ro_drives INIT(0); + +#undef EXT +#undef INIT + + + +/* Convert FCB to a Unix filename, returning 1 if it's ambiguous */ +int redir_fcb2unix(cpm_byte *fcb, char *fname); + +/* Open FCB, set file attributes */ +int redir_ofile(cpm_byte * fcb, char *s); + +/* Check that the FCB we have is valid */ +int redir_verify_fcb(cpm_byte *fcb); + +#ifndef O_BINARY /* Necessary in DOS, not present in Linux */ +#define O_BINARY 0 +#endif + +/* Facilities for debug tracing */ + + +long zxlseek(int fd, long offset, int wh); + +#ifdef DEBUG + void redir_Msg(char *s, ...); + void redir_showfcb(cpm_byte *fcb); +#else + /* Warning: This is a GCC extension */ + #define redir_Msg(x, ...) + #define redir_showfcb(x) +#endif + + + +/* Get the "sequential access" file pointer out of an FCB */ + +long redir_get_fcb_pos(cpm_byte *fcb); + +/* Write "sequential access" pointer to FCB */ + +void redir_put_fcb_pos(cpm_byte *fcb, long npos); + +/* Convert time_t to CP/M day count/hours/minutes */ +dword redir_cpmtime(time_t t); +/* And back */ +time_t redir_unixtime(cpm_byte *c); + + +/* Functions to access 24-bit & 32-bit words in memory. These are always + little-endian. */ + +void redir_wr24(cpm_byte *addr, dword v); +void redir_wr32(cpm_byte *addr, dword v); +dword redir_rd24(cpm_byte *addr); +dword redir_rd32(cpm_byte *addr); + +/* If you have 64-bit file handles, you'll need to write separate wrhandle() + and rdhandle() routines */ +#define redir_wrhandle redir_wr32 +#define redir_rdhandle redir_rd32 + +/* Mark a drive as logged in */ + +void redir_log_drv(cpm_byte drv); +void redir_log_fcb(cpm_byte *fcb); + +/* Check if a drive is software read-only */ + +int redir_ro_drv(cpm_byte drv); +int redir_ro_fcb(cpm_byte *fcb); + +/* Translate errno to a CP/M error */ + +cpm_word redir_xlt_err(void); + +/* Get disc label */ +cpm_word redir_get_label(cpm_byte drv, char *pattern); + + +/* DRDOS set/get access rights - no-ops under MSDOS and Unix: + * + * CP/M password mode -> DRDOS password mode */ +cpm_word redir_drdos_pwmode(cpm_byte b); + +/* DRDOS password mode to CP/M password mode */ +cpm_byte redir_cpm_pwmode(cpm_word w); + +/* Get DRDOS access rights for a file */ +cpm_word redir_drdos_get_rights(char *path); + +/* Set DRDOS access rights and/or password */ +cpm_word redir_drdos_put_rights(char *path, cpm_byte *dma, cpm_word rights); + +/* Was the last error caused by invalid password? */ +cpm_word redir_password_error(void); + +/* Append password to filename (FILE.TYP -> FILE.TYP;PASSWORD) */ +void redir_password_append(char *s, cpm_byte *dma); + diff --git a/Tools/unix/zx/cpmparse.c b/Tools/unix/zx/cpmparse.c new file mode 100644 index 00000000..4c709ec1 --- /dev/null +++ b/Tools/unix/zx/cpmparse.c @@ -0,0 +1,126 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file parses filenames to FCBs. +*/ + +#include "cpmint.h" + +#define is_num(c) ((c >= '0') && (c <= '9')) + +static int parse_drive_user(char *txt, cpm_byte *fcb) +{ + char uid[4], drvid[4]; + int up, dp; + + for (up = dp = 0; *txt != ':'; ++txt) + { + if (is_num (*txt)) uid [up++] = *txt; + if (isalpha(*txt)) drvid[dp++] = *txt; + if (!is_num(*txt) && !isalpha(*txt)) return -1; + } + uid[up] = 0; drvid[dp] = 0; + + if (dp > 1) return -1; /* Invalid driveletter */ + if (up > 2) return -1; /* Invalid uid */ + + fcb[0x0d] = atoi(uid) + 1; if (fcb[0x0d] > 16) return -1; + + if (islower(drvid[0])) drvid[0] = toupper(drvid[0]); + + if (drvid[0] < 'A' || drvid[0] > 'P') return -1; + + fcb[0] = drvid[0] - '@'; + return 0; +} + + + +cpm_word fcb_parse(char *txt, cpm_byte *fcb) +{ + int nl = 0, tl = 0, pl = 0, phase = 0; + char *ntxt, ch; + + memset(fcb, 0, 0x24); + + if (txt[1] == ':' || txt[2] == ':' || txt[3] == ':') + { + if (parse_drive_user(txt, fcb)) return 0xFFFF; + /* Move past the colon */ + ntxt = strchr(txt, ':') + 1; + } + else ntxt = txt; + while (phase < 3) + { + ch = *ntxt; + if (islower(ch)) ch = toupper(ch); + + switch(ch) + { + case 0: + case '\r': /* EOL */ + phase = 4; + break; + + case '.': /* file.typ */ + if (!phase) ++phase; + else phase = 3; + break; + + case ';': /* Password */ + if (phase < 2) phase = 2; + else phase = 3; + break; + + case '[': case ']': case '=': case 9: case ' ': + case '>': case '<': case ':': case ',': case '/': + case '|': /* Terminator */ + phase = 3; + + default: + switch(phase) + { + case 0: + if (nl >= 8) return 0xFFFF; + fcb[++nl] = ch; + break; + + case 1: + if (tl >= 3) return 0xFFFF; + fcb[tl + 9] = ch; + ++tl; + break; + + case 2: + if (pl >= 8) return 0xFFFF; + fcb[pl + 0x10] = ch; + ++pl; + break; + } + break; + } + } + if (!nl) return 0xFFFF; + + fcb[0x1A] = pl; + + if (phase == 4) return 0; + + return ntxt - txt; +} diff --git a/Tools/unix/zx/cpmredir.c b/Tools/unix/zx/cpmredir.c new file mode 100644 index 00000000..4fd5f9ca --- /dev/null +++ b/Tools/unix/zx/cpmredir.c @@ -0,0 +1,931 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* This file handles actual reading and writing */ + +#define CPMDEF +#include "cpmint.h" + +#ifdef DEBUG +#define SHOWNAME(func) \ + { \ + char fname[CPM_MAXPATH]; \ + redir_fcb2unix(fcb, fname); \ + redir_Msg(func "(\"%s\")\n", fname); \ + } + +#else + #define SHOWNAME(func) +#endif + + +/* DISK BDOS FUNCTIONS */ + +/* General treatment: + * + * We use the "disk block number" fields in the FCB to store our file handle; + * this is a similar trick to that used by DOSPLUS, which stores its cluster + * number in there. It works if: + * + * a) sizeof(int) <= 8 bytes (64 bits). If it's more, this needs rewriting + * to use a hash table; + * b) the program never touches these bytes. Practically no CP/M program does. + * + * We store a "magic number" (0x00FD) in the first two bytes of this field, and + * if the number has been changed then we abort. + * + * nb: Since I wrote ZXCC, I have found that DOSPLUS uses 0x8080 as a magic + * number [well, actually this is an oversimplification, but a hypothetical + * program written against DOSPLUS would work with 0x8080]. Perhaps 0x8080 + * should be used instead. + * + * Format of the field: + * + * [--2 bytes--] magic number + * [--8 bytes--] file handle. 8 bytes reserved but only 4 currently used. + * [--2 bytes--] reserved. + * [--4 bytes--] file length. + */ +#define MAGIC_OFFSET 0x10 +#define HANDLE_OFFSET 0x12 +#define LENGTH_OFFSET 0x1C + +cpm_word fcb_open(cpm_byte *fcb, cpm_byte *dma) +{ + char fname[CPM_MAXPATH]; + int handle; + int drv, l; + char *s; + DIR *dir; + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + redir_log_fcb(fcb); + + drv = fcb[0] & 0x7F; + if (!drv) drv = redir_cpmdrive; else --drv; + + if (fcb[0] & 0x80) /* Open directory */ + { + if (fcb[0x0C]) return 0x0BFF; /* Can't assign "floating" dir */ + + if (!memcmp(fcb + 1, ". ", 11)) + { + return 0; /* Opening "." */ + } + if (!memcmp(fcb + 1, ".. ", 11)) + { + l = strlen(redir_drive_prefix[drv]) - 1; + s = redir_drive_prefix[drv]; + --l; + while (l > 0) + { + if (s[l] == '/') break; +#ifdef __MSDOS__ + if (s[l] == '\\') break; + if (s[l] == ':') break; +#endif + --l; + } +#ifdef __MSDOS__ + if (l < 2) return 0; /* "C:" */ +#else + if (l <= 0) return 0; /* "/" */ +#endif + ++l; + s[l] = 0; + return 0; + } +/* Opening some other directory */ + + dir = opendir(fname); + if (!dir) return 0xFF; /* Not a directory */ + closedir(dir); + strcpy(redir_drive_prefix[drv], fname); + strcat(redir_drive_prefix[drv], "/"); + return 0; + } + + /* Note: Some programs (MAC is an example) don't close a file + * if they opened it just to do reading. MAC then reopens the + * file (which rewinds it); this causes FCB leaks under some + * DOS-based emulators */ + + handle = redir_ofile(fcb, fname); + redir_Msg("fcb_open(\"%s\")\r\n", fname); + if (handle < 0 && redir_password_error()) + { + redir_Msg("1st chance open failed on %s\r\n", fname); + redir_password_append(fname, dma); + redir_Msg("Trying with %s\r\n", fname); + handle = redir_ofile(fcb, fname); + } + + + if (handle == -1) + { + redir_Msg("Ret: -1\n"); + if (redir_password_error()) return 0x7FF; + return 0xFF; + } + fcb[MAGIC_OFFSET ] = 0xFD; /* "Magic number" */ + fcb[MAGIC_OFFSET + 1] = 0x00; + +/* TODO: Should the magic number perhaps be 0x8080, as in DOSPLUS? */ + + redir_wrhandle(fcb + HANDLE_OFFSET, handle); + + redir_put_fcb_pos(fcb, fcb[0x0C] * 16384); + /* (v1.01) "seek" to beginning of extent, not file. + * This is necessary for the awful I/O code + * in LINK-80 to work + */ + + /* Get the file length */ + redir_wr32(fcb + 0x1C, zxlseek(handle, 0, SEEK_END)); + zxlseek(handle, 0, SEEK_SET); + + /* Set the last record byte count */ + if (fcb[0x20] == 0xFF) fcb[0x20] = fcb[0x1C] & 0x7F; + + redir_Msg("Ret: 0\n"); + + return 0; +} + + +cpm_word fcb_close(cpm_byte *fcb) +{ + int handle, drv; + + SHOWNAME("fcb_close") + + if ((handle = redir_verify_fcb(fcb)) < 0) return -1; + redir_Msg(" (at %lx)\n", zxlseek(handle, 0, SEEK_CUR)); + + if (fcb[0] & 0x80) /* Close directory */ + { + drv = fcb[0] & 0x7F; + if (!drv) drv = redir_cpmdrive; else drv--; +#ifdef __MSDOS__ + strcpy(redir_drive_prefix[drv] + 1, ":/"); +#else + strcpy(redir_drive_prefix[drv], "/"); +#endif + return 0; + } + + if (fcb[5] & 0x80) /* CP/M 3: Flush rather than close */ + { +#ifndef WIN32 + sync(); +#endif + return 0; + } + +#ifdef WIN32 + { + BOOL b; + redir_Msg(">CloseHandle() Handle=%lu\n", handle); + b = CloseHandle((HANDLE)handle); + redir_Msg("80h, let it be 80h + +*/ + + +cpm_word fcb_read(cpm_byte *fcb, cpm_byte *dma) +{ + int handle; + int rv, n, rd_len; + long npos; + + SHOWNAME("fcb_read") + + if ((handle = redir_verify_fcb(fcb)) < 0) return 9; /* Invalid FCB */ + + /* The program may have mucked about with the counters, so + * do an lseek() to where it should be. */ + + npos = redir_get_fcb_pos(fcb); + zxlseek(handle, npos, SEEK_SET); + redir_Msg(" (from %lx)\n", zxlseek(handle, 0, SEEK_CUR)); + + /* Read in the required amount */ + +#ifdef WIN32 + { + BOOL b; + redir_Msg(">ReadFile() Handle=%lu, DMA=%lu, Len=%lu\n", handle, dma, redir_rec_len); + b = ReadFile((HANDLE)handle, dma, redir_rec_len, (unsigned long *)(&rv), NULL); + redir_Msg("WriteFile() Handle=%lu, DMA=%lu, Len=%lu\n", handle, dma, redir_rec_len); + b = WriteFile((HANDLE)handle, dma, redir_rec_len, (unsigned long *)(&rv), NULL); + redir_Msg("CreateFile([CREATE_ALWAYS]) Name='%s'\n", fname); + handle = (int)CreateFile(fname, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + redir_Msg("ReadFile() Handle=%lu, DMA=%lu, Len=%lu\n", handle, dma, redir_rec_len); + b = ReadFile((HANDLE)handle, dma, redir_rec_len, (unsigned long *)(&rv), NULL); + redir_Msg("WriteFile() Handle=%lu, DMA=%lu, Len=%lu\n", handle, dma, redir_rec_len); + b = WriteFile((HANDLE)handle, dma, redir_rec_len, (unsigned long *)(&rv), NULL); + redir_Msg("WriteFile() Handle=%lu, DMA=%lu, Len=%lu\n", handle, zerorec, rl); + b = WriteFile((HANDLE)handle, zerorec, rl, (unsigned long *)(&rv), NULL); + redir_Msg("= 0) len += rv; + + if (rv < rl) + { + redir_wr32(fcb + LENGTH_OFFSET, len); + return redir_xlt_err(); + } + } + redir_wr32(fcb + LENGTH_OFFSET, offs); + + return fcb_randwr(fcb, dma); +} + +cpm_word fcb_tell(cpm_byte *fcb) +{ + int handle; + off_t rv; + + SHOWNAME("fcb_tell") + + if ((handle = redir_verify_fcb(fcb)) < 0) return 9; /* Invalid FCB */ + + rv = zxlseek(handle, 0, SEEK_CUR); + + if (rv < 0) return 0xFF; + + rv = rv >> 7; + fcb[0x21] = rv & 0xFF; + fcb[0x22] = (rv >> 8) & 0xFF; + fcb[0x23] = (rv >> 16) & 0xFF; + return 0; +} + + +cpm_word fcb_stat(cpm_byte *fcb) +{ + char fname[CPM_MAXPATH]; + struct stat st; + int rv; + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + rv = stat(fname, &st); + + redir_Msg("fcb_stat(\"%s\") fcb=%x\n", fname, (int)fcb); + if (rv < 0) + { + redir_Msg("ret: -1\n"); + return 0xFF; + } + + redir_wr24(fcb + 0x21, (st.st_size + 127) / 128); + + redir_Msg("ret: 0"); + return 0; +} + + +cpm_word fcb_multirec(cpm_byte rc) +{ + if (rc < 1 || rc > 128) return 0xFF; + + redir_rec_multi = rc; + redir_rec_len = 128 * rc; + redir_Msg("Set read/write to %d bytes\n", redir_rec_len); + return 0; +} + + +cpm_word fcb_date(cpm_byte *fcb) +{ + char fname[CPM_MAXPATH]; + struct stat st; + int rv; + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + rv = stat(fname, &st); + + redir_Msg("fcb_stat(\"%s\")\n", fname); + if (rv < 0) return 0xFF; + + redir_wr32(fcb + 0x18, redir_cpmtime(st.st_atime)); + redir_wr32(fcb + 0x1C, redir_cpmtime(st.st_ctime)); + + fcb[0x0C] = redir_cpm_pwmode(redir_drdos_get_rights(fname)); + return 0; +} + + + +cpm_word fcb_trunc(cpm_byte *fcb, cpm_byte *dma) +{ + char fname[CPM_MAXPATH]; + dword offs = redir_rd24(fcb + 0x21) * 128; + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + /* Software write-protection */ + if (redir_ro_fcb(fcb)) return 0x02FF; + + redir_log_fcb(fcb); +#ifdef WIN32 + (void)offs; + return 0x06FF; /* Simply not implemented */ +#else + if (truncate(fname, offs)) + { + if (redir_password_error()) + { + redir_password_append(fname, dma); + if (!truncate(fname, offs)) return 0; + } + return redir_xlt_err(); + } + return 0; +#endif +} + + +cpm_word fcb_sdate(cpm_byte *fcb, cpm_byte *dma) +{ + char fname[CPM_MAXPATH]; +#ifdef WIN32 + /* TODO: Use SetFileTime() here */ + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + /* Software write-protection */ + if (redir_ro_fcb(fcb)) return 0x02FF; + + redir_log_fcb(fcb); +#else + struct utimbuf buf; + + buf.actime = redir_unixtime(dma); + buf.modtime = redir_unixtime(dma + 4); + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + /* Software write-protection */ + if (redir_ro_fcb(fcb)) return 0x02FF; + + redir_log_fcb(fcb); + + if (utime(fname, &buf)) + { + if (redir_password_error()) + { + redir_password_append(fname, dma); + if (!utime(fname, &buf)) return 0; + } + return redir_xlt_err(); + } +#endif + return 0; +} + + + +cpm_word fcb_chmod(cpm_byte *fcb, cpm_byte *dma) +{ + char fname[CPM_MAXPATH]; + struct stat st; + int handle, wlen, omode; + long offs, newoffs; + cpm_byte zero[128]; + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + /* Software write-protection */ + if (redir_ro_fcb(fcb)) return 0x02FF; + + redir_log_fcb(fcb); + + if (stat(fname, &st)) return redir_xlt_err(); + +#ifdef __MSDOS__ + omode = 0; + if (fcb[9] & 0x80) omode |= 1; + if (fcb[10] & 0x80) omode |= 4; + if (!(fcb[11] & 0x80)) omode |= 0x20; + + if (_chmod(fname, 1, omode) < 0) + { + if (redir_password_error()) + { + redir_password_append(fname, dma); + if (_chmod(fname, 1, omode) >= 0) return 0; + } + return redir_xlt_err(); + } +#elif defined (WIN32) + omode = 0; + + if (fcb[9] & 0x80) omode |= FILE_ATTRIBUTE_READONLY; + if (fcb[10] & 0x80) omode |= FILE_ATTRIBUTE_SYSTEM; + if (!(fcb[11] & 0x80)) omode |= FILE_ATTRIBUTE_ARCHIVE; + + if (!omode) omode = FILE_ATTRIBUTE_NORMAL; + + SetFileAttributes(fname, omode); +#else + omode = st.st_mode; + if (fcb[9] & 0x80) /* Read-only */ + { + st.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + } + else st.st_mode |= S_IWUSR; + + if (omode != st.st_mode) + { + if (chmod(fname, st.st_mode)) return redir_xlt_err(); + } + +#endif + + if (fcb[6] & 0x80) /* Set exact size */ + { + if (stat(fname, &st)) return redir_xlt_err(); + + handle = open(fname, O_RDWR | O_BINARY); + if (handle < 0) return redir_xlt_err(); + + newoffs = offs = ((st.st_size + 127) / 128) * 128; + if (fcb[0x20] & 0x7F) + { + newoffs -= (0x80 - (fcb[0x20] & 0x7F)); + } + if (newoffs == st.st_size) + { + ; /* Nothing to do! */ + } + else if (newoffs < st.st_size) + { +#ifndef WIN32 /* XXX Do this somehow in Win32 */ + if (ftruncate(handle, newoffs)) + { + close(handle); + return redir_xlt_err(); + } +#endif + } + else while (newoffs > st.st_size) + { + wlen = newoffs - st.st_size; + if (wlen > 0x80) wlen = 0x80; + memset(zero, 0x1A, sizeof(zero)); + if (write(handle, zero, wlen) < wlen) + { + close(handle); + return redir_xlt_err(); + } + st.st_size += wlen; + } + close(handle); + } + return 0; +} + + + + +cpm_word fcb_setpwd(cpm_byte *fcb, cpm_byte *dma) +{ +#ifdef __MSDOS__ + char fname[CPM_MAXPATH]; + cpm_word rv; + + /* Don't support ambiguous filenames */ + if (redir_fcb2unix(fcb, fname)) return 0x09FF; + + /* Software write-protection */ + if (redir_ro_fcb(fcb)) return 0x02FF; + + redir_log_fcb(fcb); + + rv = redir_drdos_put_rights(fname, dma, redir_drdos_pwmode(fcb[0x0c])); + if (rv || !(fcb[0x0c] & 1)) return rv; + return redir_drdos_put_rights(fname, dma, redir_drdos_pwmode(fcb[0x0c]) | 0x8000); +#else + return 0xFF; /* Unix doesn't do this */ +#endif +} + + +cpm_word fcb_getlbl(cpm_byte drv) +{ + redir_Msg("fcb_getlbl()\r\n"); +#ifdef __MSDOS__ + if (redir_drdos) return 0xA1; /* Supports passwords & Update stamps */ + return 0x21; /* Update stamps only */ +#else + return 0x61; /* Update & Access stamps */ +#endif +} + +cpm_word fcb_setlbl(cpm_byte *fcb, cpm_byte *dma) +{ +/* I am not letting CP/M fiddle with the host's FS settings - even if they + * could be altered, which they mostly can't. */ + + return 0x03FF; +} + + + +cpm_word fcb_defpwd(cpm_byte *pwd) +{ +#ifdef __MSDOS__ + union REGS r; + struct SREGS s; + + if (pwd[0] == 0 || pwd[0] == ' ') + { + redir_passwd[0] = 0; + } + else memcpy(redir_passwd, pwd, 8); + if (redir_drdos) + { + dosmemput(pwd, 8, __tb); + r.w.ax = 0x4454; + r.w.dx = __tb & 0x0F; + s.ds = __tb >> 4; + intdosx(&r, &r, &s); + } + +#endif + return 0; +} + + diff --git a/Tools/unix/zx/cpmredir.h b/Tools/unix/zx/cpmredir.h new file mode 100644 index 00000000..2584e239 --- /dev/null +++ b/Tools/unix/zx/cpmredir.h @@ -0,0 +1,151 @@ +/* + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file holds the public interface to CPMREDIR. +*/ + +#ifndef CPMREDIR_H_INCLUDED + +#define CPMREDIR_H_INCLUDED 16-11-1998 + +/* The "cpm_byte" must be exactly 8 bits. + The "cpm_word" must be exactly 16 bits. */ + +typedef unsigned char cpm_byte; +typedef unsigned short cpm_word; + +/* Maximum length of a directory path */ +#ifdef _POSIX_PATH_MAX + #define CPM_MAXPATH _POSIX_PATH_MAX +#else + #ifdef _MAX_PATH + #define CPM_MAXPATH _MAX_PATH + #else + #define CPM_MAXPATH 260 + #endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initialise this library. Call this function first. + * + * Returns 0 if failed to initialise. + */ +int fcb_init(void); + +/* Deinitialise the library. */ + +void fcb_deinit(void); + +/* Translate a name from the host FS to a CP/M name. This will (if necessary) + * create a mapping between a CP/M drive and a host directory path. + * + * CP/M drives A: to O: can be mapped in this way. P: is always the current + * drive. + * + */ + +void xlt_name(char *localname, char *cpmname); + +/* It is sometimes convenient to set some fixed mappings. This will create + * a mapping for a given directory. + * Pass drive = -1 for "first available", or 0-15 for A: to P: + * Returns 1 if OK, 0 if requested drive not available. + * + * NB: It is important that the localname should have a trailing + * directory separator! + */ + +int xlt_map(int drive, char *localdir); + +/* + * This revokes a mapping. No check is made whether CP/M has files open + * on the drive or not. + */ + +int xlt_umap(int drive); + +/* Find out if a drive is mapped, and if so to what directory */ + +char *xlt_getcwd(int drive); + + +/* BDOS functions. Eventually this should handle all disc-related BDOS + * functions. + * + * I am assuming that your emulator has the CP/M RAM in its normal address + * space, accessible as a range 0-64k. If this is not the case + * (eg: you are emulating banked memory, or using a segmented architecture) + * you will have to use "copy in and copy out" techniques. The "fcb" area + * must be 36 bytes long; the "dma" area should be 128 * the value set + * in fcb_multirec() [default is 1, so 128 bytes]. + * + */ + +cpm_byte fcb_reset (void); /* 0x0D */ +cpm_word fcb_drive (cpm_byte drv); /* 0x0E */ +cpm_word fcb_open (cpm_byte *fcb, cpm_byte *dma); /* 0x0F */ +cpm_word fcb_close (cpm_byte *fcb); /* 0x10 */ +cpm_word fcb_find1 (cpm_byte *fcb, cpm_byte *dma); /* 0x11 */ +cpm_word fcb_find2 (cpm_byte *fcb, cpm_byte *dma); /* 0x12 */ +cpm_word fcb_unlink(cpm_byte *fcb, cpm_byte *dma); /* 0x13 */ +cpm_word fcb_read (cpm_byte *fcb, cpm_byte *dma); /* 0x14 */ +cpm_word fcb_write (cpm_byte *fcb, cpm_byte *dma); /* 0x15 */ +cpm_word fcb_creat (cpm_byte *fcb, cpm_byte *dma); /* 0x16 */ +cpm_word fcb_rename(cpm_byte *fcb, cpm_byte *dma); /* 0x17 */ +cpm_word fcb_logvec(void); /* 0x18 */ +cpm_byte fcb_getdrv(void); /* 0x19 */ +/* DMA is a parameter to routines, not a separate call */ +cpm_word fcb_getalv(cpm_byte *alv, cpm_word max); /* 0x1B */ +/* Get alloc vector: caller must provide space and say how big it is. */ +cpm_word fcb_rodisk(void); /* 0x1C */ +cpm_word fcb_rovec (void); /* 0x1D */ +cpm_word fcb_chmod (cpm_byte *fcb, cpm_byte *dma); /* 0x1E */ +cpm_word fcb_getdpb(cpm_byte *dpb); /* 0x1F */ +cpm_byte fcb_user (cpm_byte usr); /* 0x20 */ +cpm_word fcb_randrd(cpm_byte *fcb, cpm_byte *dma); /* 0x21 */ +cpm_word fcb_randwr(cpm_byte *fcb, cpm_byte *dma); /* 0x22 */ +cpm_word fcb_stat (cpm_byte *fcb); /* 0x23 */ +cpm_word fcb_tell (cpm_byte *fcb); /* 0x24 */ +cpm_word fcb_resro (cpm_word bitmap); /* 0x25 */ +/* Access Drives and Free Drives are not supported. */ +cpm_word fcb_randwz(cpm_byte *fcb, cpm_byte *dma); /* 0x28 */ +/* Record locking calls not supported (though they could be) */ +cpm_word fcb_multirec(cpm_byte rc); /* 0x2C */ +/* Set hardware error action must be done by caller */ +cpm_word fcb_dfree (cpm_byte drive, cpm_byte *dma);/* 0x2E */ +cpm_word fcb_sync (cpm_byte flag); /* 0x30 */ +cpm_word fcb_purge (void); /* 0x62 */ +cpm_word fcb_trunc (cpm_byte *fcb, cpm_byte *dma); /* 0x63 */ +cpm_word fcb_setlbl(cpm_byte *fcb, cpm_byte *dma); /* 0x64 */ +cpm_word fcb_getlbl(cpm_byte drive); /* 0x65 */ +cpm_word fcb_date (cpm_byte *fcb); /* 0x66 */ +cpm_word fcb_setpwd(cpm_byte *fcb, cpm_byte *dma); /* 0x67 */ +cpm_word fcb_defpwd(cpm_byte *pwd); /* 0x6A */ +cpm_word fcb_sdate (cpm_byte *fcb, cpm_byte *dma); /* 0x74 */ +cpm_word fcb_parse (char *txt, cpm_byte *fcb); /* 0x98 */ + +/* fcb_parse returns length of filename parsed, 0 if EOL, 0xFFFF if error */ +#ifdef __cplusplus +} +#endif + + +#endif /* def CPMREDIR_H_INCLUDED */ diff --git a/Tools/unix/zx/dirent.c b/Tools/unix/zx/dirent.c new file mode 100644 index 00000000..87cf85a9 --- /dev/null +++ b/Tools/unix/zx/dirent.c @@ -0,0 +1,149 @@ +/* + + Implementation of POSIX directory browsing functions and types for Win32. + + Author: Kevlin Henney (kevlin@acm.org, kevlin@curbralan.com) + History: Created March 1997. Updated June 2003 and July 2012. + Rights: See end of file. + +*/ + +#include "dirent.h" +#include +#include /* _findfirst and _findnext set errno iff they return -1 */ +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +//typedef ptrdiff_t handle_type; /* C99's intptr_t not sufficiently portable */ +typedef long handle_type; /* C99's intptr_t not sufficiently portable */ + +struct DIR +{ + handle_type handle; /* -1 for failed rewind */ + struct _finddata_t info; + struct dirent result; /* d_name null iff first time */ + char *name; /* null-terminated char string */ +}; + +DIR *opendir(const char *name) +{ + DIR *dir = 0; + + if(name && name[0]) + { + size_t base_length = strlen(name); + const char *all = /* search pattern must end with suitable wildcard */ + strchr("/\\", name[base_length - 1]) ? "*" : "/*"; + + if((dir = (DIR *) malloc(sizeof *dir)) != 0 && + (dir->name = (char *) malloc(base_length + strlen(all) + 1)) != 0) + { + strcat(strcpy(dir->name, name), all); + + if((dir->handle = + (handle_type) _findfirst(dir->name, &dir->info)) != -1) + { + dir->result.d_name = 0; + } + else /* rollback */ + { + free(dir->name); + free(dir); + dir = 0; + } + } + else /* rollback */ + { + free(dir); + dir = 0; + errno = ENOMEM; + } + } + else + { + errno = EINVAL; + } + + return dir; +} + +int closedir(DIR *dir) +{ + int result = -1; + + if(dir) + { + if(dir->handle != -1) + { + result = _findclose(dir->handle); + } + + free(dir->name); + free(dir); + } + + if(result == -1) /* map all errors to EBADF */ + { + errno = EBADF; + } + + return result; +} + +struct dirent *readdir(DIR *dir) +{ + struct dirent *result = 0; + + if(dir && dir->handle != -1) + { + if(!dir->result.d_name || _findnext(dir->handle, &dir->info) != -1) + { + result = &dir->result; + result->d_name = dir->info.name; + } + } + else + { + errno = EBADF; + } + + return result; +} + +void rewinddir(DIR *dir) +{ + if(dir && dir->handle != -1) + { + _findclose(dir->handle); + dir->handle = (handle_type) _findfirst(dir->name, &dir->info); + dir->result.d_name = 0; + } + else + { + errno = EBADF; + } +} + +#ifdef __cplusplus +} +#endif + +/* + + Copyright Kevlin Henney, 1997, 2003, 2012. All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose is hereby granted without fee, provided + that this copyright and permissions notice appear in all copies and + derivatives. + + This software is supplied "as is" without express or implied warranty. + + But that said, if there are any problems please get in touch. + +*/ diff --git a/Tools/unix/zx/dirent.h b/Tools/unix/zx/dirent.h new file mode 100644 index 00000000..bbbfce52 --- /dev/null +++ b/Tools/unix/zx/dirent.h @@ -0,0 +1,50 @@ +#ifndef DIRENT_INCLUDED +#define DIRENT_INCLUDED + +/* + + Declaration of POSIX directory browsing functions and types for Win32. + + Author: Kevlin Henney (kevlin@acm.org, kevlin@curbralan.com) + History: Created March 1997. Updated June 2003. + Rights: See end of file. + +*/ + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct DIR DIR; + +struct dirent +{ + char *d_name; +}; + +DIR *opendir(const char *); +int closedir(DIR *); +struct dirent *readdir(DIR *); +void rewinddir(DIR *); + +/* + + Copyright Kevlin Henney, 1997, 2003. All rights reserved. + + Permission to use, copy, modify, and distribute this software and its + documentation for any purpose is hereby granted without fee, provided + that this copyright and permissions notice appear in all copies and + derivatives. + + This software is supplied "as is" without express or implied warranty. + + But that said, if there are any problems please get in touch. + +*/ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Tools/unix/zx/drdos.c b/Tools/unix/zx/drdos.c new file mode 100644 index 00000000..97bd583c --- /dev/null +++ b/Tools/unix/zx/drdos.c @@ -0,0 +1,236 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file holds DRDOS-specific password code. +*/ + +#include "cpmint.h" + +cpm_word redir_drdos_pwmode(cpm_byte b) +{ + cpm_word mode = 0; + + if (b & 0x80) mode |= 0xddd; + if (b & 0x40) mode |= 0x555; + if (b & 0x20) mode |= 0x111; + + return mode; +} + +cpm_byte redir_cpm_pwmode(cpm_word w) +{ + cpm_byte mode = 0; + + if (w & 0x8) mode |= 0x80; + if (w & 0x4) mode |= 0x40; + if (w & 0x1) mode |= 0x20; + + return mode; +} + +#ifdef __MSDOS__ +#ifdef __GO32__ /* The GO32 extender doesn't understand DRDOS password + * functions, so these are done with __dpmi_int() rather + * than intdos() */ + +cpm_word redir_drdos_get_rights(char *path) +{ + __dpmi_regs r; + + if (!redir_drdos) return 0; + + redir_Msg("Rights for file %s: \n\r", path); + + dosmemput(path, strlen(path) + 1, __tb); + r.x.ax = 0x4302; + r.x.dx = __tb & 0x0F; + r.x.ds = (__tb) >> 4; + + __dpmi_int(0x21, &r); + + redir_Msg(" %04x \n\r", r.x.cx); + + if (r.x.flags & 1) return 0; + return r.x.cx; +} + + +cpm_word redir_drdos_put_rights(char *path, cpm_byte *dma, cpm_word rights) +{ + __dpmi_regs r; + + if (!redir_drdos) return 0; + + redir_Msg("Put rights for file %s: %04x %-8.8s %-8.8s\n\r", path, rights, dma, dma + 8); + + dosmemput(dma+8, 8, __tb); /* Point DTA at password */ + r.x.ax = 0x1A00; + r.x.dx = (__tb & 0x0F); + r.x.ds = (__tb) >> 4; + __dpmi_int(0x21, &r); + + dosmemput(path, strlen(path) + 1, __tb + 0x10); + r.x.ax = 0x4303; /* Set rights */ + r.x.cx = rights; + r.x.dx = (__tb & 0x0F) + 0x10; + r.x.ds = (__tb) >> 4; + + __dpmi_int(0x21, &r); + + if (r.x.flags & 1) + { + redir_Msg(" Try 1 failed. Error %04x\n\r", r.x.ax); + if (redir_password_error()) + { + redir_password_append(path, dma); + + dosmemput(path, strlen(path) + 1, __tb + 0x10); + r.x.ax = 0x4303; /* Set rights */ + r.x.cx = rights; + r.x.dx = (__tb & 0x0F) + 0x10; + r.x.ds = (__tb) >> 4; + + __dpmi_int(0x21, &r); + if (!r.x.flags & 1) return 0; + if (redir_password_error()) return 0x7FF; + } + return 0xFF; + } + return 0; +} + +#else /* __GO32__ */ + +cpm_word redir_drdos_get_rights(char *path) +{ + union REGS r; + struct SREGS s; + + if (!redir_drdos) return 0; + + redir_Msg("Rights for file %s: \n\r", path); + + dosmemput(path, strlen(path) + 1, __tb); + r.w.ax = 0x4302; + r.w.dx = __tb & 0x0F; + s.ds = (__tb) >> 4; + + intdosx(&r, &r, &s); + + redir_Msg(" %04x \n\r", r.w.cx); + + if (r.w.cflag) return 0; + return r.w.cx; +} + + +cpm_word redir_drdos_put_rights(char *path, cpm_byte *dma, cpm_word rights) +{ + union REGS r; + struct SREGS s; + + if (!redir_drdos) return 0; + + redir_Msg("Put rights for file %s: %04x\n\r", path, rights); + + dosmemput(dma, 8, __tb); /* Point DTA at password */ + r.w.ax = 0x1A00; + r.w.dx = (__tb & 0x0F); + s.ds = (__tb) >> 4; + intdosx(&r, &r, &s); + + dosmemput(path, strlen(path) + 1, __tb + 0x10); + r.w.ax = 0x4303; /* Set rights */ + r.w.cx = rights; + r.w.dx = (__tb & 0x0F) + 0x10; + s.ds = (__tb) >> 4; + + intdosx(&r, &r, &s); + + if (r.w.cflag) + { + redir_Msg(" Try 1 failed. Error %04x \n\r", r.w.ax); + if (redir_password_error()) + { + redir_password_append(path, dma); + + dosmemput(path, strlen(path) + 1, __tb + 0x10); + r.w.ax = 0x4303; /* Set rights */ + r.w.cx = rights; + r.w.dx = (__tb & 0x0F) + 0x10; + s.ds = (__tb) >> 4; + + intdosx(&r, &r, &s); + if (!r.w.cflag) return 0; + } + return 0xFF; + } + return 0; +} + +#endif /* __GO32__ */ + + +cpm_word redir_password_error(void) +{ + union REGS r; + + if (!redir_drdos) return 0; + + r.w.ax = 0x5900; + r.w.bx = 0x0000; + + intdos(&r, &r); + + redir_Msg("Last error was: %04x\r\n", r.w.ax); + + if (r.w.ax == 0x56) return 1; /* Bad password */ + return 0; +} + + +void redir_password_append(char *s, cpm_byte *dma) +{ + int n, m; + + if (!redir_drdos) return; + + if (dma[0] == 0 || dma[0] == 0x20) return; + + strcat(s, ";"); + m = strlen(s); + + for (n = 0; n < 8; n++) + { + if (dma[n] == ' ') s[m] = 0; + else s[m] = dma[n]; + ++m; + } + s[m] = 0; + +} +#else /* __MSDOS__ */ +void redir_password_append(char *s, cpm_byte *dma) {} +cpm_word redir_password_error(void) { return 0; } +cpm_word redir_drdos_put_rights(char *path, cpm_byte *dma, cpm_word rights) +{ return 0; } +cpm_word redir_drdos_get_rights(char *path) { return 0; } +#endif /* __MSDOS__ */ + + diff --git a/Tools/unix/zx/edops.h b/Tools/unix/zx/edops.h new file mode 100644 index 00000000..6b04c96a --- /dev/null +++ b/Tools/unix/zx/edops.h @@ -0,0 +1,567 @@ +/* Emulations of the ED operations of the Z80 instruction set. + * Copyright (C) 1994 Ian Collier. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define input(var) { unsigned short u;\ + var=u=in(tstates,b,c);\ + tstates+=u>>8;\ + f=(f&1)|(var&0xa8)|((!var)<<6)|parity(var);\ + } +#define sbchl(x) { unsigned short z=(x);\ + unsigned long t=(hl-z-cy)&0x1ffff;\ + f=((t>>8)&0xa8)|(t>>16)|2|\ + (((hl&0xfff)<(z&0xfff)+cy)<<4)|\ + (((hl^z)&(hl^t)&0x8000)>>13)|\ + ((!(t&0xffff))<<6)|2;\ + l=t;\ + h=t>>8;\ + } + +#define adchl(x) { unsigned short z=(x);\ + unsigned long t=hl+z+cy;\ + f=((t>>8)&0xa8)|(t>>16)|\ + (((hl&0xfff)+(z&0xfff)+cy>0xfff)<<4)|\ + (((~hl^z)&(hl^t)&0x8000)>>13)|\ + ((!(t&0xffff))<<6)/*|2*/;\ + l=t;\ + h=t>>8;\ + } +/* [JCE] The "|2" should not be there, at least according to my tests on + * a PCW16. The PCW16's ADC always resets that bit. */ + + +#define neg (a=-a,\ + f=(a&0xa8)|((!a)<<6)|(((a&15)>0)<<4)|((a==128)<<2)|2|(a>0)) + +{ + unsigned char op=fetch(pc); + pc++; + radjust++; + switch(op){ +instr(0x40,8); + input(b); +endinstr; + +instr(0x41,8); + tstates+=out(tstates,b,c,b); +endinstr; + +instr(0x42,11); + sbchl(bc); +endinstr; + +instr(0x43,16); + {unsigned short addr=fetch2(pc); + pc+=2; + store2b(addr,b,c); + } +endinstr; + +instr(0x44,4); + neg; +endinstr; + +instr(0x45,4); + iff1=iff2; + ret; +endinstr; + +instr(0x46,4); + im=0; +endinstr; + +instr(0x47,5); + i=a; +endinstr; + +instr(0x48,8); + input(c); +endinstr; + +instr(0x49,8); + tstates+=out(tstates,b,c,c); +endinstr; + +instr(0x4a,11); + adchl(bc); +endinstr; + +instr(0x4b,16); + {unsigned short addr=fetch2(pc); + pc+=2; + c=fetch(addr); + b=fetch(addr+1); + } +endinstr; + +instr(0x4c,4); + neg; +endinstr; + +instr(0x4d,4); + ret; +endinstr; + +instr(0x4e,4); + im=1; +endinstr; + +instr(0x4f,5); + r=a; + radjust=r; +endinstr; + +instr(0x50,8); + input(d); +endinstr; + +instr(0x51,8); + tstates+=out(tstates,b,c,d); +endinstr; + +instr(0x52,11); + sbchl(de); +endinstr; + +instr(0x53,16); + {unsigned short addr=fetch2(pc); + pc+=2; + store2b(addr,d,e); + } +endinstr; + +instr(0x54,4); + neg; +endinstr; + +instr(0x55,4); + ret; +endinstr; + +instr(0x56,4); + im=2; +endinstr; + +instr(0x57,5); + a=i; + f=(f&1)|(a&0xa8)|((!a)<<6)|(iff2<<2); +endinstr; + +instr(0x58,8); + input(e); +endinstr; + +instr(0x59,8); + tstates+=out(tstates,b,c,e); +endinstr; + +instr(0x5a,11); + adchl(de); +endinstr; + +instr(0x5b,16); + {unsigned short addr=fetch2(pc); + pc+=2; + e=fetch(addr); + d=fetch(addr+1); + } +endinstr; + +instr(0x5c,4); + neg; +endinstr; + +instr(0x5d,4); + ret; +endinstr; + +instr(0x5e,4); + im=3; +endinstr; + +instr(0x5f,5); + r=(r&0x80)|(radjust&0x7f); + a=r; + f=(f&1)|(a&0xa8)|((!a)<<6)|(iff2<<2); +endinstr; + +instr(0x60,8); + input(h); +endinstr; + +instr(0x61,8); + tstates+=out(tstates,b,c,h); +endinstr; + +instr(0x62,11); + sbchl(hl); +endinstr; + +instr(0x63,16); + {unsigned short addr=fetch2(pc); + pc+=2; + store2b(addr,h,l); + } +endinstr; + +instr(0x64,4); + neg; +endinstr; + +instr(0x65,4); + ret; +endinstr; + +instr(0x66,4); + im=0; +endinstr; + +instr(0x67,14); + {unsigned char t=fetch(hl); + unsigned char u=(a<<4)|(t>>4); + a=(a&0xf0)|(t&0x0f); + store(hl,u); + f=(f&1)|(a&0xa8)|((!a)<<6)|parity(a); + } +endinstr; + +instr(0x68,8); + input(l); +endinstr; + +instr(0x69,8); + tstates+=out(tstates,b,c,l); +endinstr; + +instr(0x6a,11); + adchl(hl); +endinstr; + +instr(0x6b,16); + {unsigned short addr=fetch2(pc); + pc+=2; + l=fetch(addr); + h=fetch(addr+1); + } +endinstr; + +instr(0x6c,4); + neg; +endinstr; + +instr(0x6d,4); + ret; +endinstr; + +instr(0x6e,4); + im=1; +endinstr; + +instr(0x6f,5); + {unsigned char t=fetch(hl); + unsigned char u=(a&0x0f)|(t<<4); + a=(a&0xf0)|(t>>4); + store(hl,u); + f=(f&1)|(a&0xa8)|((!a)<<6)|parity(a); + } +endinstr; + +instr(0x70,8); + {unsigned char x;input(x);} +endinstr; + +instr(0x71,8); + tstates+=out(tstates,b,c,0); +endinstr; + +instr(0x72,11); + sbchl(sp); +endinstr; + +instr(0x73,16); + {unsigned short addr=fetch2(pc); + pc+=2; + store2(addr,sp); + } +endinstr; + +instr(0x74,4); + neg; +endinstr; + +instr(0x75,4); + ret; +endinstr; + +instr(0x76,4); + im=2; +endinstr; + +instr(0x78,8); + input(a); +endinstr; + +instr(0x79,8); + tstates+=out(tstates,b,c,a); +endinstr; + +instr(0x7a,11); + adchl(sp); +endinstr; + +instr(0x7b,16); + {unsigned short addr=fetch2(pc); + pc+=2; + sp=fetch2(addr); + } +endinstr; + +instr(0x7c,4); + neg; +endinstr; + +instr(0x7d,4); + ret; +endinstr; + +instr(0x7e,4); + im=3; +endinstr; + +instr(0xa0,12); + {unsigned char x=fetch(hl); + store(de,x); + if(!++l)h++; + if(!++e)d++; + if(!c--)b--; + f=(f&0xc1)|(x&0x28)|(((b|c)>0)<<2); + } +endinstr; + +instr(0xa1,12); + {unsigned char carry=cy; + cpa(fetch(hl)); + if(!++l)h++; + if(!c--)b--; + f=(f&0xfa)|carry|(((b|c)>0)<<2); + } +endinstr; + +instr(0xa2,12); + {unsigned short t=in(tstates,b,c); + store(hl,t); + tstates+=t>>8; + if(!++l)h++; + b--; + f=(b&0xa8)|((b==0)<<6)|2|((parity(b)^c)&4); + } +endinstr; + +instr(0xa3,12); /* I can't determine the correct flags outcome for the + block OUT instructions. Spec says that the carry + flag is left unchanged and N is set to 1, but that + doesn't seem to be the case... */ + {unsigned char x=fetch(hl); + tstates+=out(tstates,b,c,x); + if(!++l)h++; + b--; + f=(f&1)|0x12|(b&0xa8)|((b==0)<<6); + } +endinstr; + +instr(0xa8,12); + {unsigned char x=fetch(hl); + store(de,x); + if(!l--)h--; + if(!e--)d--; + if(!c--)b--; + f=(f&0xc1)|(x&0x28)|(((b|c)>0)<<2); + } +endinstr; + +instr(0xa9,12); + {unsigned char carry=cy; + cpa(fetch(hl)); + if(!l--)h--; + if(!c--)b--; + f=(f&0xfa)|carry|(((b|c)>0)<<2); + } +endinstr; + +instr(0xaa,12); + {unsigned short t=in(tstates,b,c); + store(hl,t); + tstates+=t>>8; + if(!l--)h--; + b--; + f=(b&0xa8)|((b==0)<<6)|2|((parity(b)^c^4)&4); + } +endinstr; + +instr(0xab,12); + {unsigned char x=fetch(hl); + tstates+=out(tstates,b,c,x); + if(!l--)h--; + b--; + f=(f&1)|0x12|(b&0xa8)|((b==0)<<6); + } +endinstr; + +/* Note: the Z80 implements "*R" as "*" followed by JR -2. No reason + to change this... */ + +instr(0xb0,12); + {unsigned char x=fetch(hl); + store(de,x); + if(!++l)h++; + if(!++e)d++; + if(!c--)b--; + f=(f&0xc1)|(x&0x28)|(((b|c)>0)<<2); + if(b|c)pc-=2,tstates+=5; + } +endinstr; + +instr(0xb1,12); + {unsigned char carry=cy; + cpa(fetch(hl)); + if(!++l)h++; + if(!c--)b--; + f=(f&0xfa)|carry|(((b|c)>0)<<2); + if((f&0x44)==4)pc-=2,tstates+=5; + } +endinstr; + +instr(0xb2,12); + {unsigned short t=in(tstates,b,c); + store(hl,t); + tstates+=t>>8; + if(!++l)h++; + b--; + f=(b&0xa8)|((b==0)<<6)|2|((parity(b)^c)&4); + if(b)pc-=2,tstates+=5; + } +endinstr; + +instr(0xb3,12); + {unsigned char x=fetch(hl); + tstates+=out(tstates,b,c,x); + if(!++l)h++; + b--; + f=(f&1)|0x12|(b&0xa8)|((b==0)<<6); + if(b)pc-=2,tstates+=5; + } +endinstr; + +instr(0xb8,12); + {unsigned char x=fetch(hl); + store(de,x); + if(!l--)h--; + if(!e--)d--; + if(!c--)b--; + f=(f&0xc1)|(x&0x28)|(((b|c)>0)<<2); + if(b|c)pc-=2,tstates+=5; + } +endinstr; + +instr(0xb9,12); + {unsigned char carry=cy; + cpa(fetch(hl)); + if(!l--)h--; + if(!c--)b--; + f=(f&0xfa)|carry|(((b|c)>0)<<2); + if((f&0x44)==4)pc-=2,tstates+=5; + } +endinstr; + +instr(0xba,12); + {unsigned short t=in(tstates,b,c); + store(hl,t); + tstates+=t>>8; + if(!l--)h--; + b--; + f=(b&0xa8)|((b==0)<<6)|2|((parity(b)^c^4)&4); + if(b)pc-=2,tstates+=5; + } +endinstr; + +instr(0xbb,12); + {unsigned char x=fetch(hl); + tstates+=out(tstates,b,c,x); + if(!l--)h--; + b--; + f=(f&1)|0x12|(b&0xa8)|((b==0)<<6); + if(b)pc-=2,tstates+=5; + } +endinstr; + +/* XZ80 Pseudo-ops +instr(0xfb,4); + multiloader(hl,a); +endinstr; + +instr(0xfc,4); + { int ans; + stopwatch(); + ans=loader(pc,ix,de,a,cy); + if(ans>=0){ + if(ans==1)pc=0x0806; + else if(ans==2)f&=254; + else f|=1; + ix+=de; + d=e=0; + if(ans!=1)ret; + } + startwatch(1); + } +endinstr; + +instr(0xfd,4); + { int ans; + stopwatch(); + ans=saver(pc,ix,de,a); + if(ans>0)pc=0x0806; + else if(ans==0){ + ix+=de; + d=e=0; + ret; + } + startwatch(1); + } +endinstr; +*/ + +/* ZXCC pseudo-op */ +instr(0xfe, 4); +{ + /* Create copies of the registers here so we can take their addresses + * and not lose register optimisation in the rest of the CPU code */ + byte xa,xb,xc,xd,xe,xf,xxh,xxl; + word xp,xx,xy; + + xa = a; xb = b; xc = c; xd = d; xe = e; xf = f; xxh = h; xxl = l; + xp = pc; xx = ix; xy = iy; + + ed_fe(&xa,&xb,&xc,&xd,&xe,&xf,&xxh,&xxl,&xp,&xx,&xy); + + a = xa; b = xb; c = xc; d = xd; e = xe; f = xf; h = xxh; l = xxl; + pc = xp; ix = xx; iy = xy; +} +endinstr; + +default: tstates+=4; + +}} diff --git a/Tools/unix/zx/readme.txt b/Tools/unix/zx/readme.txt new file mode 100644 index 00000000..31c3e320 --- /dev/null +++ b/Tools/unix/zx/readme.txt @@ -0,0 +1,47 @@ +ZX Command + +An adaptation of zxcc-0.5.6 by Wayne Warthen + +This directory contains the source files used to build the "zx" tool. This tool +is essentially just John Elliott's zxcc package version zxcc-0.5.6 modified to +build for Windows and simplified down to just a single command (zx) +which is essentially just the zxcc command. + +Please see http://www.seasip.info/Unix/Zxcc/ for more information on zxcc. + +To build under Open Watcom or Microsoft Visual C++, use the following command: + + cl /Fe"zx.exe" zx.c cpmdrv.c cpmglob.c cpmparse.c cpmredir.c drdos.c util.c xlt.c zxbdos.c zxcbdos.c zxdbdos.c z80.c dirent.c + +To build a debug version, use the following command: + + cl /DDEBUG /Fe"zxdbg.exe" zx.c cpmdrv.c cpmglob.c cpmparse.c cpmredir.c drdos.c util.c xlt.c zxbdos.c zxcbdos.c zxdbdos.c z80.c dirent.c + +WARNING: There seems to be a rare scenario that breaks zx under the Open Watcom build. CP/M allows a file to be accessed +under multiple FCB's without an error. Open Watcom will see this as an error. At present, the only tool I know of that does +this is M80. + +December 5, 2014 + +After struggling to get the entire zxcc package to build nicely using autoconf, +I finally gave up and took a much more direct approach. I have extracted just +the source files needed and created a simple batch file to build the tool. I +realize this could be done much better, but I cheated in the interest of time. + +The one "real" change I made in the source code was that I modified the tool +to look for bios.bin in the same directory as the executable is in. This +just makes it much easier to set up (for me, anyway). + +The GPL status of everything remains in place and carries forward. + +Wayne Warthen +wwarthen@gmail.com + +March 15, 2017 + +- Updated to compile under Open Watcom. +- Implemented BDOS console status function. +- Set stdin and stdout to binary mode at startup. + +Wayne Warthen +wwarthen@gmail.com \ No newline at end of file diff --git a/Tools/unix/zx/util.c b/Tools/unix/zx/util.c new file mode 100644 index 00000000..21d29728 --- /dev/null +++ b/Tools/unix/zx/util.c @@ -0,0 +1,378 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file holds miscellaneous utility functions. +*/ + +#include "cpmint.h" +#include + +/* In debug mode, lseek()s can be traced. */ + +#ifdef DEBUG + +long zxlseek(int fd, long offset, int wh) +{ +#ifdef WIN32 + long v; + redir_Msg(">SetFilePointer() Handle=%lu, Offset=%lu, Method=%lu\n", fd, offset, wh); + v = SetFilePointer((HANDLE)fd, offset, NULL, wh); + redir_Msg("= 0) return v; + + redir_Msg("lseek fails with errno = %d\n", errno); + if (errno == EBADF) redir_Msg(" (bad file descriptor %d)\n", fd); + if (errno == ESPIPE) redir_Msg(" (file %d is a pipe)\n", fd); + if (errno == EINVAL) redir_Msg(" (bad parameter %d)\n", wh); + + return -1; +#endif +} + +void redir_showfcb(cpm_byte *fd) +{ + int n; + + for (n = 0; n < 32; n++) + { + if (!n || n>= 12) printf("%02x ", fd[n]); + else printf("%c", fd[n] & 0x7F); + } + printf("\r\n"); +} + +#else + +long zxlseek(int fd, long offset, int wh) +{ +#ifdef WIN32 + return SetFilePointer((HANDLE)fd, offset, NULL, wh); +#else + return lseek(fd, offset, wh); +#endif +} + + +#endif + +/* Get the "sequential access" file pointer out of an FCB */ + +long redir_get_fcb_pos(cpm_byte *fcb) +{ + long npos; + + npos = 524288L * fcb[0x0E]; /* S2 */ + npos += 16384L * fcb[0x0C]; /* Extent */ + npos += 128L * fcb[0x20]; /* Record */ + + return npos; +} + +void redir_put_fcb_pos(cpm_byte *fcb, long npos) +{ + fcb[0x20] = (npos / 128) % 128; + fcb[0x0C] = (npos / 16384) % 32; + fcb[0x0E] = (npos / 524288L) % 64; +} + + +/* + * find a filename that works. + * note that this is where we handle the case sensitivity/non-case sensitivity + * horror. + * the name that is passed in should be in lower case. + * we'll modify it to the first one that matches + */ +void +swizzle(char *fullpath) +{ + struct stat ss; + char *slash; + DIR *dirp; + struct dirent *dentry; + + /* short circuit if ok */ + if (stat(fullpath, &ss) == 0) { + return; + } + + slash = rindex(fullpath, '/'); + if (!slash) { + return; + } + *slash = '\0'; + dirp = opendir(fullpath); + *slash = '/'; + while ((dentry = readdir(dirp)) != NULL) { + if (strcasecmp(dentry->d_name, slash + 1) == 0) { + strcpy(slash + 1, dentry->d_name); + break; + } + } + closedir(dirp); +} + +/* + * Passed a CP/M FCB, convert it to a unix filename. Turn its drive back into + * a path. + */ + +int redir_fcb2unix(cpm_byte *fcb, char *fname) +{ + int n, q, drv, ddrv; + char s[2]; + + s[1] = 0; + q = 0; + drv = fcb[0] & 0x7F; + if (drv == '?') drv = 0; + + ddrv = fcb[0] & 0x7F; + if (ddrv < 0x1F) ddrv += '@'; + + redir_Msg("%c:%-8.8s.%-3.3s\n", + ddrv, + fcb + 1, + fcb + 9); + + if (!drv) strcpy(fname, redir_drive_prefix[redir_cpmdrive]); + else strcpy(fname, redir_drive_prefix[drv - 1]); + + for (n = 1; n < 12; n++) + { + s[0] = (fcb[n] & 0x7F); + if (s[0] == '?') q = 1; + if (isupper(s[0])) s[0] = tolower(s[0]); + if (s[0] != ' ') + { + if (n == 9) strcat(fname, "."); + strcat(fname, s); + } + } + return q; +} + +#ifndef EROFS /* Open fails because of read-only FS */ +#define EROFS EACCES +#endif + +int redir_ofile(cpm_byte *fcb, char *s) +{ + int h, rv; + + /* Software write-protection */ +#ifdef WIN32 + redir_Msg(">CreateFile([OPEN_EXISTING]) Name='%s'\n", s); + h = (int)CreateFile(s, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + redir_Msg("= 0 || (errno != EACCES && errno != EROFS)) return h; + } + redir_Msg("**2**"); + h = open(s, O_RDONLY | O_BINARY); + if (h < 0) return -1; + fcb[9] |= 0x80; + #endif +#endif + + return h; +} + + +/* Extract a file handle from where it was stored in an FCB by fcb_open() + or fcb_creat(). Aborts if the FCB has been tampered with. + + Note: Some programs (like GENCOM) close FCBs they never opened. This causes + the Corrupt FCB message, but no harm seems to ensue. */ + +int redir_verify_fcb(cpm_byte *fcb) +{ + if (fcb[16] != 0xFD || fcb[17] != 0x00) + { + fprintf(stderr,"cpmredir: Corrupt FCB\n"); + return -1; + } + return (int)(redir_rd32(fcb + 18)); + +} + +/* Print a trace message */ + +#ifdef DEBUG + +void redir_Msg(char *s, ...) +{ + va_list ap; + + va_start(ap, s); + printf("cpmredir trace: "); + vprintf(s, ap); + va_end(ap); + fflush(stdout); +} + +#endif + +#define BCD(x) (((x % 10)+16*(x/10)) & 0xFF) + +/* Convert time_t to CP/M day count/hours/minutes */ +dword redir_cpmtime(time_t t) +{ + long d = (t / 86400) - 2921; /* CP/M day 0 is unix day 2921 */ + long h = (t % 86400) / 3600; /* Hour, 0-23 */ + long m = (t % 3600) / 60; /* Minute, 0-59 */ + + return (d | (BCD(h) << 16) | (BCD(m) << 24)); +} + +#undef BCD + +#define UNBCD(x) (((x % 16) + 10 * (x / 16)) & 0xFF) + +time_t redir_unixtime(cpm_byte *c) +{ + time_t t; + cpm_word days; + + days = (c[0] + 256 * c[1]) + 2921; + + t = 60L * UNBCD(c[3]); + t += 3600L * UNBCD(c[2]); + t += 86400L * days; + + return t; +} + +#undef UNBCD + + +/* Functions to access 24-bit & 32-bit words in memory. These are always + little-endian. */ + +void redir_wr24(cpm_byte *addr, dword v) +{ + addr[0] = v & 0xFF; + addr[1] = (v >> 8) & 0xFF; + addr[2] = (v >> 16) & 0xFF; +} + +void redir_wr32(cpm_byte *addr, dword v) +{ + addr[0] = v & 0xFF; + addr[1] = (v >> 8) & 0xFF; + addr[2] = (v >> 16) & 0xFF; + addr[3] = (v >> 24) & 0xFF; +} + +dword redir_rd24(cpm_byte *addr) +{ + register dword rv = addr[2]; + + rv = (rv << 8) | addr[1]; + rv = (rv << 8) | addr[0]; + return rv; +} + + +dword redir_rd32(cpm_byte *addr) +{ + register dword rv = addr[3]; + + rv = (rv << 8) | addr[2]; + rv = (rv << 8) | addr[1]; + rv = (rv << 8) | addr[0]; + return rv; +} + + +void redir_log_drv(cpm_byte drv) +{ + if (!drv) redir_l_drives |= 1; + else redir_l_drives |= (1L << drv); +} + +void redir_log_fcb(cpm_byte *fcb) +{ + int drv = fcb[0] & 0x7F; + + if (drv && drv != '?') redir_log_drv(drv - 1); + else redir_log_drv(redir_cpmdrive); +} + + +int redir_ro_drv(cpm_byte drv) +{ + if (!drv) return redir_ro_drives & 1; + else return redir_ro_drives & (1L << drv); +} + +int redir_ro_fcb(cpm_byte *fcb) +{ + int drv = fcb[0] & 0x7F; + + if (drv && drv != '?') return redir_ro_drv(drv - 1); + else return redir_ro_drv(redir_cpmdrive); +} + + + +cpm_word redir_xlt_err(void) +{ + if (redir_password_error()) return 0x7FF; /* DRDOS pwd error */ + switch(errno) + { + case EISDIR: + case EBADF: return 9; /* Bad FCB */ + case EINVAL: return 0x03FF; /* Readonly file */ + case EPIPE: return 0x01FF; /* Broken pipe */ + case ENOSPC: return 1; /* No space */ + default: return 0xFF; /* Software error */ + } +} + diff --git a/Tools/unix/zx/xlt.c b/Tools/unix/zx/xlt.c new file mode 100644 index 00000000..f0ba1da5 --- /dev/null +++ b/Tools/unix/zx/xlt.c @@ -0,0 +1,191 @@ +/* + + CPMREDIR: CP/M filesystem redirector + Copyright (C) 1998, John Elliott + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + This file holds functions dealing with name translation; also the + initialisation code. +*/ + +#include "cpmint.h" + +/* Detect DRDOS */ + +#ifdef __MSDOS__ +static void drdos_init(void) +{ + +/* The DJGPP DOS extender won't detect DRDOS using intdos(), so we have + to use __dpmi_int() instead. */ + +#ifdef __GO32__ + __dpmi_regs ir; + + ir.x.ax = 0x4452; /* "DR" */ + + __dpmi_int(0x21, &ir); + if (ir.x.flags & 1) return; /* Not DRDOS */ + + redir_Msg("DRDOS detected.\r\n"); + + redir_drdos = 1; + +#else /* __GO32__ */ + + union REGS ir, or; + + ir.w.ax = 0x4452; /* "DR" */ + + intdos(&ir, &or); + if (or.w.cflag) return; /* Not DRDOS */ + + redir_Msg("DRDOS detected.\r\n"); + + redir_drdos = 1; +#endif /* __GO32__ */ +} +#endif /* __MSDOS__ */ + + + +int fcb_init(void) +{ + int n; + + /* A: to O: free */ + for (n = 0; n < 15; n++) redir_drive_prefix[n][0] = 0; + + strcpy(redir_drive_prefix[15], "./"); /* P: is current directory */ + + /* Log on to P:. It is the only drive at this point which we + * know works. */ + redir_cpmdrive = 15; +#ifdef __MSDOS__ + drdos_init(); +#endif + + return 1; +} + +/* Deinitialise the library. */ + +void fcb_deinit(void) +{ + /* Nothing */ +} + +/* Translate a name from the host FS to a CP/M name. This will (if necessary) + * create a mapping between a CP/M drive and a host directory path. + * + * CP/M drives A: to O: can be mapped in this way. P: is always the current + * drive. + * + */ + +void xlt_name(char *localname, char *cpmname) +{ + char ibuf[CPM_MAXPATH + 1]; + char nbuf[CPM_MAXPATH + 1]; + char *pname; + int n; + + sprintf(ibuf, "%-.*s", CPM_MAXPATH, localname); + pname = strrchr(ibuf, '/'); +#ifdef __MSDOS__ + if (!pname) pname = strrchr(ibuf,'\\'); + if (!pname) pname = strrchr(ibuf,':'); +#endif + if (!pname) /* No path separators in the name. It is therefore a + local filename, so map it to drive P: */ + { + strcpy(cpmname, "p:"); + strcat(cpmname, ibuf); + return; + } + ++pname; + strcpy(nbuf, pname); /* nbuf holds filename component */ + *pname = 0; /* ibuf holds path component */ + + /* See if the path is one of those already mapped to drives */ + + for (n = 0; n < 15; n++) + { + if (redir_drive_prefix[n][0] && !strcmp(ibuf, redir_drive_prefix[n])) + { + sprintf(cpmname,"%c:%s", n + 'a', nbuf); + return; + } + } + + /* It is not, see if another drive can be allocated */ + + for (n = 0; n < 15; n++) if (!redir_drive_prefix[n][0]) + { + strcpy(redir_drive_prefix[n], ibuf); + sprintf(cpmname,"%c:%s", n + 'a', nbuf); + return; + } + + /* No other drive can be allocated */ + + strcpy(cpmname,"p:"); + strcat(cpmname, nbuf); +} + +/* It is sometimes convenient to set some fixed mappings. This will create + * a mapping for a given directory. + * Pass drive = -1 for "first available", or 0-15 for A: to P: + */ + +int xlt_map(int drive, char *localdir) +{ + int n; + + if (drive == -1) + { + for (n = 0; n < 15; n++) if (!redir_drive_prefix[n][0]) + { + drive = n; + break; + } + if (drive == -1) return 0; /* No space for mappings */ + } + if (redir_drive_prefix[drive][0]) return 0; /* Drive taken */ + + sprintf(redir_drive_prefix[drive], "%-.*s", CPM_MAXPATH, localdir); + return 1; +} + + +/* Unmap a drive + */ + +int xlt_umap(int drive) +{ + if (!redir_drive_prefix[drive][0]) return 0; /* Drive not taken */ + redir_drive_prefix[drive][0] = 0; + return 1; +} + + +char *xlt_getcwd(int drive) +{ + if (drive < 0 || drive > 16) return ""; + + return redir_drive_prefix[drive]; +} + diff --git a/Tools/unix/zx/z80.c b/Tools/unix/zx/z80.c new file mode 100644 index 00000000..d1d99cd5 --- /dev/null +++ b/Tools/unix/zx/z80.c @@ -0,0 +1,270 @@ +/* Emulation of the Z80 CPU with hooks into the other parts of xz80. + * Copyright (C) 1994 Ian Collier. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include "zx.h" + +#define parity(a) (partable[a]) + +unsigned char partable[256]={ + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0, + 4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4 + }; + +#ifdef DEBUG +static unsigned short breakpoint=0; +static unsigned int breaks=0; + +//static void inline log(fp,name,val) +static void log(fp,name,val) +FILE *fp; +char *name; +unsigned short val; +{ + int i; + fprintf(fp,"%s=%04X ",name,val); + for(i=0;i<8;i++,val++)fprintf(fp," %02X",fetch(val)); + putc('\n',fp); +} +#endif + +void mainloop(word spc, word ssp){ + register unsigned char a, f, b, c, d, e, h, l; + unsigned char r, a1, f1, b1, c1, d1, e1, h1, l1, i, iff1, iff2, im; + register unsigned short pc; + unsigned short ix, iy, sp; + register unsigned long tstates; + register unsigned int radjust; + register unsigned char ixoriy, new_ixoriy; + unsigned char intsample; + register unsigned char op; +#ifdef DEBUG + char flags[9]; + int bit; + FILE *fp=0; + register unsigned short af2=0,bc2=0,de2=0,hl2=0,ix2=0,iy2=0,sp2=0; + register unsigned char i2=0; + //unsigned char *memory=memptr[0]; + struct _next {unsigned char bytes[8];} *next; + unsigned short BC, DE, HL, AF; + + fputs("Press F11 to log\n",stderr); +#endif + a=f=b=c=d=e=h=l=a1=f1=b1=c1=d1=e1=h1=l1=i=r=iff1=iff2=im=0; + ixoriy=new_ixoriy=0; + ix=iy=0; + pc=spc; + sp=ssp; + tstates=radjust=0; + while(1){ + ixoriy=new_ixoriy; + new_ixoriy=0; +#ifdef DEBUG + next=(struct _next *)&fetch(pc); + BC=bc;DE=de;HL=hl;AF=(a<<8)|f; + if(fp && !ixoriy){ + log(fp,"pc",pc); + if(sp!=sp2)log(fp,"sp",sp2=sp); + if(iy!=iy2)log(fp,"iy",iy2=iy); + if(ix!=ix2)log(fp,"ix",ix2=ix); + if(hl!=hl2)log(fp,"hl",hl2=hl); + if(de!=de2)log(fp,"de",de2=de); + if(bc!=bc2)log(fp,"bc",bc2=bc); + if(((a<<8)|f)!=af2){ + af2=(a<<8)|f; + strcpy(flags,"SZ H VNC"); + for(bit=0;bit<8;bit++)if(!(f&(1<<(7-bit))))flags[bit]=' '; + fprintf(fp,"af=%04X %s\n",af2,flags); + } + if(i!=i2)fprintf(fp,"ir=%02X%02X\n",i2=i,r); + putc('\n',fp); + } + if(pc==breakpoint && pc) + breaks++; /* some code at which to set a breakpoint */ + a=AF>>8; f=AF; h=HL>>8; l=HL; d=DE>>8; e=DE; b=BC>>8; c=BC; +#endif +/* +{ + static int tr = 0; + static int id = 0; +// static byte b = 0; +// + if (pc == 0x1177) tr = 1; + if (pc == 0x1185) tr = 0; + if (tr >= 1) ++id; + if (tr >= 1) printf("%d: PC=%04x %02x AF=%02x:%02x BC=%04x DE=%04x HL=%04x IX=%04x IY=%04x\r\n", + id, pc, fetch(pc), a,f, bc, de, hl, ix, iy); +} +*/ + intsample=1; + op=fetch(pc); + pc++; + radjust++; + switch(op){ +#include "z80ops.h" + } +/*** + * ZXCC doesn't do interrupts at all, so all this is commented out + if(tstates>=int_cycles && intsample){ + tstates-=int_cycles; + frames++; + // Carry out X-related tasks (including waiting for timer + // signal if necessary) + switch(interrupt()){ + case Z80_quit: +#ifdef DEBUG + if(fp)fclose(fp); +#endif + return; + case Z80_NMI: + if(fetch(pc)==0x76)pc++; + iff2=iff1; + iff1=0; + // The Z80 performs a machine fetch cycle for 5 Tstates + // but ignores the result. It takes a further 10 Tstates + // to jump to the NMI routine at 0x66. + tstates+=15; + push2(pc); + pc=0x66; + break; + case Z80_reset: + a=f=b=c=d=e=h=l=a1=f1=b1=c1=d1=e1= + h1=l1=i=r=iff1=iff2=im=0; + ix=iy=sp=pc=0; + radjust=0; + break; +#ifdef DEBUG + case Z80_log: + if(fp){ + fclose(fp); + fp=0; + fputs("Logging turned off\n",stderr); + } else { + fp=fopen(config.log,"a"); + if(fp)fprintf(stderr,"Logging to file %s\n",config.log); + else perror(config.log); + } + break; +#endif + + case Z80_load: + stopwatch(); + if(snapload()){ + a=snapa; + f=snapf; + b=snapb; + c=snapc; + d=snapd; + e=snape; + h=snaph; + l=snapl; + a1=snapa1; + f1=snapf1; + b1=snapb1; + c1=snapc1; + d1=snapd1; + e1=snape1; + h1=snaph1; + l1=snapl1; + iff1=snapiff1; + iff2=snapiff2; + i=snapi; + r=snapr; + radjust=r; + im=snapim; + ix=snapix; + iy=snapiy; + sp=snapsp; + pc=snappc; + } + startwatch(1); + break; + case Z80_save: + r=(r&0x80)|(radjust&0x7f); + snapa=a; + snapf=f; + snapb=b; + snapc=c; + snapd=d; + snape=e; + snaph=h; + snapl=l; + snapa1=a1; + snapf1=f1; + snapb1=b1; + snapc1=c1; + snapd1=d1; + snape1=e1; + snaph1=h1; + snapl1=l1; + snapiff1=iff1; + snapiff2=iff2; + snapi=i; + snapr=r; + snapim=im; + snapix=ix; + snapiy=iy; + snapsp=sp; + snappc=pc; + snapsave(); + startwatch(1); + break; + + } + if(iff1){ +#ifdef DEBUG + if(fp)fprintf(fp,"Interrupt (im=%d)\n\n",im); +#endif + if(fetch(pc)==0x76)pc++; + iff1=iff2=0; + tstates+=5; // accompanied by an input from the data bus // + switch(im){ + case 0: // IM 0 // + case 1: // undocumented // + case 2: // IM 1 // + // there is little to distinguish between these cases // + tstates+=8; + push2(pc); + pc=0x38; + break; + case 3: // IM 2 // + tstates+=14; + { + int addr=fetch2((i<<8)|0xff); + push2(pc); + pc=addr; + } + } + } + }*/ + } +} diff --git a/Tools/unix/zx/z80.h b/Tools/unix/zx/z80.h new file mode 100644 index 00000000..89e12a84 --- /dev/null +++ b/Tools/unix/zx/z80.h @@ -0,0 +1,86 @@ +/* Miscellaneous definitions for xz80, copyright (C) 1994 Ian Collier. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* [John Elliott, 15 July 2001] + * Copied this file into ZXCC, a CP/M emulator. + * Since ZXCC's memory is a flat 64k space and will never be bank-switched, + * the bank-switching code is removed. + * Since ZXCC has no memory-mapped screen, all the screen management code + * goes as well. + * Since ZXCC doesn't need its speed regulated, all the speed regulation + * code goes as well. + * Since ZXCC doesn't save or load snapshots... OK, you get the idea. + */ + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#define Z80_quit 1 +#define Z80_NMI 2 +#define Z80_reset 3 +#define Z80_load 4 +#define Z80_save 5 +#define Z80_log 6 + +unsigned int in(); +unsigned int out(); +//int interrupt(); +int snapload(); +void snapsave(); +void mainloop(word xpc, word xsp); +void eachframe(); +void itimeron(); +void itimeroff(); +void startwatch(); +unsigned long stopwatch(); +void requester(); +int loader(); +int saver(); +void multiloader(); +void usage(); +void version(); +void drawborder(); + +#define fetch(x) (RAM[x]) +#define fetch2(x) ((fetch((x)+1)<<8)|fetch(x)) + +#define store(x,y) do { RAM[(x)] = (y); } while(0) + +#define store2b(x,hi,lo) do {\ + RAM[(x)]=(lo); \ + RAM[((x+1) & 0xFFFF)]=(hi); } while(0) + +#define store2(x,y) store2b(x,(y)>>8,y) + +#ifdef __GNUC__ +static void inline storefunc(unsigned short ad,unsigned char b){ + store(ad,b); +} +#undef store +#define store(x,y) storefunc(x,y) + +static void inline store2func(unsigned short ad,unsigned char b1,unsigned char b2){ + store2b(ad,b1,b2); +} +#undef store2b +#define store2b(x,hi,lo) store2func(x,hi,lo) +#endif + +#define bc ((b<<8)|c) +#define de ((d<<8)|e) +#define hl ((h<<8)|l) diff --git a/Tools/unix/zx/z80ops.h b/Tools/unix/zx/z80ops.h new file mode 100644 index 00000000..a9687741 --- /dev/null +++ b/Tools/unix/zx/z80ops.h @@ -0,0 +1,1304 @@ +/* Emulations of the Z80 CPU instruction set - part of xz80. + * Copyright (C) 1994 Ian Collier. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#define instr(opcode,cycles) case opcode: {tstates+=cycles +#define HLinstr(opcode,cycles,morecycles) \ + case opcode: {unsigned short addr; \ + tstates+=cycles; \ + if(ixoriy==0)addr=hl; \ + else tstates+=morecycles, \ + addr=(ixoriy==1?ix:iy)+ \ + (signed char)fetch(pc),\ + pc++ +#define endinstr }; break + +#define cy (f&1) + +#define xh (ixoriy==0?h:ixoriy==1?(ix>>8):(iy>>8)) +#define xl (ixoriy==0?l:ixoriy==1?(ix&0xff):(iy&0xff)) + +#define setxh(x) (ixoriy==0?(h=(x)):ixoriy==1?(ix=(ix&0xff)|((x)<<8)):\ + (iy=(iy&0xff)|((x)<<8))) +#define setxl(x) (ixoriy==0?(l=(x)):ixoriy==1?(ix=(ix&0xff00)|(x)):\ + (iy=(iy&0xff00)|(x))) + +#define inc(var) /* 8-bit increment */ ( var++,\ + f=(f&1)|(var&0xa8)|\ + ((!(var&15))<<4)|((!var)<<6)|\ + ((var==128)<<2)\ + ) +#define dec(var) /* 8-bit decrement */ ( f=(f&1)|((!(var&15))<<4)|2,\ + --var,\ + f|=(var&0xa8)|((var==127)<<2)|\ + ((!var)<<6)\ + ) +#define swap(x,y) {unsigned char t=x; x=y; y=t;} +#define addhl(hi,lo) /* 16-bit add */ if(!ixoriy){\ + unsigned short t;\ + l=t=l+(lo);\ + f=(f&0xc4)|(((t>>=8)+(h&0x0f)+((hi)&0x0f)>15)<<4);\ + h=t+=h+(hi);\ + f|=(h&0x28)|(t>>8);\ + }\ + else do{unsigned long t=(ixoriy==1?ix:iy);\ + f=(f&0xc4)|(((t&0xfff)+((hi<<8)|lo)>0xfff)<<4);\ + t+=(hi<<8)|lo;\ + if(ixoriy==1)ix=t; else iy=t;\ + f|=((t>>8)&0x28)|(t>>16);\ + } while(0) +#define adda(x,c) /* 8-bit add */ do{unsigned short y;\ + unsigned char z=(x);\ + y=a+z+(c);\ + f=(y&0xa8)|(y>>8)|(((a&0x0f)+(z&0x0f)+(c)>15)<<4)|\ + (((~a^z)&0x80&(y^a))>>5);\ + f|=(!(a=y))<<6;\ + } while(0) +#define suba(x,c) /* 8-bit subtract */ do{unsigned short y;\ + unsigned char z=(x);\ + y=(a-z-(c))&0x1ff;\ + f=(y&0xa8)|(y>>8)|(((a&0x0f)<(z&0x0f)+(c))<<4)|\ + (((a^z)&0x80&(y^a))>>5)|2;\ + f|=(!(a=y))<<6;\ + } while(0) +#define cpa(x) /* 8-bit compare */ do{unsigned short y;\ + unsigned char z=(x);\ + y=(a-z)&0x1ff;\ + f=(y&0xa8)|(y>>8)|(((a&0x0f)<(z&0x0f))<<4)|\ + (((a^z)&0x80&(y^a))>>5)|2|((!y)<<6);\ + } while(0) +#define anda(x) /* logical and */ do{\ + a&=(x);\ + f=(a&0xa8)|((!a)<<6)|0x10|parity(a);\ + } while(0) +#define xora(x) /* logical xor */ do{\ + a^=(x);\ + f=(a&0xa8)|((!a)<<6)|parity(a);\ + } while(0) +#define ora(x) /* logical or */ do{\ + a|=(x);\ + f=(a&0xa8)|((!a)<<6)|parity(a);\ + } while(0) + +#define jr /* execute relative jump */ do{int j=(signed char)fetch(pc);\ + pc+=j+1;\ + tstates+=5;\ + } while(0) +#define jp /* execute jump */ (pc=fetch2(pc)) +#define call /* execute call */ do{\ + tstates+=7;\ + push2(pc+2);\ + jp;\ + } while(0) +#define ret /* execute return */ do{\ + tstates+=6;\ + pop2(pc);\ + } while(0) +#define pop2(var) /* pop 16-bit register */ (var=fetch2(sp),sp+=2) +#define pop1(v1,v2) /* pop register pair */ (v2=fetch(sp),\ + v1=fetch(sp+1),sp+=2) +#define push2(val) /* push 16-bit register */ do{sp-=2;store2(sp,(val));}\ + while(0) +#define push1(v1,v2) /* push register pair */ do{sp-=2;\ + store2b(sp,v1,v2);\ + }while(0) + +instr(0,4); + /* nop */ +endinstr; + +instr(1,10); + c=fetch(pc),pc++; + b=fetch(pc),pc++; +endinstr; + +instr(2,7); + store(bc,a); +endinstr; + +instr(3,6); + if(!++c)b++; +endinstr; + +instr(4,4); + inc(b); +endinstr; + +instr(5,4); + dec(b); +endinstr; + +instr(6,7); + b=fetch(pc),pc++; +endinstr; + +instr(7,4); + a=(a<<1)|(a>>7); + f=(f&0xc4)|(a&0x29); +endinstr; + +instr(8,4); + swap(a,a1); + swap(f,f1); +endinstr; + +instr(9,11); + addhl(b,c); +endinstr; + +instr(10,7); + a=fetch(bc); +endinstr; + +instr(11,6); + if(!c--)b--; +endinstr; + +instr(12,4); + inc(c); +endinstr; + +instr(13,4); + dec(c); +endinstr; + +instr(14,7); + c=fetch(pc),pc++; +endinstr; + +instr(15,4); + f=(f&0xc4)|(a&1); + a=(a>>1)|(a<<7); + f|=a&0x28; +endinstr; + +instr(16,8); + if(!--b)pc++; + else jr; +endinstr; + +instr(17,10); + e=fetch(pc),pc++; + d=fetch(pc),pc++; +endinstr; + +instr(18,7); + store(de,a); +endinstr; + +instr(19,6); + if(!++e)d++; +endinstr; + +instr(20,4); + inc(d); +endinstr; + +instr(21,4); + dec(d); +endinstr; + +instr(22,7); + d=fetch(pc),pc++; +endinstr; + +instr(23,4); + {int t=a>>7; + a=(a<<1)|(f&1); + f=(f&0xc4)|(a&0x28)|t; + } +endinstr; + +instr(24,7); + jr; +endinstr; + +instr(25,11); + addhl(d,e); +endinstr; + +instr(26,7); + a=fetch(de); +endinstr; + +instr(27,6); + if(!e--)d--; +endinstr; + +instr(28,4); + inc(e); +endinstr; + +instr(29,4); + dec(e); +endinstr; + +instr(30,7); + e=fetch(pc),pc++; +endinstr; + +instr(31,4); + {int t=a&1; + a=(a>>1)|(f<<7); + f=(f&0xc4)|(a&0x28)|t; + } +endinstr; + +instr(32,7); + if(f&0x40)pc++; + else jr; +endinstr; + +instr(33,10); + if(!ixoriy){ + l=fetch(pc),pc++; + h=fetch(pc),pc++; + } + else { + if(ixoriy==1)ix=fetch2(pc); + else iy=fetch2(pc); + pc+=2; + } +endinstr; + +instr(34,16); + {unsigned short addr=fetch2(pc); + pc+=2; + if(!ixoriy)store2b(addr,h,l); + else if(ixoriy==1)store2(addr,ix); + else store2(addr,iy); + } +endinstr; + +instr(35,6); + if(!ixoriy){if(!++l)h++;} + else if(ixoriy==1)ix++; + else iy++; +endinstr; + +instr(36,4); + if(ixoriy==0)inc(h); + else{unsigned char t; + t=(ixoriy==1?ix:iy)>>8; + inc(t); + if(ixoriy==1)ix=(ix&0xff)|(t<<8); + else iy=(iy&0xff)|(t<<8); + } +endinstr; + +instr(37,4); + if(ixoriy==0)dec(h); + else{unsigned char t; + t=(ixoriy==1?ix:iy)>>8; + dec(t); + if(ixoriy==1)ix=(ix&0xff)|(t<<8); + else iy=(iy&0xff)|(t<<8); + } +endinstr; + +instr(38,7); + setxh(fetch(pc)); + pc++; +endinstr; + +instr(39,4); + { + unsigned char incr=0, carry=cy; + if((f&0x10) || (a&0x0f)>9) incr=6; + if((f&1) || (a>>4)>9) incr|=0x60; + if(f&2)suba(incr,0); + else { + if(a>0x90 && (a&15)>9)incr|=0x60; + adda(incr,0); + } + f=((f|carry)&0xfb)|parity(a); + } +endinstr; + +instr(40,7); + if(f&0x40)jr; + else pc++; +endinstr; + +instr(41,11); + if(!ixoriy)addhl(h,l); + else if(ixoriy==1)addhl((ix>>8),(ix&0xff)); + else addhl((iy>>8),(iy&0xff)); +endinstr; + +instr(42,16); + {unsigned short addr=fetch2(pc); + pc+=2; + if(!ixoriy){ + l=fetch(addr); + h=fetch(addr+1); + } + else if(ixoriy==1)ix=fetch2(addr); + else iy=fetch2(addr); + } +endinstr; + +instr(43,6); + if(!ixoriy){if(!l--)h--;} + else if(ixoriy==1)ix--; + else iy--; +endinstr; + +instr(44,4); + if(!ixoriy)inc(l); + else {unsigned char t; + t=(ixoriy==1?ix:iy); + inc(t); + if(ixoriy==1)ix=(ix&0xff00)|t; + else iy=(iy&0xff00)|t; + } +endinstr; + +instr(45,4); + if(!ixoriy)dec(l); + else {unsigned char t; + t=(ixoriy==1?ix:iy); + dec(t); + if(ixoriy==1)ix=(ix&0xff00)|t; + else iy=(iy&0xff00)|t; + } +endinstr; + +instr(46,7); + setxl(fetch(pc)); + pc++; +endinstr; + +instr(47,4); + a=~a; + f=(f&0xc5)|(a&0x28)|0x12; +endinstr; + +instr(48,7); + if(f&1)pc++; + else jr; +endinstr; + +instr(49,10); + sp=fetch2(pc); + pc+=2; +endinstr; + +instr(50,13); + {unsigned short addr=fetch2(pc); + pc+=2; + store(addr,a); + } +endinstr; + +instr(51,6); + sp++; +endinstr; + +HLinstr(52,11,8); + {unsigned char t=fetch(addr); + inc(t); + store(addr,t); + } +endinstr; + +HLinstr(53,11,8); + {unsigned char t=fetch(addr); + dec(t); + store(addr,t); + } +endinstr; + +HLinstr(54,10,5); + store(addr,fetch(pc)); + pc++; +endinstr; + +instr(55,4); + f=(f&0xc4)|1|(a&0x28); +endinstr; + +instr(56,7); + if(f&1)jr; + else pc++; +endinstr; + +instr(57,11); + addhl((sp>>8),(sp&0xff)); +endinstr; + +instr(58,13); + {unsigned short addr=fetch2(pc); + pc+=2; + a=fetch(addr); + } +endinstr; + +instr(59,6); + sp--; +endinstr; + +instr(60,4); + inc(a); +endinstr; + +instr(61,4); + dec(a); +endinstr; + +instr(62,7); + a=fetch(pc),pc++; +endinstr; + +instr(63,4); + f=(f&0xc4)|(cy^1)|(cy<<4)|(a&0x28); +endinstr; + +instr(0x40,4); + /* ld b,b */ +endinstr; + +instr(0x41,4); + b=c; +endinstr; + +instr(0x42,4); + b=d; +endinstr; + +instr(0x43,4); + b=e; +endinstr; + +instr(0x44,4); + b=xh; +endinstr; + +instr(0x45,4); + b=xl; +endinstr; + +HLinstr(0x46,7,8); + b=fetch(addr); +endinstr; + +instr(0x47,4); + b=a; +endinstr; + +instr(0x48,4); + c=b; +endinstr; + +instr(0x49,4); + /* ld c,c */ +endinstr; + +instr(0x4a,4); + c=d; +endinstr; + +instr(0x4b,4); + c=e; +endinstr; + +instr(0x4c,4); + c=xh; +endinstr; + +instr(0x4d,4); + c=xl; +endinstr; + +HLinstr(0x4e,7,8); + c=fetch(addr); +endinstr; + +instr(0x4f,4); + c=a; +endinstr; + +instr(0x50,4); + d=b; +endinstr; + +instr(0x51,4); + d=c; +endinstr; + +instr(0x52,4); + /* ld d,d */ +endinstr; + +instr(0x53,4); + d=e; +endinstr; + +instr(0x54,4); + d=xh; +endinstr; + +instr(0x55,4); + d=xl; +endinstr; + +HLinstr(0x56,7,8); + d=fetch(addr); +endinstr; + +instr(0x57,4); + d=a; +endinstr; + +instr(0x58,4); + e=b; +endinstr; + +instr(0x59,4); + e=c; +endinstr; + +instr(0x5a,4); + e=d; +endinstr; + +instr(0x5b,4); + /* ld e,e */ +endinstr; + +instr(0x5c,4); + e=xh; +endinstr; + +instr(0x5d,4); + e=xl; +endinstr; + +HLinstr(0x5e,7,8); + e=fetch(addr); +endinstr; + +instr(0x5f,4); + e=a; +endinstr; + +instr(0x60,4); + setxh(b); +endinstr; + +instr(0x61,4); + setxh(c); +endinstr; + +instr(0x62,4); + setxh(d); +endinstr; + +instr(0x63,4); + setxh(e); +endinstr; + +instr(0x64,4); + /* ld h,h */ +endinstr; + +instr(0x65,4); + setxh(xl); +endinstr; + +HLinstr(0x66,7,8); + h=fetch(addr); +endinstr; + +instr(0x67,4); + setxh(a); +endinstr; + +instr(0x68,4); + setxl(b); +endinstr; + +instr(0x69,4); + setxl(c); +endinstr; + +instr(0x6a,4); + setxl(d); +endinstr; + +instr(0x6b,4); + setxl(e); +endinstr; + +instr(0x6c,4); + setxl(xh); +endinstr; + +instr(0x6d,4); + /* ld l,l */ +endinstr; + +HLinstr(0x6e,7,8); + l=fetch(addr); +endinstr; + +instr(0x6f,4); + setxl(a); +endinstr; + +HLinstr(0x70,7,8); + store(addr,b); +endinstr; + +HLinstr(0x71,7,8); + store(addr,c); +endinstr; + +HLinstr(0x72,7,8); + store(addr,d); +endinstr; + +HLinstr(0x73,7,8); + store(addr,e); +endinstr; + +HLinstr(0x74,7,8); + store(addr,h); +endinstr; + +HLinstr(0x75,7,8); + store(addr,l); +endinstr; + +instr(0x76,4); + /* Was HALT - ZXCC ignores HALT */ +endinstr; + +HLinstr(0x77,7,8); + store(addr,a); +endinstr; + +instr(0x78,4); + a=b; +endinstr; + +instr(0x79,4); + a=c; +endinstr; + +instr(0x7a,4); + a=d; +endinstr; + +instr(0x7b,4); + a=e; +endinstr; + +instr(0x7c,4); + a=xh; +endinstr; + +instr(0x7d,4); + a=xl; +endinstr; + +HLinstr(0x7e,7,8); + a=fetch(addr); +endinstr; + +instr(0x7f,4); + /* ld a,a */ +endinstr; + +instr(0x80,4); + adda(b,0); +endinstr; + +instr(0x81,4); + adda(c,0); +endinstr; + +instr(0x82,4); + adda(d,0); +endinstr; + +instr(0x83,4); + adda(e,0); +endinstr; + +instr(0x84,4); + adda(xh,0); +endinstr; + +instr(0x85,4); + adda(xl,0); +endinstr; + +HLinstr(0x86,7,8); + adda(fetch(addr),0); +endinstr; + +instr(0x87,4); + adda(a,0); +endinstr; + +instr(0x88,4); + adda(b,cy); +endinstr; + +instr(0x89,4); + adda(c,cy); +endinstr; + +instr(0x8a,4); + adda(d,cy); +endinstr; + +instr(0x8b,4); + adda(e,cy); +endinstr; + +instr(0x8c,4); + adda(xh,cy); +endinstr; + +instr(0x8d,4); + adda(xl,cy); +endinstr; + +HLinstr(0x8e,7,8); + adda(fetch(addr),cy); +endinstr; + +instr(0x8f,4); + adda(a,cy); +endinstr; + +instr(0x90,4); + suba(b,0); +endinstr; + +instr(0x91,4); + suba(c,0); +endinstr; + +instr(0x92,4); + suba(d,0); +endinstr; + +instr(0x93,4); + suba(e,0); +endinstr; + +instr(0x94,4); + suba(xh,0); +endinstr; + +instr(0x95,4); + suba(xl,0); +endinstr; + +HLinstr(0x96,7,8); + suba(fetch(addr),0); +endinstr; + +instr(0x97,4); + suba(a,0); +endinstr; + +instr(0x98,4); + suba(b,cy); +endinstr; + +instr(0x99,4); + suba(c,cy); +endinstr; + +instr(0x9a,4); + suba(d,cy); +endinstr; + +instr(0x9b,4); + suba(e,cy); +endinstr; + +instr(0x9c,4); + suba(xh,cy); +endinstr; + +instr(0x9d,4); + suba(xl,cy); +endinstr; + +HLinstr(0x9e,7,8); + suba(fetch(addr),cy); +endinstr; + +instr(0x9f,4); + suba(a,cy); +endinstr; + +instr(0xa0,4); + anda(b); +endinstr; + +instr(0xa1,4); + anda(c); +endinstr; + +instr(0xa2,4); + anda(d); +endinstr; + +instr(0xa3,4); + anda(e); +endinstr; + +instr(0xa4,4); + anda(xh); +endinstr; + +instr(0xa5,4); + anda(xl); +endinstr; + +HLinstr(0xa6,7,8); + anda(fetch(addr)); +endinstr; + +instr(0xa7,4); + anda(a); +endinstr; + +instr(0xa8,4); + xora(b); +endinstr; + +instr(0xa9,4); + xora(c); +endinstr; + +instr(0xaa,4); + xora(d); +endinstr; + +instr(0xab,4); + xora(e); +endinstr; + +instr(0xac,4); + xora(xh); +endinstr; + +instr(0xad,4); + xora(xl); +endinstr; + +HLinstr(0xae,7,8); + xora(fetch(addr)); +endinstr; + +instr(0xaf,4); + xora(a); +endinstr; + +instr(0xb0,4); + ora(b); +endinstr; + +instr(0xb1,4); + ora(c); +endinstr; + +instr(0xb2,4); + ora(d); +endinstr; + +instr(0xb3,4); + ora(e); +endinstr; + +instr(0xb4,4); + ora(xh); +endinstr; + +instr(0xb5,4); + ora(xl); +endinstr; + +HLinstr(0xb6,7,8); + ora(fetch(addr)); +endinstr; + +instr(0xb7,4); + ora(a); +endinstr; + +instr(0xb8,4); + cpa(b); +endinstr; + +instr(0xb9,4); + cpa(c); +endinstr; + +instr(0xba,4); + cpa(d); +endinstr; + +instr(0xbb,4); + cpa(e); +endinstr; + +instr(0xbc,4); + cpa(xh); +endinstr; + +instr(0xbd,4); + cpa(xl); +endinstr; + +HLinstr(0xbe,7,8); + cpa(fetch(addr)); +endinstr; + +instr(0xbf,4); + cpa(a); +endinstr; + +instr(0xc0,5); + if(!(f&0x40))ret; +endinstr; + +instr(0xc1,10); + pop1(b,c); +endinstr; + +instr(0xc2,10); + if(!(f&0x40))jp; + else pc+=2; +endinstr; + +instr(0xc3,10); + jp; +endinstr; + +instr(0xc4,10); + if(!(f&0x40))call; + else pc+=2; +endinstr; + +instr(0xc5,11); + push1(b,c); +endinstr; + +instr(0xc6,7); + adda(fetch(pc),0); + pc++; +endinstr; + +instr(0xc7,11); + push2(pc); + pc=0; +endinstr; + +instr(0xc8,5); + if(f&0x40)ret; +endinstr; + +instr(0xc9,4); + ret; +endinstr; + +instr(0xca,10); + if(f&0x40)jp; + else pc+=2; +endinstr; + +instr(0xcb,4); +#include "cbops.h" +endinstr; + +instr(0xcc,10); + if(f&0x40)call; + else pc+=2; +endinstr; + +instr(0xcd,10); + call; +endinstr; + +instr(0xce,7); + adda(fetch(pc),cy); + pc++; +endinstr; + +instr(0xcf,11); + push2(pc); + pc=8; +endinstr; + +instr(0xd0,5); + if(!cy)ret; +endinstr; + +instr(0xd1,10); + pop1(d,e); +endinstr; + +instr(0xd2,10); + if(!cy)jp; + else pc+=2; +endinstr; + +instr(0xd3,11); + tstates+=out(tstates,a,fetch(pc),a); + pc++; +endinstr; + +instr(0xd4,10); + if(!cy)call; + else pc+=2; +endinstr; + +instr(0xd5,11); + push1(d,e); +endinstr; + +instr(0xd6,7); + suba(fetch(pc),0); + pc++; +endinstr; + +instr(0xd7,11); + push2(pc); + pc=16; +endinstr; + +instr(0xd8,5); + if(cy)ret; +endinstr; + +instr(0xd9,4); + swap(b,b1); + swap(c,c1); + swap(d,d1); + swap(e,e1); + swap(h,h1); + swap(l,l1); +endinstr; + +instr(0xda,10); + if(cy)jp; + else pc+=2; +endinstr; + +instr(0xdb,11); + {unsigned short t; + a=t=in(tstates,a,fetch(pc)); + tstates+=t>>8; + pc++; + } +endinstr; + +instr(0xdc,10); + if(cy)call; + else pc+=2; +endinstr; + +instr(0xdd,4); + new_ixoriy=1; + intsample=0; +endinstr; + +instr(0xde,7); + suba(fetch(pc),cy); + pc++; +endinstr; + +instr(0xdf,11); + push2(pc); + pc=24; +endinstr; + +instr(0xe0,5); + if(!(f&4))ret; +endinstr; + +instr(0xe1,10); + if(!ixoriy)pop1(h,l); + else if(ixoriy==1)pop2(ix); + else pop2(iy); +endinstr; + +instr(0xe2,10); + if(!(f&4))jp; + else pc+=2; +endinstr; + +instr(0xe3,19); + if(!ixoriy){ + unsigned short t=fetch2(sp); + store2b(sp,h,l); + l=t; + h=t>>8; + } + else if(ixoriy==1){ + unsigned short t=fetch2(sp); + store2(sp,ix); + ix=t; + } + else{ + unsigned short t=fetch2(sp); + store2(sp,iy); + iy=t; + } +endinstr; + +instr(0xe4,10); + if(!(f&4))call; + else pc+=2; +endinstr; + +instr(0xe5,11); + if(!ixoriy)push1(h,l); + else if(ixoriy==1)push2(ix); + else push2(iy); +endinstr; + +instr(0xe6,7); + anda(fetch(pc)); + pc++; +endinstr; + +instr(0xe7,11); + push2(pc); + pc=32; +endinstr; + +instr(0xe8,5); + if(f&4)ret; +endinstr; + +instr(0xe9,4); + pc=!ixoriy?hl:ixoriy==1?ix:iy; +endinstr; + +instr(0xea,10); + if(f&4)jp; + else pc+=2; +endinstr; + +instr(0xeb,4); + swap(h,d); + swap(e,l); +endinstr; + +instr(0xec,10); + if(f&4)call; + else pc+=2; +endinstr; + +instr(0xed,4); +#include"edops.h" +endinstr; + +instr(0xee,7); + xora(fetch(pc)); + pc++; +endinstr; + +instr(0xef,11); + push2(pc); + pc=40; +endinstr; + +instr(0xf0,5); + if(!(f&0x80))ret; +endinstr; + +instr(0xf1,10); + pop1(a,f); +endinstr; + +instr(0xf2,10); + if(!(f&0x80))jp; + else pc+=2; +endinstr; + +instr(0xf3,4); + iff1=iff2=0; + intsample=0; +endinstr; + +instr(0xf4,10); + if(!(f&0x80))call; + else pc+=2; +endinstr; + +instr(0xf5,11); + push1(a,f); +endinstr; + +instr(0xf6,7); + ora(fetch(pc)); + pc++; +endinstr; + +instr(0xf7,11); + push2(pc); + pc=48; +endinstr; + +instr(0xf8,5); + if(f&0x80)ret; +endinstr; + +instr(0xf9,6); + sp=!ixoriy?hl:ixoriy==1?ix:iy; +endinstr; + +instr(0xfa,10); + if(f&0x80)jp; + else pc+=2; +endinstr; + +instr(0xfb,4); + iff1=iff2=1; + intsample=0; +endinstr; + +instr(0xfc,10); + if(f&0x80)call; + else pc+=2; +endinstr; + +instr(0xfd,4); + new_ixoriy=2; + intsample=0; +endinstr; + +instr(0xfe,7); + cpa(fetch(pc)); + pc++; +endinstr; + +instr(0xff,11); + push2(pc); + pc=56; +endinstr; + diff --git a/Tools/unix/zx/zx.c b/Tools/unix/zx/zx.c new file mode 100644 index 00000000..0ea315d0 --- /dev/null +++ b/Tools/unix/zx/zx.c @@ -0,0 +1,437 @@ +#include "zx.h" + +#ifdef WIN32 +#include "windows.h" +#endif + +/* Global variables */ + +char *progname; +char **argv; +int argc; + +byte cpm_drive; +char *mypath; + +byte cpm_user; +extern byte cpm_error; + +byte RAM[65536]; /* The Z80's address space */ + +void load_comfile(void); /* Forward declaration */ + +static int deinit_term, deinit_gsx; + +void dump_regs(FILE *fp, byte a, byte b, byte c, byte d, byte e, byte f, + byte h, byte l, word pc, word ix, word iy) +{ + fprintf(fp, "\tAF=%02x%02x BC=%02x%02x DE=%02x%02x HL=%02x%02x\n" + "\tIX=%04x IY=%04x PC=%04x\n", + a,f,b,c,d,e,h,l,pc,ix,iy); +} + + + +char *parse_to_fcb(char *s, int afcb) +{ + byte *fcb = &RAM[afcb+1]; + + RAM[afcb] = 0; + memset(fcb, ' ', 11); + + while (1) + { + if (s[0] == 0) break; + if (s[0] == ' ') {++s; continue; } + if (s[1] == ':') + { + RAM[afcb] = s[0] - '@'; + if (RAM[afcb] > 16) RAM[afcb] -= 0x20; + s+=2; + continue; + } + if (s[0] == '.') + { + ++s; + fcb = &RAM[afcb+9]; + continue; + } + *fcb = *s; if (islower(*fcb)) *fcb = toupper(*fcb); + ++s; + ++fcb; + if (fcb >= &RAM[afcb+12]) break; + } + return s; +} + + +void Msg(char *s, ...) +{ +#ifdef DEBUG + va_list ap; + + va_start(ap, s); + printf("%s trace: ", progname); + vprintf(s, ap); + fflush(stdout); + if (s[strlen(s) - 1] == '\n') putchar('\r'); + va_end(ap); +#endif +} + + +void ed_fe(byte *a, byte *b, byte *c, byte *d, byte *e, byte *f, + byte *h, byte *l, word *pc, word *ix, word *iy) +{ + switch(*a) + { + case 0xC0: + cpmbdos(a,b,c,d,e,f,h,l,pc,ix,iy); + break; + + case 0xC1: + load_comfile(); + break; + + case 0xC2: + fprintf(stderr,"%s: Incompatible BIOS.BIN\n", progname); + zx_term(); + zx_exit(1); + + case 0xC3: + cpmbios(a,b,c,d,e,f,h,l,pc,ix,iy); + break; + + default: + fprintf(stderr, "%s: Z80 encountered invalid trap\n", progname); + dump_regs(stderr,*a,*b,*c,*d,*e,*f,*h,*l,*pc,*ix,*iy); + zx_term(); + zx_exit(1); + + } +} + + +/* + * load_bios() loads the minimal CP/M BIOS and BDOS. + * + */ + +void load_bios(void) +{ + int bios_len; + + FILE * fp = NULL; + char biospath[CPM_MAXPATH + 1] = ""; + +#ifdef WIN32 + if (!fp) + { + GetModuleFileName(NULL, biospath, sizeof(biospath)); + strcpy(strrchr(biospath, '\\'), "\\bios.bin"); + fp = fopen(biospath, "rb"); + } +#else + if (!fp) { + strcpy(biospath, mypath); + strcpy(strrchr(biospath, '/'), "/bios.bin"); + fp = fopen(biospath, "rb"); + } +#endif + + if (!fp && BINDIR80) + { + strcpy(biospath, BINDIR80); + strcat(biospath, "bios.bin"); + fp = fopen(biospath, "rb"); + } + + if (!fp) fp = fopen("bios.bin", "rb"); + + if (!fp) + { + fprintf(stderr,"%s: Cannot locate bios.bin\n", progname); + zx_term(); + zx_exit(1); + } + bios_len = fread(RAM + 0xFE00, 1, 512, fp); + if (bios_len < 1 || ferror(fp)) + { + fclose(fp); + fprintf(stderr,"%s: Cannot load bios.bin\n", progname); + zx_term(); + zx_exit(1); + } + fclose(fp); + + Msg("Loaded %d bytes of BIOS\n", bios_len); +} + +/* + * try_com() attempts to open file, file.com, file.COM, file.cpm and file.CPM + * + */ + +FILE *try_com(char *s) +{ + char fname[CPM_MAXPATH + 1]; + FILE *fp; + + strcpy(fname, s); + fp = fopen(s, "rb"); if (fp) return fp; + sprintf(s,"%s.com", fname); fp = fopen(s, "rb"); if (fp) return fp; + sprintf(s,"%s.COM", fname); fp = fopen(s, "rb"); if (fp) return fp; + sprintf(s,"%s.cpm", fname); fp = fopen(s, "rb"); if (fp) return fp; + sprintf(s,"%s.CPM", fname); fp = fopen(s, "rb"); if (fp) return fp; + + strcpy(s, fname); + return NULL; +} + +/* + * load_comfile() loads the COM file whose name was passed as a parameter. + * + */ + + +void load_comfile(void) +{ + int com_len; + char fname[CPM_MAXPATH + 1] = ""; + FILE *fp; + + if (BINDIR80) strcpy(fname, BINDIR80); + strcat(fname, argv[1]); + fp = try_com(fname); + if (!fp) + { + strcpy(fname, argv[1]); + fp = try_com(fname); + } + if (!fp) + { + fprintf(stderr,"%s: Cannot locate %s, %s.com, %s.COM, %s.cpm _or_ %s.CPM\r\n", + progname, argv[1], argv[1], argv[1], argv[1], argv[1]); + zx_term(); + zx_exit(1); + } + com_len = fread(RAM + 0x0100, 1, 0xFD00, fp); + if (com_len < 1 || ferror(fp)) + { + fclose(fp); + fprintf(stderr,"%s: Cannot load %s\n", progname, fname); + zx_term(); + zx_exit(1); + } + fclose(fp); + + Msg("Loaded %d bytes from %s\n", com_len, fname); +} + +unsigned int in() { return 0; } +unsigned int out() { return 0; } + + + +/* + * xltname: Convert a unix filepath into a CP/M compatible drive:name form. + * The unix filename must be 8.3 or the CP/M code will reject it. + * + * This uses the library xlt_name to do the work, and then just strcat()s + * the result to the command line. + */ + +void zx_xltname(char *name, char *pcmd) +{ + char nbuf[CPM_MAXPATH + 1]; + + xlt_name(pcmd, nbuf); + + strcat(name, nbuf); +} + +/* main() parses the arguments to CP/M form. argv[1] is the name of the CP/M + program to load; the remaining arguments are arguments for the CP/M program. + + main() also loads the vestigial CP/M BIOS and does some sanity checks + on the endianness of the host CPU and the sizes of data types. + */ + +int main(int ac, char **av) +{ + int n; + char *pCmd, *str; + + argc = ac; + argv = av; +#ifdef __PACIFIC__ /* Pacific C doesn't support argv[0] */ + progname="ZX"; +#endif + progname = argv[0]; + mypath = strdup(argv[0]); + + /* DJGPP includes the whole path in the program name, which looks + * untidy... + */ + str = strrchr(progname, '/'); + if (!str) str = strrchr(progname, '\\'); + if (str) progname = str + 1; + + if (_isatty(_fileno(stdin))) + Msg("Using interactive console mode\n"); + else + Msg("Using standard input/ouput mode\n"); + + if (sizeof(int) > 8 || sizeof(byte) != 1 || sizeof(word) != 2) + { + fprintf(stderr,"%s: type lengths incorrect; edit typedefs " + "and recompile.\n", progname); + zx_exit(1); + } + + if (argc < 2) + { + fprintf(stderr,"%s: No CP/M program name provided.\n",progname); + zx_exit(1); + } + + + setmode(_fileno(stdin), O_BINARY ); + setmode(_fileno(stdout), O_BINARY ); + + /* Parse arguments. An argument can be either: + + * preceded by a '-', in which case it is copied in as-is, less the + dash; + * preceded by a '+', in which case it is parsed as a filename and + then concatenated to the previous argument; + * preceded by a '+-', in which case it is concatenated without + parsing; + * not preceded by either, in which case it is parsed as a filename. + + So, the argument string "--a -b c +-=q --x +/dev/null" would be rendered + into CP/M form as "-a b p:c=q -xd:null" */ + + if (!fcb_init()) + { + fprintf(stderr, "Could not initialise CPMREDIR library\n"); + zx_exit(1); + } + + xlt_map(0, BINDIR80); /* Establish the 3 fixed mappings */ + xlt_map(1, LIBDIR80); + xlt_map(2, INCDIR80); + pCmd = (char *)RAM + 0x81; + + for (n = 2; n < argc; n++) + { + if (argv[n][0] == '+' && argv[n][1] == '-') + { + /* Append, no parsing */ + strcat(pCmd, argv[n] + 2); + } + else if (!argv[n][0] || argv[n][0] == '-') + { + /* Append with space; no parsing. */ + strcat(pCmd, " "); + strcat(pCmd, argv[n] + 1); + } + else if (argv[n][0] == '+') + { + zx_xltname(pCmd, argv[n]+1); + } + else /* Translate a filename */ + { + strcat(pCmd, " "); + zx_xltname(pCmd, argv[n]); + } + + } + pCmd[0x7F] = 0; /* Truncate to fit the buffer */ + RAM[0x80] = strlen(pCmd); + + str = parse_to_fcb(pCmd, 0x5C); + parse_to_fcb(str, 0x6C); + +/* This statement is very useful when creating a client like zxc or zxas + + printf("Command tail is %s\n", pCmd); +*/ + load_bios(); + + memset(RAM + 0xFE9C, 0, 0x64); /* Zap the SCB */ + RAM[0xFE98] = 0x06; + RAM[0xFE99] = 0xFE; /* FE06, BDOS entry */ + RAM[0xFEA1] = 0x31; /* BDOS 3.1 */ + RAM[0xFEA8] = 0x01; /* UK date format */ + RAM[0xFEAF] = 0x0F; /* CCP drive */ + +#ifdef USE_CPMIO + RAM[0xFEB6] = cpm_term_direct(CPM_TERM_WIDTH, -1); + RAM[0xFEB8] = cpm_term_direct(CPM_TERM_HEIGHT, -1); +#else + RAM[0xFEB6] = 79; + RAM[0xFEB8] = 23; +#endif + RAM[0xFED1] = 0x80; /* Buffer area */ + RAM[0xFED2] = 0xFF; + RAM[0xFED3] = '$'; + RAM[0xFED6] = 0x9C; + RAM[0xFED7] = 0xFE; /* SCB address */ + RAM[0xFED8] = 0x80; /* DMA address */ + RAM[0xFED9] = 0x00; + RAM[0xFEDA] = 0x0F; /* P: */ + RAM[0xFEE6] = 0x01; /* Multi sector count */ + RAM[0xFEFE] = 0x06; + RAM[0xFEFF] = 0xFE; /* BDOS */ + + cpm_drive = 0x0F; /* Start logged into P: */ + cpm_user = 0; /* and user 0 */ + +#ifdef USE_CPMIO + cpm_scr_init(); deinit_term = 1; +#endif +#ifdef USE_CPMGSX + gsx_init(); deinit_gsx = 1; +#endif + + /* Start the Z80 at 0xFF00, with stack at 0xFE00 */ + mainloop(0xFF00, 0xFE00); + + return zx_term(); +} + +void zx_exit(int code) +{ +#ifdef USE_CPMIO + if (deinit_term) cpm_scr_unit(); +#endif +#ifdef USE_CPMGSX + if (deinit_gsx) gsx_deinit(); +#endif + exit(code); +} + +int zx_term(void) +{ + word n; + + + n = RAM[0x81]; /* Get the return code. This is Hi-Tech C */ + n = (n << 8) | RAM[0x80]; /* specific and fails with other COM files */ + + putchar('\n'); + + if (cpm_error != 0) /* The CP/M "set return code" call was used */ + { /* (my modified Hi-Tech C library uses this */ + n = cpm_error; /* call) */ + } + if (n < 256 || n == 0xFFFF) + { + Msg("Return code %d\n", n); + return n; + } + else return 0; +} + + diff --git a/Tools/unix/zx/zx.h b/Tools/unix/zx/zx.h new file mode 100644 index 00000000..dac69ce0 --- /dev/null +++ b/Tools/unix/zx/zx.h @@ -0,0 +1,95 @@ +/* + * Change the directories in these #defines if necessary. Note trailing slash. + */ + +#include "config.h" + +#ifdef __MSDOS__ + #define BINDIR80 "d:/tools/cpm/bin80/" + #define LIBDIR80 "d:/tools/cpm/lib80/" + #define INCDIR80 "d:/tools/cpm/include/" +#else +/* Unless overridden, these are defined by autoconf. Note trailing slash. + #undef BINDIR80 + #undef LIBDIR80 + #undef INCDIR80 + #define BINDIR80 "/usr/local/lib/cpm/bin80/" + #define LIBDIR80 "/usr/local/lib/cpm/lib80/" + #define INCDIR80 "/usr/local/lib/cpm/include80/" +*/ +#endif + +#define SERIAL "ZXCC05" + +/* System include files */ + +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(LINUX) || defined(DARWIN) +#include +#define _isatty(a) isatty(a) +#define _fileno(a) fileno(a) +#define setmode(a,b) +#define O_BINARY 0 +#endif +#ifdef WIN32 +#include +#endif +#include +#include +#ifdef __MSDOS +#include +#endif + +/* Library includes */ + +#ifdef USE_CPMIO +#include "cpmio.h" +#endif + +#ifdef USE_CPMGSX +#include "cpmgsx.h" +#endif + +#include "cpmredir.h" /* BDOS disc simulation */ + +typedef unsigned char byte; /* Must be exactly 8 bits */ +typedef unsigned short word; /* Must be exactly 16 bits */ + +/* Prototypes */ + +void ed_fe (byte *a, byte *b, byte *c, byte *d, byte *e, byte *f, + byte *h, byte *l, word *pc, word *ix, word *iy); +void cpmbdos(byte *a, byte *b, byte *c, byte *d, byte *e, byte *f, + byte *h, byte *l, word *pc, word *ix, word *iy); +void cpmbios(byte *a, byte *b, byte *c, byte *d, byte *e, byte *f, + byte *h, byte *l, word *pc, word *ix, word *iy); +void dump_regs(FILE *fp, byte a, byte b, byte c, byte d, byte e, byte f, + byte h, byte l, word pc, word ix, word iy); +void Msg(char *s, ...); +int zx_term(void); +void zx_exit(int code); + +byte cin(void); +void cout(byte); +int cstat(void); + +/* Global variables */ + +extern char *progname; +extern char **argv; +extern int argc; +extern byte RAM[65536]; /* The Z80's address space */ + +extern int usestdio; + +/* Z80 CPU emulation */ + +#include "z80.h" + diff --git a/Tools/unix/zx/zx.html b/Tools/unix/zx/zx.html new file mode 100644 index 00000000..b425ee1e --- /dev/null +++ b/Tools/unix/zx/zx.html @@ -0,0 +1,131 @@ +zx CP/M Command Line Emulator + +

zx CP/M Command Line Emulator

+ +

zx allows execution of CP/M 2.2 and 3.X application from a +Windows command line. It is compatible with Windows XP and greater (both +32 and 64 bit).

+ +
+ +

While the original zxcc package was generally intended to allow +execution of the Hi-Tech C CP/M compiler under Unix, zx is slightly +more general and intended to allow running most CP/M tools. Specific +changes were incorporated to improve interactice console operation of +CP/M applications.

+ +

Setup

+ +

The zx application (zx.exe) may be copied to any directory for execution. +The bios.bin file must be copied to the same directory. For ease of use, +you will probably want the directory to part of your PATH environment +variable so that you can run the tool from any location.

+ +

You will also need the CP/M applications that you want to run. +zx will load files fromthe current directory or one of the following +directories based on file type. Any of the following environment +variables may be defined to determine where zx searches for the +respective file types:

+ +
    +
  • ZXBINDIR may contain a single path which will +be searched for executable files (usually *.com)
  • +
  • ZXLIBDIR may contain a single path which will +be search for library files (usually *.lib)
  • +
  • ZXINCDIR may contain a single path which will +be searched for include files (usually *.inc)
  • +
+ +

Usage

+ +

In general CP/M applications are executed by prefixing the CP/M command +line with "zx". So for example, you could assemble a test.asm using +rmac with a command line like:

+ +
zx rmac hello
+ +

In this case, rmac.com would need to be in the directory specified by +environment variable ZXBINDIR or in the current directory. Also, +hello.asm would need to be in the current directory.

+ +

Filenames

+ +

Where you would normally enter a CP/M filename you instead enter +a Windows filename. Note that you will need to use a forward slash +instead of the traditional backslash as a directory separator. The +filename itself (as opposed to any directories in +its path) must obey CP/M 8.3 naming conventions.

+ +

Where the documentation requires a CP/M drive letter/user number +you should enter a path complete with trailing slash, for example:

+
-I/usr/src/linux-80/include/
+ +

Technical

+ +

zx emulates a subset of CP/M 3; hopefully enough to run the +most CP/M tools. It can be used as a limited general-purpose CP/M 3 +emulator provided the emulated program only uses a common subset of +system calls.

+ +

Syntax for zx is:

+ +
+zx comfile.com arg1 arg2 ... +
+ +

The comfile is the program to run; zx searches the current +directory and ZXBINDIR for it.

+ +

The arguments are parsed in this way:

+ +
    +
  • Any argument starting with a - sign is passed to the CP/M program as-is, +minus the leading - sign. +
  • Any argument starting with a + sign is parsed as a filename (see below) +and then concatenated to the previous argument. +
  • Any argument starting "+-" is concatenated without being parsed. +
  • All other arguments are parsed as filenames. The path is +converted to a CP/M driveletter. +
+ +

For example: +

+zx foo.com --Q -A /src/main --I +/src/sub +-, +/foo/bar +
+ +

would pass these arguments to foo.com:

+ +
+-Q A d:main -Id:sub,e:bar +
+ +

The other programs are merely wrappers that convert their command lines +into the form required by zx.

+ +

Errors

+ +

Any errors raised by the zx runtime system will be prefixed +with zx:. Some errors you may encounter are:

+ +
+
Unsupported BDOS call
+
Part of CP/M 3 that the program uses has not been emulated. Add the +required functionality to zxbdos.c and recompile.
+
Z80 encountered invalid trap
+
The CP/M program being run attempted to call the zx runtime +system with an unknown call number.
+
+ +

Acknowledgements

+ +
    +
  • zxcc was written by John Elliott
  • +
  • Hi-Tech C was written by Hi-Tech Software.
  • +
  • The Z80 emulation engine was written by Ian Collier.
  • +
  • Thanks to Jacob Nevins, Andy Parkins and others for bug fix suggestions.
  • +
+ + diff --git a/Tools/unix/zx/zxbdos.c b/Tools/unix/zx/zxbdos.c new file mode 100644 index 00000000..06b951ad --- /dev/null +++ b/Tools/unix/zx/zxbdos.c @@ -0,0 +1,561 @@ +#include "zx.h" + +#define BDOS_DEF +#include "zxbdos.h" +#include "zxcbdos.h" +#include "zxdbdos.h" + +#ifdef __MSDOS__ +#include +#endif + +#define BCD(x) (((x % 10)+16*(x/10)) & 0xFF) + +/* Convert time_t to CP/M day count/hours/minutes */ +dword cpmtime(time_t t) +{ + long d = (t / 86400) - 2921; /* CP/M day 0 is unix day 2921 */ + long h = (t % 86400) / 3600; /* Hour, 0-23 */ + long m = (t % 3600) / 60; /* Minute, 0-59 */ + + return (d | (BCD(h) << 16) | (BCD(m) << 24)); +} + + +byte get_time(cpm_word b) +{ + time_t t; + + time(&t); + wr32(b, cpmtime(t)); + + return (BCD(t % 60)); +} + + +/* Functions to access 24-bit & 32-bit words in memory. These are always + little-endian. */ + +void wr24(word addr, dword v) +{ + RAM[addr ] = v & 0xFF; + RAM[addr + 1] = (v >> 8) & 0xFF; + RAM[addr + 2] = (v >> 16) & 0xFF; +} + +void wr32(word addr, dword v) +{ + RAM[addr ] = v & 0xFF; + RAM[addr + 1] = (v >> 8) & 0xFF; + RAM[addr + 2] = (v >> 16) & 0xFF; + RAM[addr + 3] = (v >> 24) & 0xFF; +} + +dword rd24(word addr) +{ + register dword rv = RAM[addr + 2]; + + rv = (rv << 8) | RAM[addr + 1]; + rv = (rv << 8) | RAM[addr]; + return rv; +} + + +dword rd32(word addr) +{ + register dword rv = RAM[addr + 3]; + + rv = (rv << 8) | RAM[addr + 2]; + rv = (rv << 8) | RAM[addr + 1]; + rv = (rv << 8) | RAM[addr]; + return rv; +} + +#define peekw(addr) ( (((word)(RAM[addr + 1])) << 8) | RAM[addr]) + + +/* Get / set the program return code. We store this in 'C' form: 0 for + success, 1-255 for failure. Translate to/from the CP/M form of: + + 0x0000-0xFEFF for success + 0xFF00-0xFFFE for failure + + We also store the actual value so it can be returned + + */ + +word cpm_errcde(word DE) +{ + static word real_err = 0; + + if (DE == 0xFFFF) return real_err; + real_err = DE; + + if (DE == 0xFF00) cpm_error = 1; + else if (DE > 0xFF00) cpm_error = (DE & 0xFF); + else cpm_error = 0; + return 0; +} + + +#ifdef USE_CPMGSX +gsx_byte gsxrd(gsx_word addr) +{ + return RdZ80(addr); +} + +void gsxwr(gsx_word addr, gsx_byte value) +{ + WrZ80(addr, value); +} + +#endif + +#undef bc +#undef de +#undef hl + +void setw(byte *l, byte *h, word w) +{ + *l = (w & 0xFF); + *h = (w >> 8) & 0xFF; +} + +void cpmbdos(byte *a, byte *b, byte *c, byte *d, byte *e, byte *f, + byte *h, byte *l, word *pc, word *ix, word *iy) +{ + word de = ((*d) << 8) | *e; + word hl = ((*h) << 8) | *l; + byte *pde = &RAM[de]; + byte *pdma = &RAM[cpm_dma]; + word temp; + int retv; + + Msg("BDOS service invoked: C=%02x DE=%04x\n", *c, de); + + switch(*c) + { + case 0: + *pc = 0; + break; + + case 1: /* Get a character */ +#ifdef USE_CPMIO + retv = cpm_bdos_1(); +#else + retv = cin(); +#endif + if (retv < 0) *pc = 0; + setw(l, h, retv); + break; + + case 2: /* Print a character */ +#ifdef USE_CPMIO + if (cpm_bdos_2(*e)) *pc = 0; +#else + cout(*e); +#endif + break; + + case 3: /* No auxin */ + setw(l, h, 0x1A); + break; + + case 4: /* No auxout */ + break; + + case 5: /* No printer */ + break; + + case 6: /* Direct console I/O */ + retv = cpm_bdos_6(*e); + if (retv < 0) *pc = 0; + setw(l, h, retv); + break; + + case 7: /* No auxist */ + case 8: /* No auxost */ + break; + + case 9: /* Print a $-terminated string */ +#ifdef USE_CPMIO + if (cpm_bdos_9((char *)pde)) *pc = 0; +# else + for (temp = 0; RAM[de + temp] != '$'; ++temp) + { + cout(RAM[de + temp]); + } +#endif + break; + + case 0x0A: + bdos_rdline(de, &(*pc)); + break; + + case 0x0B: /* Console status */ + //*l = *h = 0; /* No keys pressed */ + *l = cstat(); + *h = 0; + break; + + case 0x0C: /* Get CP/M version */ + +/* For GENCOM's benefit, claim to be v3.1 */ + + *l = 0x31; /* v3.1 */ + //*l = 0x22; /* v2.2 */ + *h = 0; /* CP/M, no network */ + break; + + case 0x0D: /* Re-log discs */ + fcb_reset(); + break; + + case 0x0E: /* Set default drive */ + setw(l, h, fcb_drive(*e)); + break; + + case 0x0F: /* Open using FCB */ + setw(l, h, x_fcb_open(pde, pdma)); + break; + + case 0x10: /* Close using FCB */ + setw(l, h, fcb_close(pde)); + break; + + case 0x11: /* Find first */ + setw(l, h, fcb_find1(pde, pdma)); + break; + + case 0x12: + setw(l, h, fcb_find2(pde, pdma)); + break; + + case 0x13: /* Delete using FCB */ + setw(l, h, fcb_unlink(pde, pdma)); + break; + + case 0x14: /* Sequential read using FCB */ + setw(l, h, fcb_read(pde, pdma)); + + //Msg("fcb_read L=%02x H=%02x\n", *l, *h); + + break; + + case 0x15: /* Sequential write using FCB */ + setw(l, h, fcb_write(pde, pdma)); + break; + + case 0x16: /* Create using FCB */ + setw(l, h, fcb_creat(pde, pdma)); + break; + + case 0x17: /* Rename using FCB */ + setw(l, h, fcb_rename(pde, pdma)); + break; + + case 0x18: /* Get login vector */ + setw(l, h, fcb_logvec()); + break; + + case 0x19: /* Get default drive */ + setw(l, h, cpm_drive); + break; + + case 0x1A: /* Set DMA */ + Msg("Set DMA to %04x\n", de); + cpm_dma = de; + break; + + case 0x1B: /* Get alloc vector */ + fcb_getalv(RAM + 0xFF80, 0x40); + setw(l, h, 0xFF80); + break; + + case 0x1C: /* Make disc R/O */ + setw(l, h, fcb_rodisk()); + break; + + case 0x1D: /* Get R/O vector */ + setw(l, h, fcb_rovec()); + break; + + case 0x1E: /* Set attributes */ + setw(l, h, fcb_chmod(pde, pdma)); + break; + + case 0x1F: /* Get DPB */ + fcb_getdpb(RAM + 0xFFC0); + setw(l, h, 0xFFC0); + break; /* Whoops. Missed that 'break'. */ + + case 0x20: /* Get/set uid */ + setw(l, h, fcb_user(*e)); + break; + + case 0x21: /* Read a record */ + setw(l, h, fcb_randrd(pde, pdma)); + break; + + case 0x22: /* Write a record */ + setw(l, h, fcb_randwr(pde, pdma)); + break; + + case 0x23: /* Get file size */ + setw(l, h, x_fcb_stat(pde)); + break; + + case 0x24: /* Get file pointer */ + setw(l, h, fcb_tell(pde)); + break; + + case 0x25: + setw(l, h, fcb_resro(de)); + break; + + /* MP/M drive access functions, not implemented */ + + case 0x28: /* Write with 0 fill */ + setw(l, h, fcb_randwz(pde, pdma)); + break; + + /* MP/M record locking functions, not implemented */ + + case 0x2C: /* Set no. of records to read/write */ + setw(l, h, fcb_multirec(*e)); + break; + + case 0x2D: /* Set error mode */ + err_mode = *e; + break; + + case 0x2E: + setw(l, h, fcb_dfree(*e, pdma)); + break; /* Whoops. Missed that 'break'. */ + + /* 0x2F: Chain */ + + case 0x30: + setw(l, h, fcb_sync(*e)); + break; + + case 0x31: + if (pde[1] == 0xFE) + { + RAM[0xFE9C + *pde] = pde[2]; + RAM[0xFE9D + *pde] = pde[3]; + } + else if (RAM[hl + 1] == 0xFF) + { + RAM[0xFE9C + *pde] = pde[2]; + } + else + { + *l = RAM[0xFE9C + *pde]; + *h = RAM[0xFE9D + *pde]; + } + break; + + case 0x32: + temp = *ix; + *ix = 3 * (pde[0] + 1); + *a = pde[1]; + *c = pde[2]; + *b = pde[3]; + *e = pde[4]; + *d = pde[5]; + *l = pde[6]; + *h = pde[7]; + cpmbios(a,b,c,d,e,f,h,l,pc,ix,iy); + *ix = temp; + break; + + case 0x3C: /* Communicate with RSX */ + *h = *l = 0; + break; + + case 0x62: /* Purge */ + setw(l, h, fcb_purge()); + break; + + case 0x63: /* Truncate file */ + setw(l, h, fcb_trunc(pde, pdma)); + break; + + case 0x64: /* Set label */ + setw(l, h, fcb_setlbl(pde, pdma)); + break; + + case 0x65: /* Get label byte */ + setw(l, h, fcb_getlbl(*e)); + break; + + case 0x66: /* Get file date */ + setw(l, h, fcb_date(pde)); + break; + + case 0x67: /* Set password */ + setw(l, h, fcb_setpwd(pde, pdma)); + break; + + case 0x68: /* Set time of day */ + /* Not advisable to let an emulator play with the clock */ + break; + + case 0x69: /* Get time of day */ + setw(l, h, get_time(de)); + break; + + case 0x6A: /* Set default password */ + setw(l, h, fcb_defpwd(pde)); + break; + + case 0x6B: /* Get serial number */ + memcpy(pde, SERIAL, 6); + break; + + case 0x6C: /* 0.03 set error code */ + setw(l, h, cpm_errcde(de)); + break; + +#ifdef USE_CPMIO + case 0x6D: /* Set/get console mode */ + setw(l, h, cpm_bdos_109(de)); + break; + + case 0x6E: /* Set/get string delimiter */ + setw(l, h, cpm_bdos_110(*e)); + break; + + case 0x6F: /* Send fixed length string to screen */ + if (cpm_bdos_111((char *)RAM + peekw(de), + peekw(de + 2))) + *pc = 0; + break; + + case 0x70: /* Send fixed length string to printer */ + break; + + /* 0x71: Strange PCP/M function */ +#else + case 0x6D: /* Set/get console mode */ + setw(l, h, 0); + break; + +#endif + +#ifdef USE_CPMGSX + case 0x73: /* GSX */ + setw(l, h, gsx80(gsxrd, gsxwr, de)); + break; +#endif + + case 0x74: /* Set date stamp */ + setw(l, h, fcb_sdate(pde, pdma)); + break; + + case 0x98: /* Parse filename */ + setw(l, h, fcb_parse((char *)RAM + peekw(de), + (byte *)RAM + peekw(de + 2))); + break; + + default: +#ifdef USE_CPMIO + cpm_scr_unit(); +#endif +#ifdef USE_CPMGSX + gsx_deinit(); +#endif + + fprintf(stderr,"%s: Unsupported BDOS call %d\n", progname, + (int)(*c)); + dump_regs(stderr,*a,*b,*c,*d,*e,*f,*h,*l,*pc,*ix,*iy); + zx_exit(1); + break; + } + + *a = *l; + *b = *h; + + Msg("BDOS service completion.\n"); +} + + + +void cpmbios(byte *a, byte *b, byte *c, byte *d, byte *e, byte *f, + byte *h, byte *l, word *pc, word *ix, word *iy) +{ + int func = (((*ix) & 0xFF) / 3) - 1; + + Msg("BIOS service invoked: func=%02x\n", func); + + switch(func) /* BIOS function */ + { + case 1: + zx_exit(zx_term()); /* Program termination */ + break; + + case 2: /* CONST */ +#ifdef USE_CPMIO + *a = cpm_const(); +#else + *a = cpm_bdos_6(0xFE); +#endif + break; + + case 3: /* CONIN */ +#ifdef USE_CPMIO + *a = cpm_conin(); +#else + *a = cpm_bdos_6(0xFD); +#endif + break; + + case 4: /* CONOUT */ +#ifdef USE_CPMIO + cpm_conout(*c); +#else + cpm_bdos_6(*c); +#endif + break; + + case 20: /* DEVTBL */ + setw(l, h, 0xFFFF); + break; + + case 22: /* DRVTBL */ + setw(l, h, 0xFFFF); + break; + + case 26: /* TIME */ + RAM[0xFEF8] = get_time(0xFEF4); + break; + + case 30: /* USERF!!! */ +#ifdef USE_CPMIO + cpm_bdos_110('$'); + cpm_bdos_9("This program has attempted to call USERF, " + "which is not implemented\r\n$"); +#else + printf("This program has attempted to call USERF, which " + "is not implemented.\n"); +#endif + zx_term(); + zx_exit(1); + break; + + default: +#ifdef USE_CPMIO + cpm_scr_unit(); +#endif +#ifdef USE_CPMGSX + gsx_deinit(); +#endif + + fprintf(stderr,"%s: Unsupported BIOS call %d\n", progname, func); + dump_regs(stderr,*a,*b,*c,*d,*e,*f,*h,*l,*pc,*ix,*iy); + zx_exit(1); + } + + Msg("BIOS service completion.\n"); +} diff --git a/Tools/unix/zx/zxbdos.h b/Tools/unix/zx/zxbdos.h new file mode 100644 index 00000000..4111c78e --- /dev/null +++ b/Tools/unix/zx/zxbdos.h @@ -0,0 +1,50 @@ +extern char *progname; +extern char **argv; +extern int argc; + +extern byte cpm_drive; +extern byte cpm_user; + +extern byte RAM[65536]; /* The Z80's address space */ + +extern void Msg(char *s, ...); + +#ifdef BDOS_DEF + +word cpm_dma = 0x80; /* DMA address */ +byte err_mode = 0xFF; +byte rec_multi = 1; +word rec_len = 128; +word ffirst_fcb = 0xFFFF; +byte cpm_error = 0; /* Error code returned by CP/M */ + +#else /* BDOS_DEF */ + +extern word cpm_dma, rec_len, ffirst_fcb; +extern byte err_mode, rec_multi, cpm_error; + +#endif /* BDOS_DEF */ + +#ifndef O_BINARY /* Necessary in DOS, not present in Linux */ +#define O_BINARY 0 +#endif + +typedef unsigned long dword; + +/* Functions in zxbdos.c */ + +void wr24(word addr, dword v); +void wr32(word addr, dword v); +dword rd24(word addr); +dword rd32(word addr); +dword cpmtime(time_t t); +word cpm_errcde(word DE); + +#ifdef USE_CPMGSX +gsx_byte gsxrd(gsx_word addr); +void gsxwr(gsx_word addr, gsx_byte value); +#endif + +void cpmbdos(); +void cpmbios(); + diff --git a/Tools/unix/zx/zxcbdos.c b/Tools/unix/zx/zxcbdos.c new file mode 100644 index 00000000..b441fa7f --- /dev/null +++ b/Tools/unix/zx/zxcbdos.c @@ -0,0 +1,114 @@ +#include "zx.h" +#include "zxbdos.h" +#include "zxcbdos.h" +#include +#ifdef WIN32 +#include +#endif + +/* Line input */ +#ifdef USE_CPMIO + + +void bdos_rdline(word line, word *PC) +{ + char *buf; + + if (!line) line = cpm_dma; + else RAM[line + 1] = 0; + + buf = (char *)&RAM[line]; + + if (cpm_bdos_10(buf)) *PC = 0; +} + +#else /* def USE_CPMIO */ + +void bdos_rdline(word line, word *PC) +{ + int maxlen; + + if (!line) line = cpm_dma; + maxlen = RAM[line]; + + fgets((char *)(RAM + line + 2), maxlen, stdin); + RAM[line + 1] = strlen((char *)(RAM + line + 2)) - 1; + + Msg("Input: [%d] %-*.*s\n", RAM[line + 1], RAM[line + 1], RAM[line +1], (char *)(RAM+line+2)); +} +#endif /* ndef USE_CPMIO */ + +#ifndef USE_CPMIO + + +int cpm_bdos_6(byte e) +{ + int c; + + switch(e) { + case 0xFF: + if (cstat()) return cin(); + return 0; + + case 0xFE: + return cstat(); + + case 0xFD: + return cin(); + + default: + cout(e); + break; + } + return 0; +} +#endif + +#if defined(__MINGW32__) || defined(_MSC_BUILD) || defined(__WATCOMC__) + +byte cin() +{ + if (_isatty(_fileno(stdin))) + return getch(); + else + return getchar(); +} + +void cout(byte c) +{ + if (_isatty(_fileno(stdout))) + putch(c); + else + putchar(c); +} + +int cstat() +{ + if (_isatty(_fileno(stdin))) + return _kbhit() ? 0xFF : 0; + else + return 0xFF; +} + +#else /* defined(__MINGW32__) || defined(_MSC_BUILD) */ + +byte cin() +{ + return getchar(); +} + +void cout(byte c) +{ + putchar(c); +} + +int cstat() +{ + int i; + + ioctl(_fileno(stdin), FIONREAD, &i); + if (i > 0) return 0xff; + return 0; +} + +#endif diff --git a/Tools/unix/zx/zxcbdos.h b/Tools/unix/zx/zxcbdos.h new file mode 100644 index 00000000..7f724441 --- /dev/null +++ b/Tools/unix/zx/zxcbdos.h @@ -0,0 +1,4 @@ +void bdos_rdline(word line, word *PC); +int cpm_bdos_6(byte e); + + diff --git a/Tools/unix/zx/zxdbdos.c b/Tools/unix/zx/zxdbdos.c new file mode 100644 index 00000000..f5f21f3b --- /dev/null +++ b/Tools/unix/zx/zxdbdos.c @@ -0,0 +1,88 @@ +#include "zx.h" +#include "zxbdos.h" +#include "zxdbdos.h" + +/* This file used to deal with all disc-based BDOS calls. + Now the calls have been moved into libcpmredir, it's a bit empty round + here. + + ZXCC does a few odd things when searching, to make Hi-Tech C behave + properly. +*/ + + +/* If a file could not be found on the default drive, try again on a "search" + drive (A: for .COM files, B: for .LIB and .OBJ files) */ + +int fcbforce(byte *fcb, byte *odrv) +{ + byte drive; + char typ[4]; + int n; + + for (n = 0; n < 3; n++) typ[n] = fcb[n+9] & 0x7F; + typ[3] = 0; + + Msg("fcbforce: typ=%s, fcb=%hhx\r\n", typ, *fcb); + + drive = 0; + if (*fcb) return 0; /* not using default drive */ + //if ((*fcb) != 16) return 0; /* not using default drive */ + if (!strcmpi(typ, "COM")) drive = 1; + if (!strcmpi(typ, "LIB")) drive = 2; + if (!strcmpi(typ, "OBJ")) drive = 2; + if (!strcmpi(typ, "H ")) drive = 3; + + Msg("fcbforce: drive=%i\r\n", drive); + + if (!drive) return 0; + + *odrv = *fcb; + *fcb = drive; + return 1; +} + +/* zxcc has a trick with some filenames: If it can't find them where they + should be, and a drive wasn't specified, it searches BINDIR80, + LIBDIR80 or INCDIR80 (depending on the type of the file). + */ + +word x_fcb_open(byte *fcb, byte *dma) +{ + word rv = fcb_open(fcb, dma); + byte odrv; + + Msg("x_fcb_open: rv=%X\r\n", rv); + + if (rv == 0xFF) + { + if (fcbforce(fcb, &odrv)) + { + rv = fcb_open(fcb, dma); + Msg("x_fcb_open: rv=%X\r\n", rv); + *fcb = odrv; + } + } + return rv; +} + + + +word x_fcb_stat(byte *fcb) +{ + word rv = fcb_stat(fcb); + byte odrv; + + if (rv == 0xFF) + { + if (fcbforce(fcb, &odrv)) + { + rv = fcb_stat(fcb); + *fcb = odrv; + } + } + return rv; +} + + + diff --git a/Tools/unix/zx/zxdbdos.h b/Tools/unix/zx/zxdbdos.h new file mode 100644 index 00000000..7a47b801 --- /dev/null +++ b/Tools/unix/zx/zxdbdos.h @@ -0,0 +1,8 @@ + + + +int fcbforce(byte *fcb, byte *odrv); + +word x_fcb_open(byte *fcb, byte *dma); +word x_fcb_stat(byte *fcb); +

zx is basically a port of a subset of the zxcc package by John Elliott. +The GPLv2 licensing carries forward. Please refer to the + +zxcc web page for more information.