TriOS-alt/zubehör/sphinx/spinx100225-ori/sphinx2/sxfs.spn

871 lines
44 KiB
Plaintext
Raw Normal View History

2010-11-26 23:58:06 +01:00
SXTVRENDEZVOUS = $8000 - 4
SXKBRENDEZVOUS = SXTVRENDEZVOUS - 4
SDSPIRENDEZVOUS = SXKBRENDEZVOUS - 3 * 4
SXFS2RENDEZVOUS = SDSPIRENDEZVOUS - 4 * 4 ' four rendezvous variables
SXFSRENDEZVOUS = SXFS2RENDEZVOUS - 4 * 4 ' four rendezvous variables
METADATABUFFER = SXFSRENDEZVOUS - 512
_free = ($8000 - METADATABUFFER) / 4
{
sxfsrendezvous sdspirendezvous
--------+ +--------+ +-------+
| <===> command <===> | | <=====> command <=====> | |
Spin | | | | |
routines| <===> param0 <====> | SXFS | <=====> param <=====> | SDSPI |
| | cog | | cog |
| ----> param1 -----> | | ------> blockno ------> | |
| | | +-------+
| ----> param2 -----> | |
--------+ | | sxfs2rendezvous
| | +-------+
| | <====> command <======> | |
| | | | <==> (sxfsrendezvous)
| | <====> param0 <=======> | SXFS2 |
| | | cog | <==> (sdspirendezvous)
| | | |
+--------+ +-------+
SXFS cog handles file reading: open, read, execute, close(?).
SXFS2 cog, under SXFS's direction, handles file writing: open, write, close.
SDSPI cog handles SD card sector reading and writing.
SXFS cog communicates with main program via sxfsrendezvous and Spin interface routines.
SXFS cog communicates with SXFS2 cog via sxfs2rendezvous.
SXFS cog communicates with SDSPI cog via sdspirendezvous.
-1: eof, general error
-100: bad command
-101: not FAT16
-102: file not found
-104: bad mode
-106: file not closed
-107: directory full
-109: file not properly opened
-110: FAT full.
-113: bad filename
}
obj
sxfs2: "sxfs2"
sdspi : "sxsdspiq"
pub Start( sdPin ) | pbrSector
' Determine if sxfs cog and, by association, sxfs2 and sdspi cogs, are already running.
' If not, start them up.
' Returns true if the cogs needed to be started, false if the cogs were already running.
ifnot Ping
result~~
' start sdspi cog and initialize SD card
long[pSdspiCommand] := "I"
sdspi.start( sdPin, pSdspiCommand )
repeat while long[pSdspiCommand]
if long[pSdspiParam]
abort long[pSdspiParam]
pbrSector := ReadPbr
' start sxfs cog
long[pCommand] := "?"
if cognew( @sxfsEntry, 0 ) < 0
abort -1
repeat while long[pCommand]
' start sxfs2 cog
sxfs2.Start( pbrSector )
pri ReadPbr : pbrSector
repeat
sdspi.readblock( pbrSector, pMetadataBuffer )
if bytecompare( pMetadataBuffer+$36, string("FAT16"), 5 )
quit
if pbrSector
abort -101 ' not FAT16
pbrSector := PeekL( pMetadataBuffer + $1c6 )
bytesPerSector := PeekW( pMetadataBuffer + $0b )
sectorsPerCluster := byte[pMetadataBuffer][$0d]
clusterShift := >| sectorsPerCluster - 1
reservedSectors := PeekW( pMetadataBuffer + $0e )
numberOfFats := byte[pMetadataBuffer][$10]
sectorsPerFat := PeekW( pMetadataBuffer + $16 )
RootDirEntries := PeekW( pMetadataBuffer + $11 )
fatSector0 := pbrSector + reservedSectors
dirSector0 := fatSector0 + numberOfFats * sectorsPerFat
dataSector0 := dirSector0 + rootDirEntries >> 4
pri bytecompare( _p, _q, _n )
repeat _n
if byte[_p++] <> byte[_q++]
return
return true
pri PeekW( a )
return byte[a++] + byte[a] << 8
pri PeekL( a )
return byte[a++] + byte[a++] << 8 + byte[a++] << 16 + byte[a] << 24
pri Ping
{{
returns 1 if bmfs cog is active, 0 otherwise.
}}
long[pCommand] := "?"
if long[pCommand]
return 0
else
return 1
con
SECTORSIZE = 512
SECTORSHIFT = 9
DIRSIZE = 32
con
' filestuff definitions
' filestuff must be long-aligned
{ long } _length = 0
{ long } _leftInFile = 4
{ long } _sopDir = 8 ' sector/offset "pointer" to directory entry
{ long } _sopTail = 12 ' sector/offset "pointer" to last cluster# in FAT chain
{ word } _cluster = 16
{ word } _sector = 18 ' sector# within cluster; ranges from 0 to sectorsPerCluster-1
{ word } _bp = 20 ' byte pointer (actually an index into the sector buffer); range [0..511]
{ word } _mode = 22 ' 0: closed; "R": open for reading; "W": open for writing.
_buffer = 24 ' 512-byte buffer starts here
SIZEOFFILESTUFF = SECTORSIZE + 24
dat
{{
command param0 param1 param2
"?" -- -- -- Do nothing, but acknowledge to indicate that sxfs cog is active.
"O" filestuff filename mode Open a file, populate filestuff fields. mode is "R" or "W".
"R" filestuff buffer num Reads num bytes into buffer from file specified by filestuff.
"W" filestuff buffer num Writes num bytes from buffer to file specified by filestuff.
"C" filestuff -- -- Closes file specified by filestuff.
"X" filestuff execmode cog Executes file.
}}
org 0
SXFSEntry
AckNoReturnCode
mov temp, #0 ' clear return code
AckReturnCode ' enter here with temp = return code
wrlong temp, pParam0
mov temp, #0
wrlong temp, pCommand ' clear command to signal operation complete.
:ready ' wait for new command
rdlong temp, pCommand wz
if_z jmp #:ready
cmp temp, #"?" wz
if_e jmp #AckNoReturnCode
cmp temp, #"O" wz
if_e jmp #Open
cmp temp, #"R" wz
if_e jmp #Read
cmp temp, #"W" wz
if_e jmp #Write
cmp temp, #"C" wz
if_e jmp #Close
cmp temp, #"X" wz
if_e jmp #Execute
' else
neg temp, #100 ' -100: bad command
jmp #AckReturnCode
'*********************************************************************************************************************
'* *
'* Open stuff *
'* *
'*********************************************************************************************************************
Open ' ( param0 = pFilestuff, param1 = pFilename, param2 = mode )
{
Return value:
File found File not found Error
mode = "R" 0 1 < 0
mode = "W" 0 0 < 0
Mode W will either open an existing file or create a new file.
}
rdword pFilestuff, pParam0
rdword pFilename, pParam1
mov p, pFilestuff
add p, #_mode
rdword temp, p wz
if_nz neg temp, #106 ' -106: file not closed
if_nz jmp #AckReturnCode
call #MassageFilename
rdword temp, pParam2
cmp temp, #"R" wz
if_e jmp #:openForReading
cmp temp, #"W" wz
if_e jmp #:openForWriting
neg temp, #104 ' -104: bad mode
jmp #AckReturnCode
:openForReading
call #FindFile
mov p, pFilestuff
add p, #_sopDir ' pFilestuff->sopDir = 0 => file not found
rdlong temp, p wz
if_z mov temp, #1 ' return 1 to indicate file not found
if_z jmp #AckReturnCode
mov p, sop ' sop sector is still in memory
and p, #$1ff
add p, pMetadataBuffer
add p, #$1a ' point to cluster information
rdword temp, p
mov q, pFilestuff
add q, #_cluster
wrword temp, q
add p, #2 ' point to length information
rdlong temp, p
sub q, #_cluster-_length ' q points to length
wrlong temp, q
add q, #_leftInFile-_length ' q points to leftInFile
wrlong temp, q
add q, #_sector-_leftInFile ' q points to sector
mov temp, #0
wrword temp, q
add q, #_bp-_sector ' q points to bp
wrword k512, q
mov p, pFilestuff
add p, #_mode
mov temp, #"R"
wrword temp, p ' save mode
jmp #AckNoReturnCode
:openForWriting
mov sop, dirSector0 ' sop "points" to directory entries:
shl sop, #9 ' incremented by 32 each time through the loop.
mov sopDir, #0 ' sopDir "points" to the directory entry
' that will be used or reused for the file being opened.
mov i, rootDirEntries
:forEachDirEntry ' Loop through the directory entries, looking for a deleted entry,
' an empty entry, or an entry that matches pFilename.
mov sector, sop
shr sector, #9
call #ReadMetadataSector
mov p, sop
and p, #$1ff
add p, pMetadataBuffer ' p points to the current directory entry
add p, #$0b
rdbyte temp, p ' p[$0b] = attribute byte
sub p, #$0b
and temp, #$02 wz ' If hidden,
if_nz jmp #:next ' skip. Hidden dir entries include long name entries.
rdbyte temp, p ' p[0]
cmp temp, #$e5 wz ' = $e5? (deleted directory entry?)
if_e cmp sopDir, #0 wz ' save pointer in sopDir (just the first time)
if_e mov sopDir, sop
tjz temp, #:quit ' empty directory entry? We're done
mov q, pFilename ' matching filename? Done.
mov n, #11
call #ByteComp
if_e jmp #:quit
:next
add sop, #DIRSIZE
djnz i, #:forEachDirEntry
:quit
cmp i, #0 wz ' did we go through the whole directory?
if_z neg temp, #107 ' -107: directory full
if_z jmp #AckReturnCode
cmp sopDir, #0 wz ' if sopDir hasn't been set yet,
if_e mov sopDir, sop ' set it to sop.
mov p, pFilestuff
add p, #_sopDir
wrlong sopDir, p ' pFilestuff->sopDir := sopDir
add p, #_sopTail-_sopDir
add sopDir, #$1a
wrlong sopDir, p ' pFilestuff->sopTail := sopDir + $1a
' (points to directory entry's cluster field).
mov sector, sopDir
shr sector, #9 ' Get the chosen directory entry into memory
call #ReadMetadataSector ' if it isn't already.
mov p, sopDir ' Recall that sopDir was changed
and p, #$1ff ' to point to the cluster field,
add p, pMetadataBuffer ' so p now points to the cluster field in the buffer.
rdword cluster, p ' Save the cluster field.
mov temp, #0
wrword temp, p ' Zero the cluster field.
sub p, #$1a ' Point to start of directory entry
rdbyte temp, p ' Check to see if this was a deleted dir entry.
cmp temp, #$e5 wz ' If it was, we definitely do not want to zero the
if_e mov cluster, #0 ' cluster chain, so set cluster to 0.
mov q, pFilename ' Store the filename in the directory entry.
mov n, #11
call #ByteCopy
mov temp, #$20 ' Set the attribute byte (immediately following name)
wrbyte temp, p
add p, #$10-$0b ' Point to creation date.
mov temp, aDate ' 2001/01/01
wrword temp, p
add p, #$12-$10 ' Point to last access date.
wrword temp, p
add p, #$18-$12 ' Point to last update date.
wrword temp, p
mov dirty, #1 ' We've modified metadata.
tjz cluster, #:done
:forEachClusterInList ' Traverse the cluster list and zero each link.
mov sector, cluster
shr sector, #8
add sector, fatSector0
call #ReadMetadataSector ' Read FAT sector for current cluster
mov p, cluster
and p, #$ff
shl p, #1
add p, pMetadataBuffer ' Point p at cluster entry in FAT
rdword cluster, p ' Get next cluster link.
cmp cluster, #1 wc, wz ' Break if cluster <= 1
if_be jmp #:done
mov temp, #0
wrword temp, p ' Zero the cluster link.
mov dirty, #1 ' We've modified metadata.
cmp cluster, kfff0 wc, wz ' repeat unless new cluster >= $fff0
if_b jmp #:forEachClusterInList
:done
mov p, pFilestuff
add p, #_sector
mov temp, #0
wrword temp, p ' pFilestuff->sector := 0
add p, #_bp-_sector
wrword temp, p ' pFilestuff->bp := 0
mov p, pFilestuff
mov temp, #0
wrlong temp, p ' pFilestuff->length := 0
add p, #_mode
mov temp, #"W"
wrword temp, p ' save mode
jmp #AckNoReturnCode
MassageFilename
{
Take the null-terminated 8.3 filename pointed to by pFilename,
move it into memory starting at pFilestuff->buffer and expand it to 11 characters;
change pFilename to point to the expanded filename.
}
mov p, pFilestuff
add p, #_buffer
mov q, p
add p, #1
mov temp, #" "
wrbyte temp, q
mov n, #10
call #ByteCopy
mov q, pFilename ' q is src
mov p, pFilestuff ' p is dst
add p, #_buffer
mov i, #9 ' Copy up to 8 chars (stop at . or null).
:upTo8
rdbyte temp, q
add q, #1
tjz temp, #:done
cmp temp, #"." wz
if_z jmp #:dot
call #ValidateChar
wrbyte temp, p
add p, #1
djnz i, #:upTo8
' If we fall through, we've copied 9 characters (tsk tsk).
neg temp, #113 ' -113: bad filename
jmp #AckReturnCode
:dot
mov p, pFilestuff
add p, #_buffer+8
mov i, #4 ' Copy up to 3 chars (stop at null).
:upTo3
rdbyte temp, q
add q, #1
tjz temp, #:done
call #ValidateChar
wrbyte temp, p
add p, #1
djnz i, #:upTo3
' If we fall through, we've copied 4 characters (tsk tsk ).
neg temp, #113 ' -113: bad filename
jmp #AckReturnCode
:done
mov pFilename, pFilestuff
add pFilename, #_buffer
MassageFilename_ret ret
FindFile
{
pFilename points to 11-character buffer (e.g. "BLAH TXT").
pFilestuff points to a filestuff structure.
Return value:
If file is found, pFilestuff->sopDir is disk address of directory entry.
If file is not found, pFilestuff->sopDir is 0.
}
mov sop, dirSector0 ' sop starts at dirSector0<<9, counts up by DIRSIZE
shl sop, #SECTORSHIFT
mov i, rootDirEntries
:loop
mov sector, sop
shr sector, #9
call #readMetadataSector
mov p, sop
and p, #$1ff
add p, pMetadataBuffer
rdbyte temp, p wz
if_z jmp #:notfound
add p, #$0b
rdbyte temp, p ' p[$0b] = attribute byte
sub p, #$0b
and temp, #$02 wz ' If hidden,
if_nz jmp #:next ' skip. Hidden dir entries include long name entries.
mov q, pFilename
mov n, #11
call #ByteComp
if_e jmp #:found
:next
add sop, #DIRSIZE
djnz i, #:loop
:notfound
mov sop, #0
:found
mov p, pFilestuff
add p, #_sopDir
wrlong sop, p
FindFile_ret ret
'*********************************************************************************************************************
'* *
'* Read stuff *
'* *
'*********************************************************************************************************************
Read ' ( param0 = pFilestuff, param1 = ptr, param2 = n )
{
Read n bytes from file described by pIoblock into memory starting at p.
Returns number of bytes actually read or -1 if attempting to read past EOF.
}
rdlong pFilestuff, pParam0
rdlong destPtr, pParam1
rdlong nBytes, pParam2
mov p, pFilestuff ' Verify that file was opened for reading.
add p, #_mode
rdword temp, p
cmp temp, #"R" wz
if_ne neg temp, #109 ' -109: file not properly opened
if_ne jmp #AckReturnCode
sub p, #_mode-_leftInFile
' Adjust nBytes depending on how much is left to read in file.
' E.g. if we're 10 bytes from EOF and we try to read 15 bytes,
' just read 10. If we're at EOF and try to read 15 bytes, return -1.
rdlong temp, p
max nBytes, temp ' nBytes is lesser of nBytes and leftInFile
tjnz temp, #:x
neg temp, #1 ' -1: eof
jmp #ackReturnCode
:x
mov retcode, nBytes
:while
mov leftInSector, k512 ' leftInSector = 512 - pFilestuff->bp
mov p, pFilestuff
add p, #_bp
rdword temp, p ' temp = bp
sub leftInSector, temp
cmp leftInSector, nBytes wc, wz
if_ae jmp #:endwhile
mov p, destPtr
mov q, pFilestuff
add q, #_buffer ' q points to buffer area
add q, temp ' offset by bp (= temp)
mov n, leftInSector
call #ByteCopy ' bytemove( p, q, n )
add destPtr, leftInSector ' destPtr += leftInSector
sub nBytes, leftInSector ' nBytes -= leftInSector
mov p, pFilestuff ' long[pIoblock+_leftInFile] -= leftInSector
add p, #_leftInFile
rdlong temp, p
sub temp, leftInSector
wrlong temp, p
' sdspi.readblock( dataSector0 + (word[pFilestuff+_cluster] - 2) << clusterShift + word[pFilestuff+_sector], pFilestuff+_buffer )
add p, #_cluster-_leftInFile
rdword temp, p ' temp = (cluster
sub temp, #2 ' - 2)
shl temp, clusterShift ' << clusterShift
add temp, dataSector0 ' + dataSector0
add p, #_sector-_cluster
rdword temp1, p
add temp, temp1 ' + cluster
wrlong temp, pSdspiBlockno ' Prepare to read sector
add p, #_buffer-_sector
wrlong p, pSdspiParam
mov temp, #"R"
call #SdspiCommand
' if ++word[pFilestuff+_sector] == sectorsPerCluster
mov p, pFilestuff
add p, #_sector
rdword temp, p
add temp, #1
wrword temp, p
cmp temp, sectorsPerCluster wz
if_ne jmp #:y
' word[pIoblock+_sector]~
mov temp, #0
wrword temp, p
' word[pFilestuff+_cluster] := NextCluster( word[pFilestuff+_cluster] )
sub p, #_sector-_cluster
rdword cluster, p
call #NextCluster '( cluster )
mov p, pFilestuff
add p, #_cluster
wrword cluster, p
:y
' word[pFilestuff+_bp]~
mov p, pFilestuff
add p, #_bp
mov temp, #0
wrword temp, p
jmp #:while
:endwhile
' bytemove( destPtr, pIoblock + word[pIoblock+_bp], n )
mov p, destPtr
mov q, pFilestuff
add q, #_bp
rdword temp, q
add q, #_buffer-_bp
add q, temp
mov n, nBytes
call #ByteCopy
' long[pIoblock+_leftInFile] -= n
mov p, pFilestuff
add p, #_leftInFile
rdlong temp, p
sub temp, nBytes
wrlong temp, p
' word[pIoblock+_bp] += n
add p, #_bp-_leftInFile
rdword temp, p
add temp, nBytes
wrword temp, p
mov temp, retcode
jmp #AckReturnCode
NextCluster ' ( cluster )
{
Given cluster, determines next cluster in FAT.
Result in cluster.
}
cmp cluster, #1 wc, wz
if_be jmp #NextCluster_ret
cmp cluster, kfff0 wc, wz
if_ae jmp #NextCluster_ret
mov sector, cluster
shr sector, #8
add sector, fatSector0
call #ReadMetadataSector
mov p, cluster
and p, #$ff
shl p, #1
add p, pMetadataBuffer
rdword cluster, p
NextCluster_ret ret
kfff0 long $fff0
'*********************************************************************************************************************
'* *
'* Write stuff *
'* *
'*********************************************************************************************************************
Write
rdlong p, pParam0 ' p = pFilestuff
add p, #_mode ' Verify that file was opened for writing.
rdword temp, p
cmp temp, #"W" wz
if_ne neg temp, #109 ' -109: file not properly opened
if_ne jmp #AckReturnCode
call #FlushMetadataSector
neg currentsector, #1 ' invalidate current sector 'cuz we're about to hand the reins
' to sxfs2 cog.
mov temp, #"W"
wrlong temp, pSxfs2Command
:wait rdlong temp, pSxfs2Command wz
if_nz jmp #:wait
rdlong temp, pSxfs2Param
jmp #AckReturnCode
'*********************************************************************************************************************
'* *
'* Close stuff *
'* *
'*********************************************************************************************************************
Close ' ( param0 = pFilestuff )
rdlong pFilestuff, pParam0
mov p, pFilestuff
add p, #_mode
rdword temp, p
cmp temp, #"W" wz
if_e jmp #:closew
' Files opened in mode "W" need special closing code, but for mode "R" or anything else
' (like file not even open) we can just do this:
mov temp, #0
wrword temp, p ' clear mode
jmp #AckNoReturnCode ' and we're done
:closew
mov temp, #0 ' clear mode
wrword temp, p
call #FlushMetadataSector
neg currentsector, #1 ' invalidate current sector 'cuz we're about to hand the reins
' to sxfs2 cog.
mov temp, #"C"
wrlong temp, pSxfs2Command
:wait rdlong temp, pSxfs2Command wz
if_nz jmp #:wait
rdlong temp, pSxfs2Param
jmp #AckReturnCode
'*********************************************************************************************************************
'* *
'* Execute stuff *
'* *
'*********************************************************************************************************************
Execute ' ( pFilestuff, execmode, cogid )
call #FlushMetadataSector
neg currentsector, #1 ' invalidate current sector 'cuz we're about to hand the reins
' to sxfs2 cog.
mov temp, #"X"
wrlong temp, pSxfs2Command
:wait rdlong temp, pSxfs2Command wz
if_nz jmp #:wait
rdlong temp, pSxfs2Param
jmp #AckReturnCode
'*********************************************************************************************************************
'* *
'* Utility stuff *
'* *
'*********************************************************************************************************************
ByteCopy ' ( p, q, n )
tjz n, #ByteCopy_ret
:loop rdbyte temp, q
add q, #1
wrbyte temp, p
add p, #1
djnz n, #:loop
ByteCopy_ret ret
ByteComp ' ( p, q, n )
{
Compares j bytes starting at p and q.
Returns Z if they match, NZ if they differ.
Destroys p, q, and j.
}
rdbyte temp, p
add p, #1
rdbyte temp1, q
add q, #1
cmp temp, temp1 wz
if_z djnz n, #ByteComp
ByteComp_ret ret
ValidateChar
{
Make sure that temp is a valid filename character (for our purposes, 0-9, A-Z, _).
}
cmp temp, #"a" wc, wz
if_b jmp #:notLowerCase
cmp temp, #"z" wc, wz
if_a jmp #:notLowerCase
sub temp, #"a"-"A" ' convert to upper-case
jmp #ValidateChar_ret
:notLowerCase
cmp temp, #"A" wc, wz
if_b jmp #:notAlpha
cmp temp, #"Z" wc, wz
if_be jmp #ValidateChar_ret
:notAlpha
cmp temp, #"0" wc, wz
if_b jmp #:notNumeric
cmp temp, #"9" wc, wz
if_be jmp #ValidateChar_ret
:notNumeric
cmp temp, #"_" wz
if_e jmp #ValidateChar_ret
mov temp, #113 ' -113: bad filename
jmp #AckReturnCode
ValidateChar_ret ret
ReadMetadataSector ' ( sector )
call #FlushMetadataSector
cmp sector, currentSector wz
if_e jmp #ReadMetadataSector_ret
wrlong pMetadataBuffer, pSdspiParam
wrlong sector, pSdspiBlockno
mov currentSector, sector
mov temp, #"R"
call #SdspiCommand
ReadMetadataSector_ret ret
FlushMetadataSector
tjz dirty, #FlushMetadataSector_ret
mov dirty, #0
' write current sector
wrlong pMetadataBuffer, pSdspiParam
wrlong currentSector, pSdspiBlockno
mov temp, #"W"
call #SdspiCommand
FlushMetadataSector_ret ret
SdspiCommand
wrlong temp, pSdspiCommand
:wait
rdlong temp, pSdspiCommand wz
if_nz jmp #:wait
rdlong temp, pSdspiParam wz
if_nz jmp #AckReturnCode
SdspiCommand_ret ret
kFAT1 long "F" + "A"<<8 + "T"<<16 + "1"<<24
k512 long 512
dirty long 0
currentSector long -1
aDate long $2a21
pMetadataBuffer long METADATABUFFER
pCommand long SXFSRENDEZVOUS+0
pParam0 long SXFSRENDEZVOUS+4
pParam1 long SXFSRENDEZVOUS+8
pParam2 long SXFSRENDEZVOUS+12
pSdspiCommand long SDSPIRENDEZVOUS+0
pSdspiParam long SDSPIRENDEZVOUS+4
pSdspiBlockno long SDSPIRENDEZVOUS+8
pSxfs2Command long SXFS2RENDEZVOUS+0
pSxfs2Param long SXFS2RENDEZVOUS+4
bytesPerSector long 0
sectorsPerCluster long 0
clusterShift long 0
reservedSectors long 0
numberOfFats long 0
sectorsPerFat long 0
rootDirEntries long 0
fatSector0 long 0
dirSector0 long 0
dataSector0 long 0
temp res 1
temp1 res 1
i res 1
j res 1
n res 1
p res 1
q res 1
sop res 1
byte4 res 0
sopDir res 1
sector res 1
cluster res 1
pFilename res 1
pFilestuff res 1
destPtr res 1
nBytes res 1
leftInFile res 1
leftInSector res 1
retcode res 1
fit
{{
Copyright (c) 2009 Michael Park
+------------------------------------------------------------------------------------------------------------------------------+
| TERMS OF USE: MIT 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. |
+------------------------------------------------------------------------------------------------------------------------------+
}}