Apple II
Technical Notes
_____________________________________________________________________________
Developer Technical Support
Apple IIgs
#94: Packing It In (and Out)
Written by: C.K. Haun
September 1990
This Technical Note discusses a potential problem with the Miscellaneous Tools
routine UnPackBytes.
_____________________________________________________________________________
PackBytes and UnPackBytes are handy data compression and expansion routines
built into the Apple IIgs System Software. Using them can dramatically reduce
the amount of space your application uses on disk or in memory, but you need
to understand how these calls work to avoid problems in your applications.
Buffer Size, Buffer Size, Buff, Buff, Buffer Size
There are some situations where the Miscellaneous Tools call UnPackBytes does
not function as expected and can cause your application to loop infinitely
while you're waiting for an unpacking process to finish.
The following packed data and code (in APW assembly) demonstrates the problem.
It shows a small routine that unpacks data in two steps, simulating the
situation in many applications where an arbitrary amount of data is unpacked
in a variable amount of unpacking actions, depending on the results of the
last unpack pass.
UnPackBuffer ds 160 ; area to unpack the data to
UnPackBufferPtr dc i4'UnPackBuffer' ; pointer to unpacking buffer
UnPackBufferSize ds 2
temp ds 2
PackedData dc h'FFFFFFFF'
EndPackData anop
PackLength dc i2'EndPackData-PackedData' ; how many bytes of packed data
* In packbytes format $FFFF means '64 repeats of the next byte ($FF) taken as
* 4 bytes' as described on page 14-39 of Toolbox Reference, so
* this data should unpack into 512 $FF bytes
* The following code loops infinitely
lda #160 ; Unpack buffer size
sta UnPackBufferSize
UnPackLoop pea 0 ; return space
pushlong #PackedData ; pointer to packed data
pea 2 ; size of the packed data,
; unpack two bytes
; at a time
pushlong #UnPackBufferPtr ; pointer to pointer to
; unpacking buffer
pushlong #UnPackBufferSize ; pointer to word with the
; size of the unpacking buffer
_UnPackBytes
pla ; returns 0 bytes unpacked
sta temp
lda PackLength
sec
sbc temp ; subtracting it from our
; known
sta PackLength ; length of packed data
bne UnPackLoop ; this is always be non-zero
The problem is in the data and the buffer size. UnPackBytes is being told to
unpack two bytes ($FFFF), which generate 256 bytes of unpacked data, into a
160-byte buffer. Instead of reporting an error with this condition,
UnPackBytes instead just does nothing and passes back zero as the returned
number of bytes unpacked. If you are relying on the unpacked byte count
returned to control your unpacking loop, then you may encounter this problem.
UnPackBytes can be used to unpack in multiple steps, of course, but it cannot
unpack a partial record. It cannot unpack 160 bytes of the 256 bytes
specified in this record because UnPackBytes does not maintain any state
information, so it must unpack full records or do nothing. If the buffer had
been 256 bytes, this call would have succeeded.
The Fix
Fortunately, it's easy to avoid this situation if you know that it can exist.
Simply, always supply UnPackBytes with a buffer that is big enough for it to
unpack at least two bytes (a flag or count byte and a data byte). The largest
value of a flag or count word possible is $FF, 64 repeats of the next byte
taken as four bytes, which generates 256 unpacked bytes. So always give
UnPackBytes a 256-byte long output buffer and you should never encounter this
problem.
Check Your Current Applications
Please check your current applications to see if you could encounter this
problem. One of the most likely places for this error to occur is in
applications that process Apple Preferred (file type $C0, auxiliary type
$0002) pictures. While most pictures currently available are screen-width or
less (160 bytes or less per scan line), the Apple Preferred format and
QuickDraw II both support pictures that are wider than the current Apple IIgs
screen. If someone has created a picture with a PixelsPerScanLine value of
1,280 with a ModeWord of $0080, it would generate a scan line that was 320
bytes long. If a scan line in this hypothetical picture were all white, for
example, the first two bytes of the packed scan line would be $FFFF, and
applications that assume a standard maximum 160 bytes per scan line would not
handle this correctly.
But That's Not All...
UnPackBytes has some other buffering problems of which you need to be aware.
The size and location of the input buffer (the buffer containing your packed
data) can also cause problems.
Note: These problems only occur if you are doing multipass unpacks. If
you always unpack a packed data range in one pass (with one call
to UnPackBytes for the whole data set) then you are not affected
by these problems, and the restrictions described herein do not
apply.
Multipass Restrictions
When performing a multipass unpack (as described on pp. 14-43..44 of the Apple
IIgs Toolbox Reference, Volume 1) the packed data needs to follow two rules.
Rule 1: Your packed data buffer cannot cross a bank boundary.
Rule 2: Your packed data buffer needs to be at least 65 bytes longer
than the actual size of the data.
These rules are required by a bug in UnPackBytes. When UnPackBytes begins to
unpack a record, it checks the record data to see if there are enough bytes in
the current source buffer to unpack the number of bytes requested in the
record header (described on pg. 14-39 of the Apple IIgs Toolbox Reference,
Volume 1). If there are not enough bytes left for the current record (i.e.,
the header says to process 63 bytes, and there are only 30 left in the
buffer), UnPackBytes returns to the caller. The caller then adjusts the
source buffer for the next pass based on the amount of actual bytes unpacked,
so the bytes left over from the last pass get processed the next time.
The problem occurs when the partial record is close to the end of a bank.
When UnPackBytes checks to see if there is enough data left in the buffer, the
check is flawed when the real end of the buffer is near the end of a bank, and
a complete copy of the partial record would extend into the next bank.
UnPackBytes erroneously thinks that the record is complete, and happily
unpacks the remaining actual packed data, plus random information from the
next bank. It continues to unpack nonsense data until it fills the unpacking
buffer and the number of bytes unpacked returned by the UnPackBytes call is
greater than the bufferSize parameter passed as input.
To prevent this bug from occurring, you need to make sure that the buffer for
the packed data is at least one record length away from the end of a memory
bank. Since the largest packed data record is one flag byte and 64 data
bytes, adding 65 bytes to the end of your buffer does the trick. This ensures
that your packed data is 65 bytes away from the end.
Following is an example of a safe way to prepare your packed data buffer for
multipass unpacking, in APW assembly:
* Some data space
myCallBlock dc i2'2' ; two parameters
fileRefNum ds 2 ; file reference number
EOFreturned ds 4 ; file length returned by this call
myIDNumber ds 2 ; your application memory manager ID number
* assume that a packed data file is open, and it's a plain packed screen
* image, not over 32K
jsl $E100A8 ; ask GS/OS for the length of the
data
dc i2'$2019' ; Get_EOF call
dc i4'myCallBlock'
* Now we need a handle to read it into
pha
pha ; return space
pea 0 ; size, high word
lda EOFreturned ; the actual size of the packed data
sta actualPackDataSize
clc
adc #65 ; ask for a handle 65 bytes longer
; than the data
pha
lda myIDnumber ; Memory Manager ID for your
; application
pha
pea $8010 ; attrLocked and attrNoCross
pea 0
pea 0 ; anywhere
_NewHandle ; get the handle
Now you have a handle 65 bytes longer than your data that does not cross a
bank boundary. You are ready to read in the data and perform a multipass
unpack.
PackBytes Buffers Count Too
PackBytes can also cause you problems if you do not plan for the worst-case
situation. Unlike the other toolbox compression routine ACECompress,
PackBytes is not guaranteed to shrink the source data. In fact, your data
size may actually grow after a PackBytes call.
If you pass a data stream of 64 bytes, all with different values, to
PackBytes, PackBytes puts 65 bytes in your output buffer--the 64 original data
bytes and the flag byte of $3F, indicating "64 bytes follow, all different."
Unless you preprocess or analyze your data before packing to avoid this
situation, make sure your output buffer is large enough to hold the worst case
situation, one additional byte generated for every 64 bytes passed to
PackBytes for compression.
Further Reference
_____________________________________________________________________________
o Apple IIgs Toolbox Reference, Volumes 1-3
o File Type Note for File Type $C0, Auxiliary Type $0002, Apple Preferred
Format