JQCODING - Superfast/Supertiny Compression/Encryption library for VXers!
(c) 1998 by Jacky Qwerty/29A.


Contents

1. Description
2. Compiling notes
3. Linking notes
4. API Function reference
  4.1. jq_encode
  4.2. jq_decode
5. ASM Source code
  5.1. Inclusion at assembly time
  5.2. Inclusion at link time
6. HLL support
  6.1. Static library makefile
  6.2. C/C++ Header file
7. Special considerations
8. Examples
  8.1. Compression example
  8.2. Decompression example
9. Disclaimer


1. Description

This is very powerful super(tiny/fast) compresion/encryption tool for VXers!
It consists of both compressor/encryptor and decompresor/decryptor routines!
Compresor and decompresor routines are only 9Ah/7Ah bytes long respectively!
Compression ratio is much better than RLE and acceptably good compared with
other compresors. Compresion algorithm is not based on LZ dictionaries, nor
huffman coding, nor any other popular algorithm, it rather implements a new
compresion technique which favors code size and both compresion speed/ratio!

Both routines were intended to be included together into an ASM source file
(see 5.1.), but you could prefer to include them at link time into your exe
(see 5.2.), you choose. For this latter purpose there's a makefile provided
(see 6.1.) in order to build the static library. If you intend to use these
functions from a HLL such as C/C++, you need to include such static library
at link time plus the C/C++ header file at compile time (see 6.2). This and
other related files are included too in the file section of the zine. Enjoy!


2. Compiling notes

If you intend to include the JQCODING functions at assembly time, read on.
ASM Compiler must support HLL (High-Level Language) constructions such
as .if, .repeat, .until, etc to implement equivalent ASM control flow
instructions. This improves readability and eliminates the use of many
labels throughout the source. Use MASM 6.xx, although TASM 5.x could
also do the job as well. I actually haven't tested this, though.


3. Linking notes

If you intend to include the JQCODING functions at link time, read on.
Exported functions use 'stdcall' calling convention: Parameters are
pushed from last to first (or right to left if you prefer), callee
(not caller) cleans the stack, function names are case sensitive
starting with underscore and ending with @n where n is the total
number of *bytes* (in decimal) pushed as arguments to the stack.


4. API Function reference

  4.1. jq_encode

Compresses 'in_len' bytes from the input buffer pointed by 'in' and
writes the compressed stream to the output buffer pointed by 'out'.


C/C++ syntax:

  unsigned long
  __stdcall
  jq_encode(void          *out,           /* output stream ptr */
            const void    *in,            /* input stream ptr */
            unsigned long  in_len,        /* input stream length */
            void          *mem64k);       /* work mem ptr */


Parameters:

  out           Pointer to the output buffer where the compressed
                stream will be written to.

  in            Pointer to the input buffer where the uncompressed
                stream will be read from.

  in_len        Length in bytes of the input stream to be
                compressed.

  mem64k        Pointer to a 64Kb memory buffer, already
                allocated prior to the call.


Return value:

  Length in bytes of the resulting compressed stream.

  X86 specific:

    Return value is copied into the EAX register.
    All other registers are preserved except EFLAGS and EDX register.


  4.2. jq_decode

Decompress 'in_len' bytes from the input buffer pointed by 'in' and
writes the decompressed stream to the output buffer pointed by 'out'.


C/C++ syntax:

  unsigned long
  __stdcall
  jq_decode(void          *out,           /* output stream ptr */
            void          *in,            /* input stream ptr */
            unsigned long  in_len,        /* input stream length */
            void          *mem64k);       /* work mem ptr */


Parameters:

  out           Pointer to the output buffer where the decompressed
                stream will be written to.

  in            Pointer to the input buffer where the compressed
                stream will be read from.

  in_len        Length in bytes of the input stream to be
                decompressed

  mem64k        Pointer to a 64Kb memory buffer, already
                allocated prior to the call.


Return value:

  Length in bytes of the resulting decompressed stream.

  X86 specific:

    Return value is copied into the EAX register.
    All other registers are preserved except the EFLAGS register.


5. ASM Source code

  5.1. Inclusion at assembly time

Too add compression/decompression to your ASM projects at assembly time
you need to add JQCODING ASM source code to your own ASM source. Just
copy the following two ASM routines 'jq_encode' and 'jq_decode' and
paste them into your ASM source file or simply include the JQCODING.ASM
file (in the file section of the zine) into your ASM source (see 8.).

Remember however, that both JQCODING functions expect to find parameters
onto the stack, so you should push them in the appropriate order before
calling the functions. Remember also that the return value is copied into
the EAX register and all other registers are preserved, except the EFLAGS
and the EDX register (see 4.). Refer also to 8. for a working example.


***
jq_encode:      sub     edx, edx                ;Encoder/Encryptor
                xchg    eax, edx
                pushad
                mov     ebp, esp
                and     ecx, eax
                mov     edi, [ebp+30h]
                cld
                mov     ch, 40h
                push    edi
                rep     stosd
                sub     edx, 2864E25Ch
                mov     esi, [ebp+28h]
                jnz     jq_e0
                dec     edx
         jq_e0: push    ecx
                sub     ax, 0AEB6h
                mov     edi, [ebp+24h]
                pop     ebx
                stosw
                xchg    eax, edx
                pop     ebp
                stosd
                push    edi
                xchg    eax, edx
                push    esp
         jq_e1: test    cl, 7
                lodsb
                jnz     jq_e3
                xchg    edx, [esp]
                adc     ah, dl
                pop     edx
                xchg    edi, [esp]
                ror     edx, 1
                mov     [edi], ah
                jc      jq_e2
                xor     edx, 2C047C3Eh
         jq_e2: pop     edi
                mov     ah, 0FFh
                push    edi
                xor     edx, 76C52B8Dh
                inc     edi
                push    edx
         jq_e3: cmp     al, [ebx+ebp]
                jz      jq_e5
                ror     edx, 1
                mov     [ebx+ebp], al
                jnc     jq_e4
                xor     edx, 2C047C3Eh
         jq_e4: mov     bh, al
                xor     edx, 5AC157B3h
                adc     al, dl
                stosb
                mov     al, bh
                stc
         jq_e5: inc     ecx
                mov     bh, bl
                rcl     ah, 1
                cmp     ecx, [esp+34h]
                mov     bl, al
                jc      jq_e1
                ror     ah, cl
                pop     ebx
                add     ah, bl
                pop     esi
                mov     ebp, esp
                sub     edi, [ebp+24h]
                mov     [ebp+14h], edx
                xchg    ah, [esi]
                add     [ebp+1Ch], edi
                popad
                ret     10h


jq_decode:      sub     eax, eax                ;Decoder/Decryptor
                pushad
                mov     ebp, esp
                and     ecx, eax
                mov     edi, [ebp+30h]
                cld
                mov     ch, 40h
                push    edi
                rep     stosd
                mov     esi, [ebp+28h]
                xchg    ebx, eax
                add     ecx, [ebp+2Ch]
                lodsw
                mov     edi, [ebp+24h]
                add     ecx,-6
                pop     ebp
                lodsd
                xchg    eax, edx
         jq_d0: test    byte ptr [esp+1Ch], 7
                jnz     jq_d2
                ror     edx, 1
                jecxz   jq_d5
                jnc     jq_d1
                xor     edx, 2C047C3Eh
         jq_d1: lodsb
                dec     ecx
                xor     edx, 5AC157B3h
                sbb     al, dl
                mov     ah, al
         jq_d2: shl     ah, 1
                inc     byte ptr [esp+1Ch]
                jnc     jq_d4
                ror     edx, 1
                jecxz   jq_d5
                jc      jq_d3
                xor     edx, 2C047C3Eh
         jq_d3: lodsb
                dec     ecx
                xor     edx, 76C52B8Dh
                sbb     al, dl
                mov     [ebx+ebp], al
         jq_d4: mov     al, [ebx+ebp]
                mov     bh, bl
                stosb
                mov     bl, al
                jmp     jq_d0
                dec     edx
                push    ecx
         jq_d5: sub     edi, [esp+24h]
                mov     [esp+1Ch], edi
                popad
                ret     10h
***


  5.2. Inclusion at link time

You can also add the JQCODING functions at link time to your ASM or HLL
project. Just add the JQCODING library file (JQCODING.LIB) at the link
command line in order to include the compressor/decompressor functions
to your final exe.

You can build the JQCODING library yourself from the Compressor/
Decompressor source code which is included in the file section of
the zine (JQENCODE.ASM and JQDECODE.ASM). You will also want to take
a look at the makefile JQCODING.MAK that builds the library (see 6.1.).


6. HLL support

You can also call the JQCODING functions from a HLL interface, assuming
that your HLL compiler supports the 'stdcall' convention. This is becoz
the jq_encode and jq_decode functions are implemented in ASM following
the 'stdcall' calling convention, which is also used by the Win32 API.

If you intend to use the JQCODING functions from the C/C++ language,
just add the C/C++ header file (see 6.2.). If you intend to use them
under other HLL's (PASCAL, BASIC, etc.) make sure that the compiler
understands and generates the appropiate call construction for the
functions, according to the 'stdcall' convention. You might also have
to declare both JQCODING functions according to the specific HLL syntax.


  6.1. Static library makefile

If you want to create the static JQCODING library, you will need this
makefile, also included in the file section as JQCODING.MAK. It assumes
your assembler is Macro Assembler ML.EXE >6.1x, but it's not mandatory.
However if you use another assembler, you have to modify the makefile.


***
AFLAGS = /nologo /coff

jqcoding.lib : jqencode.obj jqdecode.obj
  @if exist $@ del $@
  lib /nologo /out:$@ $**
***


  6.2. C/C++ Header file

You will need this C/C++ header file, also included in the file section
of the zine, if you intend to use the JQCODING functions right from the
C/C++ language. Simply add the following JQCODING.H header file to your
C/C++ source, this way:

#include "jqcoding.h"


***
unsigned long
__stdcall
jq_encode(void          *out,           /* output stream ptr */
          const void    *in,            /* input stream ptr */
          unsigned long  in_len,        /* input stream length */
          void          *mem64k);       /* work mem ptr */

unsigned long
__stdcall
jq_decode(void          *out,           /* output stream ptr */
          const void    *in,            /* input stream ptr */
          unsigned long  in_len,        /* input stream length */
          void          *mem64k);       /* work mem ptr */
***


7. Special considerations

In some unusual cases the compressor may expand data rather than
compressing. This usually happens when dealing with files already
compressed, or files with relatively low data redundancy. This
needs to be considered when allocating memory for the output
buffer, since the output stream may overflow the buffer in such
cases. The worst case occurs when no compression at all is possible
(btw very unlikely) in which the maximum length possible for the
output buffer becomes:

  (output buffer max length) = (input stream length) / 8 * 9 + 14

The "output buffer overflow" issue is evidenced much further at
decompression for obvious reasons: the output stream will expand
itself to reach its uncompressed state. So the worst case (or best
if you prefer) occurs when decompressing a data stream previously
compressed at the maximum optimal compression, in which the
maximum length possible for the output buffer becomes:

  (output buffer max length) = (input stream length) * 8

However, if you previously saved the original size prior to compression,
which is usually the case, then you don't need to worry at all about
the latter formula.


8. Examples

These examples show you how to use the JQCODING functions to add
compression/decompression to your ASM projects at the assembly
level. First Step is to include the JQCODING.ASM file into your
ASM source:

include jqcoding.asm

Second step is to call the JQCODING functions. Remember that parameters
should be pushed to the stack following the 'stdcall' calling convention
and the return value is copied into the EAX register. All other registers
are preserved, except EFLAGS, EDX and EAX register.

The following examples show you how to compress a data stream (see 8.1.)
and how to DEcompress a compressed data stream (see 8.2.).


  8.1. Compression example

(...)

original_input  db  'Hello World, Hello World, Watermellon, Watermellon'
original_size   db  $ - original_input

max_compressed_size  equ  original_size / 8 * 9 + 14

compressed_output     db  max_compressed_size dup (?)
real_compressed_size  dd  ?

work_memory  db  10000h dup (?)  ;better allocate this with GlobalAlloc API

(...)
        push    offset work_memory              ;work mem ptr
        push    original_size                   ;input stream length
        push    offset original_input           ;input stream ptr
        push    offset compressed_output        ;output stream ptr

        mov     eax, (pi * e * arctanh(3 - j6)) ;optional: seed value (undocumented)

        call    jq_encode                       ;Compress/Encrypt

        mov     [real_compressed_size], eax     ;store real compr. size
(...)


  8.2. Decompression example

(...)

original_input  db  012h, 008h, 037h, 09Eh, 0B9h, 0B6h, 0E9h, 05Dh
                    0E1h, 026h, 0F6h, 02Fh, 04Eh, 0E0h, 034h, 0BEh
                    0FEh, 0AAh, 02Ch, 07Dh, 0FCh, 0C6h, 00Fh, 0B2h
                    001h, 0D7h, 0A1h, 09Fh, 008h, 08Eh, 038h, 0D0h
                    06Fh, 039h, 0ABh, 048h, 056h
original_size   db  $ - original_input

max_DEcompressed_size  equ  original_size * 8

DEcompressed_output     db  max_DEcompressed_size dup (?)
real_DEcompressed_size  dd  ?

work_memory  db  10000h dup (?)  ;better allocate this with GlobalAlloc API

(...)
        push    offset work_memory              ;work mem ptr
        push    original_size                   ;input stream length
        push    offset original_input           ;input stream ptr
        push    offset DEcompressed_output      ;output stream ptr

        call    jq_decode                       ;DEcompress/Decrypt

        mov     [real_DEcompressed_size], eax   ;store real decompr. size
(...)


If you previously saved the original size of the data after compressing
with 'jq_encode' then you don't need to consider the max_DEcompressed_size
value becoz you already know the real size of the decompressed data, which
btw should match exactly the original size of the data. For instance:

(...)

original_input  db  012h, 008h, 037h, 09Eh, 0B9h, 0B6h, 0E9h, 05Dh
                    0E1h, 026h, 0F6h, 02Fh, 04Eh, 0E0h, 034h, 0BEh
                    0FEh, 0AAh, 02Ch, 07Dh, 0FCh, 0C6h, 00Fh, 0B2h
                    001h, 0D7h, 0A1h, 09Fh, 008h, 08Eh, 038h, 0D0h
                    06Fh, 039h, 0ABh, 048h, 056h
original_size   db  $ - original_input

DEcompressed_output  db  real_DEcompressed_size dup (?)

work_memory  db  10000h dup (?)  ;better allocate this with GlobalAlloc API

(...)
        push    offset work_memory              ;work mem ptr
        push    original_size                   ;input stream length
        push    offset original_input           ;input stream ptr
        push    offset DEcompressed_output      ;output stream ptr

        call    jq_decode                       ;DEcompress/Decrypt
(...)


9. Disclaimer

This information is provided for educational purposes only. The author is
NOT responsible in any way, for problems it may cause due to improper use!


(c) 1998. Jacky Qwerty/29A.
