← Back to Technotes

#94: Packing It In (and Out)

Author: C.K. Haun
Year: 1990

... discusses a potential problem with the Miscellaneous Tools routine UnPackBytes.

View raw text file

Apple II
Technical Notes
_____________________________________________________________________________
                                                  Developer Technical Support

Apple IIgs
#94:    Packing It In (and Out)

Written by:    C.K. Haun <TR>                                  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