-- Copyright (C) 1984 by Xerox Corporation. All rights reserved. --FloppyImplPublicC.mesa (last edited by JXG on 18-Jan-84 17:25:19) DIRECTORY Environment USING [wordsPerPage], File USING [File, PageCount, PageNumber, Type], Floppy USING [ AlreadyFormatted, BootFilePointer, Close, CopyFromPilotFile, CopyToPilotFile, DataError, Density, Error, ErrorType, FileHandle, FileID, Format, GetAttributes, GetBootFiles, GetFileAttributes, GetNextFile, nullFileID, Open, PageCount, PageNumber, SetBootFiles, SetRootFile, Sides, VolumeHandle], FloppyChannel USING [DiskAddress], FloppyFormat USING [ ConvertPageNumber, DiskAddressToSector, firstDataAddress, nullSector, Sector, SectorToDiskAddress], FloppyImplInterface, Inline USING [LowHalf], Space USING [Interval, Map, Unmap], SpecialFloppy USING [CreateInitialMicrocodeWithID, CreateFileAtAddressWithID, Error, ErrorType, GetFileAtDiskAddress, nullDiskAddress]; FloppyImplPublicC: MONITOR LOCKS volumeDesc USING volumeDesc: FloppyImplInterface.VolumeDesc IMPORTS Floppy, FloppyFormat, FloppyImplInterface, Inline, Space, SpecialFloppy EXPORTS Floppy, SpecialFloppy = BEGIN OPEN FloppyImplInterface; FloppyFileInfo: TYPE = MACHINE DEPENDENT RECORD [ fileID(0): Floppy.FileID, size(2): Floppy.PageCount, fileType(4): File.Type, offset(5): File.PageNumber]; FloppyInfoSeq: TYPE = RECORD [ length: CARDINAL, item: ARRAY [0..0) OF FloppyFileInfo]; FloppyInfoSeqType: TYPE = LONG POINTER TO FloppyInfoSeq; HeaderPage0: TYPE = MACHINE DEPENDENT RECORD [ seal(0): CARDINAL ¬ ImageSeal, version(1): CARDINAL¬ ImageVersion, currentNumberOfFiles(2): CARDINAL, maxNumberOfFiles(3): CARDINAL, fileList(4): Floppy.FileHandle, rootFile(8): Floppy.FileHandle, density(12): Floppy.Density, sides(13): Floppy.Sides, initialUCode(14): Floppy.BootFilePointer, pilotUCode(18): Floppy.BootFilePointer, diagUCode(22): Floppy.BootFilePointer, germ(26): Floppy.BootFilePointer, pilotBootFile(30): Floppy.BootFilePointer, pilotUCodeAddress(34): FloppyChannel.DiskAddress, diagUCodeAddress(36): FloppyChannel.DiskAddress, germAddress(38): FloppyChannel.DiskAddress, pilotBootFileAddress(40): FloppyChannel.DiskAddress, sizeOfFile(42): Floppy.PageCount, pseudoRootPageIndex(44): CARDINAL, labelStringBody(45): StringBody]; ImageSeal: CARDINAL = 146363B; ImageVersion: CARDINAL = 1; PseudoPVRootPageAddress: FloppyChannel.DiskAddress = [ cylinder: 4, head: 0, sector: 1]; CreateFileAtAddress: PUBLIC PROCEDURE [ volume: Floppy.VolumeHandle, size: Floppy.PageCount, type: File.Type, location: FloppyChannel.DiskAddress] RETURNS [file: Floppy.FileHandle] = BEGIN volumeDesc: VolumeDesc; file.volume ¬ volume; volumeDesc ¬ ValidateHandle[volume]; file.file ¬ CreateFileAtAddressWithIDInternal[ volumeDesc, Floppy.nullFileID, size, type, location]; END; CreateFileAtAddressWithID: PUBLIC PROCEDURE [ volume: Floppy.VolumeHandle, id: Floppy.FileID, size: Floppy.PageCount, type: File.Type, location: FloppyChannel.DiskAddress] RETURNS [file: Floppy.FileHandle] = BEGIN volumeDesc: VolumeDesc; file.volume ¬ volume; volumeDesc ¬ ValidateHandle[volume]; file.file ¬ CreateFileAtAddressWithIDInternal[ volumeDesc, id, size, type, location]; END; CreateFileAtAddressWithIDInternal: ENTRY PROCEDURE [ volumeDesc: VolumeDesc, id: Floppy.FileID, size: Floppy.PageCount, type: File.Type, location: FloppyChannel.DiskAddress] RETURNS [file: Floppy.FileID] = BEGIN ENABLE BEGIN UNWIND => NULL; END; startSector: FloppyFormat.Sector; IF ~volumeDesc.open THEN RETURN WITH ERROR Floppy.Error[volumeNotOpen]; IF location = SpecialFloppy.nullDiskAddress THEN startSector ¬ FloppyFormat.nullSector ELSE BEGIN startSector ¬ FloppyFormat.DiskAddressToSector[ location, volumeDesc.sectorNine.cylinders, volumeDesc.sectorNine.tracksPerCylinder, volumeDesc.sectorNine.sectorsPerTrack]; --check that location is valid IF (FloppyFormat.DiskAddressToSector[ FloppyFormat.firstDataAddress, volumeDesc.sectorNine.cylinders, volumeDesc.sectorNine.tracksPerCylinder, volumeDesc.sectorNine.sectorsPerTrack] > startSector) OR (startSector + size > volumeDesc.numPages - 1) THEN RETURN WITH ERROR SpecialFloppy.Error[invalidDiskAddress]; END; file ¬ CreateFileInternal[volumeDesc, size, type, startSector, id].fileID; END; CreateFloppyFromImage: PUBLIC PROCEDURE [ floppyDrive: CARDINAL, imageFile: File.File, firstImagePage: File.PageNumber, reformatFloppy: BOOLEAN, floppyDensity: Floppy.Density, floppySides: Floppy.Sides, numberOfFiles: CARDINAL, newLabelString: LONG STRING] = BEGIN floppyFileInfoPerPage: CARDINAL = Environment.wordsPerPage/SIZE[FloppyFileInfo]; headerPage0Ptr: LONG POINTER TO HeaderPage0; floppyHandle: Floppy.VolumeHandle; space1Interval: Space.Interval; space2Interval: Space.Interval; floppyInfoSeqPtr: FloppyInfoSeqType; fileHandle: Floppy.FileHandle; oldLabelString: LONG STRING; index: CARDINAL ¬ 0; spaceAvailable: Floppy.PageCount; dataErrorFile: Floppy.FileHandle; dataErrorPage: Floppy.PageNumber; BEGIN OPEN hdr:headerPage0Ptr, flptr: floppyInfoSeqPtr; space1Interval ¬ Space.Map[ [file: imageFile, base: firstImagePage, count: 1]]; headerPage0Ptr ¬ space1Interval.pointer; IF hdr.seal # ImageSeal OR hdr.version # ImageVersion THEN ERROR Floppy.Error[error: floppyImageInvalid]; IF numberOfFiles # 0 AND hdr.currentNumberOfFiles > numberOfFiles THEN ERROR Floppy.Error[error: fileListLengthTooShort]; oldLabelString ¬ @hdr.labelStringBody; space2Interval ¬ Space.Map[ [file: imageFile, base: firstImagePage + 1, count: hdr.currentNumberOfFiles/floppyFileInfoPerPage + 1]]; floppyInfoSeqPtr ¬ space2Interval.pointer; Floppy.Format[ drive: floppyDrive, maxNumberOfFileListEntries: IF numberOfFiles = 0 THEN hdr.maxNumberOfFiles ELSE numberOfFiles, labelString: IF newLabelString = NIL THEN oldLabelString ELSE IF newLabelString.length = 0 THEN oldLabelString ELSE newLabelString, density: floppyDensity, sides: floppySides ! Floppy.AlreadyFormatted => IF reformatFloppy THEN RESUME ELSE REJECT]; floppyHandle ¬ Floppy.Open[floppyDrive]; [freeSpace: spaceAvailable] ¬ Floppy.GetAttributes [ volume: floppyHandle, labelString: NIL]; IF hdr.sizeOfFile > spaceAvailable THEN ERROR Floppy.Error[error: floppySpaceTooSmall]; FOR index IN [0..flptr.length) DO SELECT flptr.item[index].fileID FROM Floppy.nullFileID => EXIT; hdr.initialUCode.file => fileHandle ¬ SpecialFloppy.CreateInitialMicrocodeWithID[ volume: floppyHandle, id: flptr.item[index].fileID, size: flptr.item[index].size, type: flptr.item[index].fileType, startingPageNumber: hdr.initialUCode.page ! SpecialFloppy.Error => SELECT error FROM IDAlreadyInUse => ERROR Floppy.Error[error: floppyImageInvalid]; ENDCASE]; flptr.item[hdr.pseudoRootPageIndex].fileID => fileHandle ¬ SpecialFloppy.CreateFileAtAddressWithID[ volume: floppyHandle, id: flptr.item[index].fileID, size: flptr.item[index].size, type: flptr.item[index].fileType, location: PseudoPVRootPageAddress ! SpecialFloppy.Error => SELECT error FROM IDAlreadyInUse => ERROR Floppy.Error[error: floppyImageInvalid]; ENDCASE]; hdr.pilotUCode.file => fileHandle ¬ SpecialFloppy.CreateFileAtAddressWithID[ volume: floppyHandle, id: flptr.item[index].fileID, size: flptr.item[index].size, type: flptr.item[index].fileType, location: hdr.pilotUCodeAddress ! SpecialFloppy.Error => SELECT error FROM IDAlreadyInUse => ERROR Floppy.Error[error: floppyImageInvalid]; ENDCASE]; hdr.diagUCode.file => fileHandle ¬ SpecialFloppy.CreateFileAtAddressWithID[ volume: floppyHandle, id: flptr.item[index].fileID, size: flptr.item[index].size, type: flptr.item[index].fileType, location: hdr.diagUCodeAddress ! SpecialFloppy.Error => SELECT error FROM IDAlreadyInUse => ERROR Floppy.Error[error: floppyImageInvalid]; ENDCASE]; hdr.germ.file => fileHandle ¬ SpecialFloppy.CreateFileAtAddressWithID[ volume: floppyHandle, id: flptr.item[index].fileID, size: flptr.item[index].size, type: flptr.item[index].fileType, location: hdr.germAddress ! SpecialFloppy.Error => SELECT error FROM IDAlreadyInUse => ERROR Floppy.Error[error: floppyImageInvalid]; ENDCASE]; hdr.pilotBootFile.file => fileHandle ¬ SpecialFloppy.CreateFileAtAddressWithID[ volume: floppyHandle, id: flptr.item[index].fileID, size: flptr.item[index].size, type: flptr.item[index].fileType, location: hdr.pilotBootFileAddress ! SpecialFloppy.Error => SELECT error FROM IDAlreadyInUse => ERROR Floppy.Error[error: floppyImageInvalid]; ENDCASE]; ENDCASE => fileHandle ¬ SpecialFloppy.CreateFileAtAddressWithID[ volume: floppyHandle, id: flptr.item[index].fileID, size: flptr.item[index].size, type: flptr.item[index].fileType, location: SpecialFloppy.nullDiskAddress ! SpecialFloppy.Error => SELECT error FROM IDAlreadyInUse => ERROR Floppy.Error[error: floppyImageInvalid]; ENDCASE]; fileHandle ¬ [floppyHandle, flptr.item[index].fileID]; Floppy. CopyFromPilotFile[ pilotFile: imageFile, floppyFile: fileHandle, firstPilotPage: firstImagePage + flptr.item[index].offset, firstFloppyPage: 0, count: flptr.item[index].size ! Floppy.DataError => BEGIN dataErrorFile ¬ file; dataErrorPage ¬ page; space2Interval.pointer ¬ Space.Unmap[space2Interval.pointer]; space1Interval.pointer ¬ Space.Unmap[space1Interval.pointer]; Floppy.Close[floppyHandle]; GOTO errorExit; END; ]; ENDLOOP; Floppy.SetBootFiles[ volume: floppyHandle, pilotMicrocode: hdr.pilotUCode, diagnosticMicrocode: hdr.diagUCode, germ: hdr.germ, pilotBootFile: hdr.pilotBootFile]; IF hdr.rootFile.file # Floppy.nullFileID THEN Floppy.SetRootFile[hdr.rootFile]; space2Interval.pointer ¬ Space.Unmap[space2Interval.pointer]; space1Interval.pointer ¬ Space.Unmap[space1Interval.pointer]; Floppy.Close[floppyHandle]; EXITS errorExit => ERROR Floppy.DataError[file: dataErrorFile, page: dataErrorPage , vm: NIL]; END; END; CreateInitialMicrocodeWithID: PUBLIC PROCEDURE [ volume: Floppy.VolumeHandle, id: Floppy.FileID, size: Floppy.PageCount, type: File.Type, startingPageNumber: Floppy.PageNumber] RETURNS [file: Floppy.FileHandle] = BEGIN volumeDesc: VolumeDesc; file.volume ¬ volume; volumeDesc ¬ ValidateHandle[volume]; file.file ¬ CreateInitialMicrocodeInternal[ volumeDesc, size, type, startingPageNumber, id]; END; GetDiskAddress: PUBLIC PROCEDURE [ file: Floppy.FileHandle, page: Floppy.PageNumber] RETURNS [diskAddress: FloppyChannel.DiskAddress] = BEGIN volumeDesc: VolumeDesc; GetDiskAddressInternal: ENTRY PROCEDURE [ volumeDesc: VolumeDesc, page: Floppy.PageNumber] = BEGIN ENABLE BEGIN UNWIND => NULL; END; sector: FloppyFormat.Sector; IF ~volumeDesc.open THEN RETURN WITH ERROR Floppy.Error[volumeNotOpen]; sector ¬ FindFile[volumeDesc, file.file].address + FloppyFormat.ConvertPageNumber[ page]; diskAddress ¬ FloppyFormat.SectorToDiskAddress[ sector, volumeDesc.sectorNine.cylinders, volumeDesc.sectorNine.tracksPerCylinder, volumeDesc.sectorNine.sectorsPerTrack]; END; volumeDesc ¬ ValidateHandle[file.volume]; GetDiskAddressInternal[volumeDesc, page]; END; GetFileAtDiskAddress: PUBLIC PROCEDURE [ volume: Floppy.VolumeHandle, diskAddress: FloppyChannel.DiskAddress] RETURNS [file: Floppy.FileHandle, pageNumber: Floppy.PageNumber] = BEGIN volumeDesc: VolumeDesc; GetFileAtDiskAddressInternal: ENTRY PROCEDURE [ volumeDesc: VolumeDesc, diskAddress: FloppyChannel.DiskAddress] = BEGIN ENABLE BEGIN UNWIND => NULL; END; sector: FloppyFormat.Sector; file.volume ¬ volume; IF ~volumeDesc.open THEN RETURN WITH ERROR Floppy.Error[volumeNotOpen]; sector ¬ FloppyFormat.DiskAddressToSector[ diskAddress, volumeDesc.sectorNine.cylinders, volumeDesc.sectorNine.tracksPerCylinder, volumeDesc.sectorNine.sectorsPerTrack]; IF volumeDesc.allocationMap[sector] ~= allocated THEN BEGIN file.file ¬ Floppy.nullFileID; pageNumber ¬ 0; RETURN; END ELSE BEGIN file.file ¬ Floppy.nullFileID; pageNumber ¬ 0; FOR i: CARDINAL IN [0..volumeDesc.fileList.count) DO IF sector IN [volumeDesc.fileList.files[ i].location..volumeDesc.fileList.files[i].location + volumeDesc.fileList.files[i].size) THEN BEGIN file.file ¬ volumeDesc.fileList.files[i].file; pageNumber ¬ sector - volumeDesc.fileList.files[i].location; EXIT; END; ENDLOOP; END; END; volumeDesc ¬ ValidateHandle[volume]; GetFileAtDiskAddressInternal[volumeDesc, diskAddress]; END; GetCurrentNumberOfFiles: PROCEDURE [volume: Floppy.VolumeHandle] RETURNS [currentNumberOfFiles: CARDINAL] = BEGIN volumeDesc: VolumeDesc; GetCurrentNumberOfFilesInternal: ENTRY PROCEDURE [volumeDesc: VolumeDesc] = BEGIN ENABLE BEGIN UNWIND => NULL; END; IF ~volumeDesc.open THEN RETURN WITH ERROR Floppy.Error[volumeNotOpen]; currentNumberOfFiles ¬ volumeDesc.fileList.count; END; volumeDesc ¬ ValidateHandle[volume]; GetCurrentNumberOfFilesInternal[volumeDesc]; END; GetImageAttributes: PUBLIC PROCEDURE [ imageFile: File.File, firstImagePage: File.PageNumber, name: LONG STRING] RETURNS [ maxNumberOfFiles: CARDINAL, currentNumberOfFiles: CARDINAL, density: Floppy.Density[single..double], sides: Floppy.Sides[one..two]] = BEGIN headerPage0Ptr: LONG POINTER TO HeaderPage0; spaceInterval: Space.Interval; labelString: LONG STRING; spaceInterval ¬ Space.Map[[file: imageFile, base: firstImagePage, count: 1]]; headerPage0Ptr ¬ spaceInterval.pointer; labelString ¬ @headerPage0Ptr.labelStringBody; IF name # NIL THEN BEGIN FOR index: CARDINAL IN [0..MIN[name.maxlength, labelString.length]) DO name.text[index] ¬ labelString.text[index]; ENDLOOP; name.length ¬ labelString.length; END; maxNumberOfFiles ¬ headerPage0Ptr.maxNumberOfFiles; currentNumberOfFiles ¬ headerPage0Ptr.currentNumberOfFiles; density ¬ headerPage0Ptr.density; sides ¬ headerPage0Ptr.sides; spaceInterval.pointer ¬ Space.Unmap[spaceInterval.pointer]; END; MakeImage: PUBLIC PROCEDURE [ floppyDrive: CARDINAL, imageFile: File.File, firstImagePage: File.PageNumber] = BEGIN floppyFileInfoPerPage: CARDINAL = Environment.wordsPerPage/SIZE[FloppyFileInfo]; headerPage0Ptr: LONG POINTER TO HeaderPage0; floppyHandle: Floppy.VolumeHandle; space1Interval: Space.Interval; space2Interval: Space.Interval; floppyInfoSeqPtr: FloppyInfoSeqType; labelString: LONG STRING; numOfFiles: CARDINAL; fileHandle: Floppy.FileHandle; floppyID: Floppy.FileID ¬ Floppy.nullFileID; index: CARDINAL ¬ 0; numOfPages: LONG CARDINAL; pages: Floppy.PageCount; pseudoRootPageHandle: Floppy.FileHandle; dataErrorFile: Floppy.FileHandle; dataErrorPage: Floppy.PageNumber; BEGIN OPEN hdr: headerPage0Ptr, flptr: floppyInfoSeqPtr; floppyHandle ¬ Floppy.Open[floppyDrive]; [pages: pages, files: numOfFiles] ¬ PagesForFloppyInfoSeq[ floppyHandle]; numOfPages ¬ pages; space2Interval ¬ Space.Map[[file: imageFile, base: firstImagePage + 1, count: pages]]; floppyInfoSeqPtr ¬ space2Interval.pointer; space1Interval ¬ Space.Map[[file: imageFile, base: firstImagePage, count: 1]]; headerPage0Ptr ¬ space1Interval.pointer; flptr.length ¬ numOfFiles - 1; -- The fileList is not included numOfPages ¬ numOfPages + 1; [file: pseudoRootPageHandle] ¬ SpecialFloppy .GetFileAtDiskAddress[ volume: floppyHandle, diskAddress: PseudoPVRootPageAddress]; fileHandle ¬ [floppyHandle, floppyID]; fileHandle ¬ Floppy.GetNextFile[fileHandle]; FOR index IN [0..flptr.length) DO SELECT fileHandle.file FROM pseudoRootPageHandle.file => hdr.pseudoRootPageIndex ¬ index; hdr.pilotUCode.file => hdr.pilotUCodeAddress ¬ GetDiskAddress[file: fileHandle, page: 0]; hdr.diagUCode.file => hdr.diagUCodeAddress ¬ GetDiskAddress[file: fileHandle, page: 0]; hdr.germ.file => hdr.germAddress ¬ GetDiskAddress[file: fileHandle, page: 0]; hdr.pilotBootFile.file => hdr.pilotBootFileAddress ¬ GetDiskAddress[file: fileHandle, page: 0]; ENDCASE => NULL; [size: flptr.item[index].size, type: flptr.item[index].fileType] ¬ Floppy.GetFileAttributes[fileHandle]; flptr.item[index].fileID ¬ fileHandle.file; flptr.item[index].offset ¬ numOfPages; Floppy.CopyToPilotFile[ floppyFile: fileHandle, pilotFile: imageFile, firstFloppyPage: 0, firstPilotPage: firstImagePage + flptr.item[index].offset, count: flptr.item[index].size ! Floppy.DataError => BEGIN dataErrorFile ¬ file; dataErrorPage ¬ page; space1Interval.pointer ¬ Space.Unmap[space1Interval.pointer]; space2Interval.pointer ¬ Space.Unmap[space2Interval.pointer]; Floppy.Close[floppyHandle]; GOTO errorExit; END; ]; numOfPages ¬ numOfPages + flptr.item[index].size; fileHandle ¬ Floppy.GetNextFile[fileHandle]; ENDLOOP; numOfPages ¬ numOfPages - flptr.item[0].offset; space2Interval.pointer ¬ Space.Unmap[space2Interval.pointer]; labelString ¬ @hdr.labelStringBody; labelString­ ¬ StringBody[length: 0, maxlength: 128, text:]; hdr.seal ¬ ImageSeal; hdr.version ¬ ImageVersion; hdr.currentNumberOfFiles ¬ GetCurrentNumberOfFiles[floppyHandle]; [fileList: hdr.fileList, rootFile: hdr.rootFile, density: hdr.density, sides: hdr.sides, maxFileListEntries: hdr.maxNumberOfFiles] ¬ Floppy.GetAttributes[volume: floppyHandle, labelString: labelString]; [hdr.initialUCode, hdr.pilotUCode, hdr.diagUCode, hdr.germ, hdr.pilotBootFile] ¬ Floppy.GetBootFiles[floppyHandle]; hdr.sizeOfFile ¬ numOfPages; space1Interval.pointer ¬ Space.Unmap[space1Interval.pointer]; Floppy.Close[floppyHandle]; EXITS errorExit => ERROR Floppy.DataError[file: dataErrorFile, page: dataErrorPage, vm: NIL]; END; END; PagesForFloppyInfoSeq: PROCEDURE [ floppyHandle: Floppy.VolumeHandle] RETURNS [pages: File.PageCount, files: CARDINAL] = BEGIN files ¬ GetCurrentNumberOfFiles[floppyHandle]; pages ¬ (files*SIZE[FloppyFileInfo] + Environment.wordsPerPage -1)/ Environment.wordsPerPage; END; PagesForImage: PUBLIC PROCEDURE [floppyDrive: CARDINAL] RETURNS [File.PageCount] = BEGIN floppyHandle: Floppy.VolumeHandle; fileHandle: Floppy.FileHandle; floppyID: Floppy.FileID ¬ Floppy.nullFileID; numOfPages: File.PageCount ¬ 1; -- for the header sizeOfFile: Floppy.PageCount; pages: File.PageCount; floppyHandle ¬ Floppy.Open[floppyDrive]; [pages: pages] ¬ PagesForFloppyInfoSeq[floppyHandle]; numOfPages ¬ numOfPages + pages; fileHandle ¬ [floppyHandle, floppyID]; fileHandle ¬ Floppy.GetNextFile[fileHandle]; WHILE fileHandle.file # Floppy.nullFileID DO [size: sizeOfFile] ¬ Floppy.GetFileAttributes[fileHandle]; numOfPages ¬ numOfPages + LOOPHOLE[sizeOfFile, LONG CARDINAL]; fileHandle ¬ Floppy.GetNextFile[fileHandle]; ENDLOOP; Floppy.Close[floppyHandle]; RETURN[numOfPages]; END; END. LOG Time: 1-Feb-82 15:08:14 by: LXD Created File Time: 9-Feb-82 19:04:53 by: LXD added GetDiskAddress Time: 11-Feb-82 18:05:30 by: LXD added GetFileAtDiskAddress Time: 6-Aug-82 18:47:49 by: AWL Implemented GetFileListAttributes, CreateWithIDAtLocation, CreateInitialMicrocodeWIthID Time: 2-Nov-82 11:15:25 By: EKN Added CreateFloppyFromImage, GetImageAttributes, MakeImage, PagesForImage, PagesForFloppyInfoSeq, FCError. (written by DXG ) Time: 2-Mar-83 15:16:57 By: EKN Updated Space Stuff to Klamath. ChangedGetFileListAttributes to GetCurrentNumberOfFiles. References to SpecialFloppyExtras and FloppyExtras are now SpecialFloppy and Floppy, respectively. Time: 18-Jan-84 15:14:36 By: JXG off by one error Time: 18-Jan-84 17:26:08 By: JXG undid erroneous fix