;
; Copyright (c) 2002 - 2021 Magnus Lind.
;
; This software is provided 'as-is', without any express or implied warranty.
; In no event will the authors be held liable for any damages arising from
; the use of this software.
;
; Permission is granted to anyone to use this software, alter it and re-
; distribute it freely for any non-commercial, non-profit purpose subject to
; the following restrictions:
;
;   1. The origin of this software must not be misrepresented; you must not
;   claim that you wrote the original software. If you use this software in a
;   product, an acknowledgment in the product documentation would be
;   appreciated but is not required.
;
;   2. Altered source versions must be plainly marked as such, and must not
;   be misrepresented as being the original software.
;
;   3. This notice may not be removed or altered from any distribution.
;
;   4. The names of this software and/or it's copyright holders may not be
;   used to endorse or promote products derived from this software without
;   specific prior written permission.
;
; -------------------------------------------------------------------
; -- r_start_addr, done /* required, -2=basic start */
; -- r_target, done /* required, 1, 20, 23, 52, 55, 4, 64, 128, $a2 or $a8 */
; -- r_in_load, done /* required, load address of decrunched data area */
; -- r_in_len, done /* required, length of decrunched data area */
; -- i_literal_sequences_used, done /* defined if true, otherwise not */
; -- i_max_sequence_length_256, done /* defined if true, otherwise not */
; -- i_reuse_offset, done /* defined if true, otherwise not */
; -- i_ram_enter, done /* undef=c_rom_config_value */
; -- i_irq_enter, done /* undef=on, 0=off, 1=on */
; -- i_ram_during, done /* undef=auto
; -- i_irq_during, done /* undef=auto, 0=off, 1=on */
; -- i_ram_exit, done /* undef=auto
; -- i_irq_exit, done /* undef=auto, 0=off, 1=on */
; -- i_nmi_during
; -- i_nmi_enter
; -- i_nmi_exit
; -- i_effect, done /* -1=none, 0(default)=lower right, 1-3=border */
; -- i_effect_speed, done /* undef or 0=slow, otherwise fast */
; -- i_table_addr, done /* undef=$0334 or if(r_target == 128) $0b00 */
; -- i_fourth_offset_table, done /* defined if true, otherwise not */
; -- i_load_addr, done /* optional, if set no basic stub, loads to value */
; -- i_raw, done /* optional, if defined and != 0 outfile is raw */
; -- i_perf, done [-1 (slow/short), 0(default), 1, 2(fast/large)
; -- the crunched file to it.
; -------------------------------------------------------------------
; - if basic start --------------------------------------------------
; -- i_basic_txt_start    /* will be set before basic start, if defined */
; -- i_basic_var_start    /* will be set before basic start, if defined */
; -- i_basic_highest_addr /* will be set before basic start, if defined */
; -------------------------------------------------------------------

.IF(!.DEFINED(r_target))
  .ERROR("Required symbol r_target not defined.")
.ENDIF
.IF(!.DEFINED(i_effect))
  i_effect = 0
.ENDIF
.IF(!.DEFINED(i_perf))
  i_perf = 0
.ENDIF

.IF(.DEFINED(i_irq_enter) && i_irq_enter != 0 && i_irq_enter != 1)
  .ERROR("Symbol i_irq_enter must be undefined, 0 or 1.")
.ENDIF

.IF(.DEFINED(i_irq_during) && i_irq_during != 0 && i_irq_during != 1)
  .ERROR("Symbol i_irq_during must be undefined, 0 or 1.")
.ENDIF

.IF(.DEFINED(i_irq_exit) && i_irq_exit != 0 && i_irq_exit != 1)
  .ERROR("Symbol i_irq_exit must be undefined, 0 or 1.")
.ENDIF

.IF(.DEFINED(i_load_addr) && (i_load_addr < 0 || i_load_addr > 65535))
  .ERROR("Symbol i_load_addr must be undefined or [0 or 65535].")
.ENDIF

.IF(r_target == 1)
  zp_len_lo = $80
  zp_len_hi = $81
  zp_src_lo = $82
  zp_src_hi = zp_src_lo + 1
  zp_bits_hi = $84
  zp_ro_state = $85

  c_basic_start    = $0501
  c_end_of_mem_rom = $c000
  c_effect_color   = $bfdf
  c_border_color   = $bfdf
  c_rom_config_value = 0
  c_ram_config_value = 1
  c_rom_nmi_value = 0
  c_ram_nmi_value = 0
  c_default_table = $b800
.ELIF(r_target == 20 || r_target == 23 || r_target == 52 || r_target == 55 ||
      r_target == 64 || r_target == 65 || r_target == 128)
  zp_len_lo = $9e
  zp_len_hi = $9f
  zp_src_lo = $ae
  zp_src_hi = zp_src_lo + 1
  zp_bits_hi = $a7
  zp_ro_state = $a8

  .IF(r_target == 20)
  c_basic_start    = $1001
  c_end_of_mem_rom = $2000
  c_effect_char    = $1ff9
  c_effect_color   = $97f9
  c_border_color   = $900f
  c_rom_config_value = 0
  c_ram_config_value = 0
  c_rom_nmi_value = 0
  c_ram_nmi_value = 0
  c_default_table = $0334
  .ELIF(r_target == 23)
  c_basic_start    = $0401
  c_end_of_mem_rom = $2000
  c_effect_char    = $1ff9
  c_effect_color   = $97f9
  c_border_color   = $900f
  c_rom_config_value = 0
  c_ram_config_value = 0
  c_rom_nmi_value = 0
  c_ram_nmi_value = 0
  c_default_table = $0334
  .ELIF(r_target == 52)
  c_basic_start    = $1201
  c_end_of_mem_rom = $8000
  c_effect_char    = $11f9
  c_effect_color   = $95f9
  c_border_color   = $900f
  c_rom_config_value = 0
  c_ram_config_value = 0
  c_rom_nmi_value = 0
  c_ram_nmi_value = 0
  c_default_table = $0334
  .ELIF(r_target == 55)
  c_basic_start    = $1201
  c_end_of_mem_rom = $8000
  c_effect_char    = $11f9
  c_effect_color   = $95f9
  c_border_color   = $900f
  c_rom_config_value = 0
  c_ram_config_value = 0
  c_rom_nmi_value = 0
  c_ram_nmi_value = 0
  c_default_table = $0334
  .ELIF(r_target == 64)
  c_basic_start    = $0801
  c_end_of_mem_rom = $a000
  c_effect_char    = $07e7
  c_effect_color   = $dbe7
  c_border_color   = $d020
  c_rom_config_value = $37
  c_ram_config_value = $38
  c_rom_nmi_value = 0
  c_ram_nmi_value = 0
  c_default_table = $0334
  .ELIF(r_target == 65)
  c_basic_start    = $2001
  c_end_of_mem_rom = $2000
  c_effect_char    = $0fcf
  c_effect_color   = $dfcf
  c_border_color   = $d020
  c_rom_config_value = 255      ; value of $01 + unmapped
  c_ram_config_value = 0        ; value of $01 + unmapped
  c_rom_nmi_value = 0
  c_ram_nmi_value = 0
    .IF(r_start_addr == -2)
    c_default_table = $1802     ; BASIC start, don't disturb stuff
    c_page0location = $1800     ; move page zero here (and decr to $1900)
    .ELSE
    c_default_table = $0200       ; $0200-$02a1 line input buffer
    .ENDIF
  .ELIF(r_target == 128)
  c_basic_start    = $1c01
  c_end_of_mem_rom = $4000
  c_effect_char    = $07e7
  c_effect_color   = $dbe7
  c_border_color   = $d020
  c_rom_config_value = $00
  c_ram_config_value = $3f
  c_rom_nmi_value = 0
  c_ram_nmi_value = 0
  c_default_table = $0b00
  .ENDIF
.ELIF(r_target == 4032)
  zp_len_lo = $16
  zp_len_hi = $17
  zp_src_lo = $18
  zp_src_hi = zp_src_lo + 1
  zp_bits_hi = $1a
  zp_ro_state = $1b

  c_basic_start = $0401
  c_end_of_mem_rom = $8000
  c_effect_color   = $83e7
  c_border_color   = $83e7
  c_rom_config_value = 0
  c_ram_config_value = 0
  c_rom_nmi_value = 0
  c_ram_nmi_value = 0
  c_default_table = $027a
.ELIF(r_target == 16 || r_target == 4)
  zp_len_lo = $d8
  zp_len_hi = $d9
  zp_src_lo = $da
  zp_src_hi = zp_src_lo + 1
  zp_bits_hi = $dc
  zp_ro_state = $dd

  .IF(r_target == 16)
  c_basic_start    = $1001
  c_end_of_mem_rom = $8000
  c_effect_char    = $0fe7
  c_effect_color   = $0be7
  c_border_color   = $ff19
  c_rom_config_value = 0
  c_ram_config_value = 1
  c_rom_nmi_value = 0
  c_ram_nmi_value = 0
  c_default_table = $0334
  .ELIF(r_target == 4)
  c_basic_start    = $1001
  c_end_of_mem_rom = $8000
  c_effect_char    = $0fe7
  c_effect_color   = $0be7
  c_border_color   = $ff19
  c_rom_config_value = 0
  c_ram_config_value = 1
  c_rom_nmi_value = 0
  c_ram_nmi_value = 0
  c_default_table = $0334
  .ENDIF
.ELIF(r_target == $a2)
  ; http://apple2.org.za/gswv/a2zine/faqs/csa2pfaq.html#017
  zp_len_lo = $eb
  zp_len_hi = $ec
  zp_src_lo = $ed
  zp_src_hi = zp_src_lo + 1
  zp_bits_hi = $ef
  zp_ro_state = $fa

  c_basic_start    = $0801
  c_end_of_mem_rom = $9600
  c_effect_color   = $07f7
  c_border_color   = $07f7
  c_rom_config_value = 0
  c_ram_config_value = 0
  c_rom_nmi_value = 0
  c_ram_nmi_value = 0
  c_default_table = $0334
.ELIF(r_target == $a8)
  zp_len_lo = $f7
  zp_len_hi = $fc
  zp_src_lo = $f9
  zp_src_hi = zp_src_lo + 1
  zp_bits_hi = $f8
  zp_ro_state = $f6

  c_end_of_mem_rom = $a000
  c_effect_color   = $d017
  c_border_color   = $d01a
  c_rom_config_value = $ff
  c_ram_config_value = $fe
  c_rom_nmi_value = $40
  c_ram_nmi_value = 0
  c_default_table = $0600
.ELIF(r_target == $bbcb)
  zp_len_lo = $70
  zp_len_hi = $71
  zp_src_lo = $72
  zp_src_hi = zp_src_lo + 1
  zp_bits_hi = $74
  zp_ro_state = $75

  c_basic_start    = $1900
  c_end_of_mem_rom = $7c00
  c_effect_char    = $7fe7
  c_effect_color   = $7fe6
  c_border_color   = $7fe7
  c_rom_config_value = 0
  c_ram_config_value = 0
  c_rom_nmi_value = 0
  c_ram_nmi_value = 0
  c_default_table = $0a34
.ELSE
  .ERROR("Symbol r_target_addr has an invalid value.")
.ENDIF
.IF (!.DEFINED(c_page0location))
  c_page0location = $0000
.ENDIF

v_safety_addr = .INCWORD("crunched_data", 0)
transfer_len ?= 0

.IF(i_effect == 0 && (r_target == 16 || r_target == 4 || r_target == $a2) &&
    (r_in_load < c_effect_color + 1 &&
     r_in_load + r_in_len > c_effect_color ||
     v_safety_addr < c_effect_color + 1 &&
     v_safety_addr + transfer_len > c_effect_color))
        ;; disable default effect if the address is in the
        ;; data area for the c64/plus4 and Apple targets.
  i_effect2 = -1
.ELSE
  i_effect2 = i_effect
.ENDIF

.IF(!.DEFINED(i_table_addr))
  i_table_addr = c_default_table
.ENDIF

.IF(!.DEFINED(i_ram_enter))
  i_ram_enter = c_rom_config_value
.ENDIF

.IF(!.DEFINED(i_nmi_enter))
  i_nmi_enter = c_rom_nmi_value
.ENDIF

.IF(!.DEFINED(i_irq_enter))
  i_irq_enter = 1
.ENDIF

.IF(!.DEFINED(i_irq_during))
  .IF((r_target == 16 || r_target == 4) &&
      (r_in_load < $0800 ||
       v_safety_addr < $0800 ||
       i_table_addr < $0800 && i_table_addr != c_default_table))
    ;; must disable irq on plus4/c16 to access mem < $0800
    i_irq_during = 0
    .IF(!.DEFINED(i_irq_exit))
      i_irq_exit = 0
    .ENDIF
  .ELIF((r_target == 20 || r_target == 23 || r_target == 52 ||
         r_target == 55 || r_target == 64 || r_target == 128) &&
        (r_in_load < c_default_table ||
         v_safety_addr < c_default_table ||
         i_table_addr < c_default_table))
    ;; must disable irq on vic20,c64,c128 to access mem < c_default_table
    i_irq_during = 0
    .IF(!.DEFINED(i_irq_exit))
      i_irq_exit = 0
    .ENDIF
  .ELIF(r_target == 65 && r_start_addr != -2)
    ;; Non BASIC starting decruncher trashes system areas
    i_irq_during = 0
    .IF(!.DEFINED(i_irq_exit))
      i_irq_exit = 0
    .ENDIF
  .ENDIF
.ENDIF

.IF(!.DEFINED(i_ram_exit))
  i_ram_exit = c_rom_config_value
.ENDIF

.IF(!.DEFINED(i_nmi_exit))
  i_nmi_exit = c_rom_nmi_value
.ENDIF

.IF(!.DEFINED(i_irq_exit))
  .IF(i_ram_exit != i_ram_enter)
  i_irq_exit = 0
  .ELSE
  i_irq_exit = i_irq_enter
  .ENDIF
.ENDIF

.IF(!.DEFINED(r_start_addr))
  .ERROR("Required symbol r_start_addr not defined.")
.ENDIF

.IF(r_start_addr == -2 && i_ram_exit != c_rom_config_value)
  .ERROR("Basic start and non-ROM configuration can't be combined.")
.ENDIF

.IF(.DEFINED(i_line_number) && .DEFINED(i_load_addr))
  .ERROR("Basic line number and load address can't be combined.")
.ENDIF

.IF(!.DEFINED(i_line_number))
  i_line_number = 313
.ENDIF

.IF(.DEFINED(i_fourth_offset_table))
encoded_entries = 68
.ELSE
encoded_entries = 52
.ENDIF
; -------------------------------------------------------------------
; -- validate some input parameters ---------------------------------
; -------------------------------------------------------------------
.IF(r_target == 1 || r_target == 16 || r_target == 4)
  .IF(i_ram_exit != 0 && i_ram_exit != 1)
    .ERROR("Symbol i_ram_exit must have a value [0-1].")
  .ENDIF
  .IF(i_ram_enter != 0 && i_ram_enter != 1)
    .ERROR("Symbol i_ram_enter must have a value [0-1].")
  .ENDIF
.ELIF(r_target == 64)
  .IF(i_ram_exit > $38 || i_ram_exit < $34)
    .ERROR("Symbol i_ram_exit must be undefined or be within [$34-$38].")
  .ENDIF
  .IF(i_ram_enter > $38 || i_ram_enter < $34)
    .ERROR("Symbol i_ram_enter must be undefined or be within [$34-$38].")
  .ENDIF
.ELIF(r_target == 128)
  .IF(i_ram_exit > $3f || i_ram_exit < 0)
    .ERROR("Symbol i_ram_exit must be undefined or be within [0-$3f].")
  .ENDIF
  .IF(i_ram_enter > $3f || i_ram_enter < 0)
    .ERROR("Symbol i_ram_enter must be undefined or be within [0-$3f].")
  .ENDIF
.ENDIF

; -------------------------------------------------------------------
; -- convert $0 to $10000 but leave $1 - $ffff ----------------------
; -------------------------------------------------------------------
v_highest_addr = (.INCWORD("crunched_data", -2) + 65535) % 65536 + 1
v_highest_addr_out ?= v_highest_addr

; -------------------------------------------------------------------
; -- file2_start_hook and stage2_exit_hook --------------------------
; -------------------------------------------------------------------
.IF(!.DEFINED(i_effect_custom) && i_effect2 == 0 && .DEFINED(c_effect_char))
file2_start_hook = 1
  .MACRO("file2_start_hook")
    .IF(v_safety_addr < file2start && ; if we are transferring anyhow
        c_effect_char < v_safety_addr &&
        (file2start - c_effect_char < 257 ||
         file2start - v_safety_addr > 256) &&
        (i_table_addr + 3 * encoded_entries < c_effect_char ||
         i_table_addr > v_highest_addr))
raw_transfer_len = file2start - c_effect_char
lowest_addr = c_effect_char
    .ENDIF
  .ENDMACRO
stage2_exit_hook = 1
  .MACRO("stage2_exit_hook")
    .IF(c_effect_char < lowest_addr || c_effect_char > v_highest_addr)
      .IF(r_target == $bbcb)
        sty c_effect_char
      .ELSE
        stx c_effect_char
      .ENDIF
    .ENDIF
  .ENDMACRO
.ENDIF

; -------------------------------------------------------------------
; -- calculate "*_during" parameters --------------------------------
; -------------------------------------------------------------------
.IF(!.DEFINED(i_ram_during))
  .IF(v_highest_addr > c_end_of_mem_rom ||
      v_highest_addr_out > c_end_of_mem_rom)
    i_ram_during = c_ram_config_value
  .ELSE
    i_ram_during = i_ram_enter
  .ENDIF
.ENDIF

.IF(!.DEFINED(i_nmi_during))
  .IF(v_highest_addr > c_end_of_mem_rom)
    i_nmi_during = c_ram_nmi_value
  .ELSE
    i_nmi_during = i_nmi_enter
  .ENDIF
.ENDIF
.IF(!.DEFINED(i_irq_during))
  .IF(i_irq_enter==i_irq_exit && i_ram_during==i_ram_enter && r_target!=1)
    i_irq_during = i_irq_enter
  .ELSE
    i_irq_during = 0
  .ENDIF
.ENDIF

.IF(r_target == 16 || r_target == 4)
  .IF(i_ram_during != 0 && i_ram_during != 1)
    .ERROR("Symbol i_ram_during must have a value [0-1].")
  .ENDIF
.ELIF(r_target == 64)
  .IF(i_ram_during > $38 || i_ram_during < $34)
    .ERROR("Symbol i_ram_during must be undefined or be within [$34-$38].")
  .ENDIF
.ELIF(r_target == 128)
  .IF(i_ram_during > $3f || i_ram_during < 0)
    .ERROR("Symbol i_ram_during must be undefined or be within [0-$3f].")
  .ENDIF
.ELIF(r_target == 65)
  .IF(i_ram_during != 0 && i_ram_during != $ff)
    .ERROR("Symbol i_ram_during must be undefined or be within [$ff-0].")
  .ENDIF
.ENDIF

; -------------------------------------------------------------------
; -- The decruncher enter macro definition --------------------------
; -------------------------------------------------------------------
enter_hook = 1
.MACRO("enter_hook")
  .IF(i_irq_during != i_irq_enter)
    .IF(i_irq_during == 1)
        cli
    .ELSE
        sei
    .ENDIF
  .ENDIF
  .IF(i_nmi_during != i_nmi_enter)
    .INCLUDE("b2d_nmi")
  .ENDIF
  .IF(.DEFINED(i_enter_custom))
    .INCLUDE("enter_custom")
  .ENDIF
  .IF(i_ram_during != i_ram_enter)
    .INCLUDE("b2d_ram")
  .ENDIF
.ENDMACRO
; -------------------------------------------------------------------
; -- The decruncher exit macro definition ---------------------------
; -------------------------------------------------------------------
exit_hook = 1
.MACRO("exit_hook")
  .IF(i_ram_exit != i_ram_during)
    .INCLUDE("d2r_ram")
  .ENDIF
  .IF(.DEFINED(i_exit_custom))
    .INCLUDE("exit_custom")
  .ENDIF
  .IF(i_nmi_exit != i_nmi_during)
    .INCLUDE("d2r_nmi")
  .ENDIF
  .IF(i_irq_exit != i_irq_during)
    .IF(i_irq_exit == 1)
        cli
    .ELSE
        sei
    .ENDIF
  .ENDIF
  .IF(r_start_addr == -2)
    .IF(r_target == $a2)
      .IF(.DEFINED(i_basic_txt_start))
        lda #i_basic_txt_start % 256
        sta <$67
        lda #i_basic_txt_start / 256
        sta <$68
      .ENDIF
      .IF(.DEFINED(i_basic_var_start))
        lda #i_basic_var_start % 256
        sta <$69
        lda #i_basic_var_start / 256
        sta <$6a
      .ENDIF
      .IF(.DEFINED(i_basic_highest_addr))
        lda #(i_basic_highest_addr - 1) % 256
        sta <$73
        lda #(i_basic_highest_addr - 1) / 256
        sta <$74
      .ENDIF
    .ELIF(r_target == $a8)
        .ERROR("Atari target can't handle basic start.")
    .ELIF(r_target == $bbcb)
        .ERROR("BBC Micro B target can't handle basic start.")
    .ELIF(r_target == 1)
      .IF(.DEFINED(i_basic_txt_start))
        lda #i_basic_txt_start % 256
        sta <$9a
        lda #i_basic_txt_start / 256
        sta <$9b
      .ENDIF
      .IF(.DEFINED(i_basic_var_start))
        lda #i_basic_var_start % 256
        sta <$9c
        lda #i_basic_var_start / 256
        sta <$9d
      .ENDIF
      .IF(.DEFINED(i_basic_highest_addr))
        lda #(i_basic_highest_addr - 1) % 256
        sta <$a6
        lda #(i_basic_highest_addr - 1) / 256
        sta <$a7
      .ENDIF
    .ELIF(r_target == 128)
      .IF(.DEFINED(i_basic_txt_start))
        lda #i_basic_txt_start % 256
        sta <$2d
        lda #i_basic_txt_start / 256
        sta <$2e
      .ENDIF
      .IF(.DEFINED(i_basic_var_start))
        lda #i_basic_var_start % 256
        sta $1210
        lda #i_basic_var_start / 256
        sta $1211
      .ENDIF
      .IF(.DEFINED(i_basic_highest_addr))
        lda #i_basic_highest_addr % 256
        sta $1212
        lda #i_basic_highest_addr / 256
        sta $1213
      .ENDIF
    .ELIF(r_target == 65)
      .IF(.DEFINED(i_basic_txt_start))
        lda #i_basic_txt_start % 256
        sta <$2d
        lda #i_basic_txt_start / 256
        sta <$2e
      .ENDIF
      .IF(.DEFINED(i_basic_var_start))
        lda #i_basic_var_start % 256
        sta <$82
        lda #i_basic_var_start / 256
        sta <$83
      .ENDIF
      .IF(.DEFINED(i_basic_highest_addr))
        lda #i_basic_highest_addr % 256
        sta <$39
        lda #i_basic_highest_addr / 256
        sta <$3a
      .ENDIF
    .ELIF(r_target == 4032)
      .IF(.DEFINED(i_basic_txt_start))
        lda #i_basic_txt_start % 256
        sta <$28
        lda #i_basic_txt_start / 256
        sta <$29
      .ENDIF
      .IF(.DEFINED(i_basic_var_start))
        lda #i_basic_var_start % 256
        sta <$2a
        lda #i_basic_var_start / 256
        sta <$2b
      .ENDIF
      .IF(.DEFINED(i_basic_highest_addr))
        lda #i_basic_highest_addr % 256
        sta <$34
        lda #i_basic_highest_addr / 256
        sta <$35
      .ENDIF
    .ELSE
      .IF(.DEFINED(i_basic_txt_start))
        lda #i_basic_txt_start % 256
        sta <$2b
        lda #i_basic_txt_start / 256
        sta <$2c
      .ENDIF
      .IF(.DEFINED(i_basic_var_start))
        lda #i_basic_var_start % 256
        sta <$2d
        lda #i_basic_var_start / 256
        sta <$2e
      .ENDIF
      .IF(.DEFINED(i_basic_highest_addr))
        lda #i_basic_highest_addr % 256
        sta <$37
        lda #i_basic_highest_addr / 256
        sta <$38
      .ENDIF
    .ENDIF
    .IF(r_target == 20 || r_target == 23 || r_target == 52 || r_target == 55)
        jsr $c659               ; init
        jsr $c533               ; regenerate line links
        jmp $c7ae               ; start
    .ELIF(r_target == 4032)
        jsr $B622               ; Reset TXTPTR
        jsr $B5F0               ; CLR
        jsr $B4B6               ; relink
        jmp $B74A
    .ELIF(r_target == 16 || r_target == 4)
        jsr $8bbe               ; init
        jsr $8818               ; regenerate line links and set $2d/$2e
        jsr $f3b5               ; regen color table at $0113
        jmp $8bdc               ; start
    .ELIF(r_target == 64)
        jsr $a659               ; init
        jsr $a533               ; regenerate line links
        jmp $a7ae               ; start
    .ELIF(r_target == 65)
        lda #$80
        sta $02d1               ; switch BASIC10 back to BANK128
        jmp $7f8d               ; combo function clr/relink/run basic
    .ELIF(r_target == 128)
        jsr $5ab5               ; init
        jsr $4f4f               ; regenerate line links and set $1210/$1211
        jmp $4af6               ; start
    .ELIF(r_target == $a2)
        lda #a2relinked % 256   ; set kbd vector
        sta <$36
        lda #a2relinked / 256
        sta <$37
        jmp $d4f2               ; relink, exits by kbd vector
a2relinked:
        jsr $fe89               ; reset kbd vector at $36/$37
        jmp $d566               ; start
    .ELIF(r_target == 1)
        bit $fffc
        bmi oric_ROM11
        jsr $c56f               ; regenerate line links
        jmp $c733               ; start
oric_ROM11:
        jsr $c55f               ; regenerate line links
        jmp $c708               ; start
    .ENDIF
  .ELSE
        jmp r_start_addr
  .ENDIF
.ENDMACRO

; -------------------------------------------------------------------
; -- The decrunch effect macro definition ---------------------------
; -------------------------------------------------------------------
.IF(!.DEFINED(i_effect_speed) || i_effect_speed == 0)
  slow_effect_hook = 1
.ELSE
  fast_effect_hook = 1
.ENDIF
.MACRO("effect_hook")
  .IF(.DEFINED(i_effect_custom))
    .INCLUDE("d2io")
    .INCLUDE("effect_custom")
    .INCLUDE("io2d")
  .ELIF(i_effect2 != -1)
    .INCLUDE("d2io")
    .IF(i_effect2 == 0)
      .IF(r_target == 16 || r_target == 4)
        lda <$fd,x
        sta c_effect_color
      .ELIF(r_target == 1)
        lda <$fd,x
        and #$07
        ora #$10
        sta c_effect_color
      .ELIF(r_target == $bbcb)
        txa
        and #$1f
        ora #$80
        sta c_effect_color
      .ELSE
        stx c_effect_color
      .ENDIF
    .ELIF(i_effect2 == 1)
      .IF(r_target == 20 || r_target == 23 || r_target == 52 || r_target == 55)
        and #$07
        ora #$18
      .ELIF(r_target == 16 || r_target == 4)
        ora #$30
      .ENDIF
        sta c_border_color
    .ELIF(i_effect2 == 2)
      .IF(r_target == 20 || r_target == 23 || r_target == 52 || r_target == 55)
        txa
        and #$07
        ora #$18
        sta c_border_color
      .ELIF(r_target == 16 || r_target == 4)
        lda <$fd,x
        sta c_border_color
      .ELSE
        stx c_border_color
      .ENDIF
    .ELIF(i_effect2 == 3)
      .IF(r_target == 20 || r_target == 23 || r_target == 52 || r_target == 55)
        tya
        and #$07
        ora #$18
        sta c_border_color
      .ELIF(r_target == 16 || r_target == 4)
        sty c_border_color
      .ELSE
        sty c_border_color
      .ENDIF
    .ELSE
      .ERROR("Unknown decrunch effect.")
    .ENDIF
    .INCLUDE("io2d")
  .ENDIF
.ENDMACRO

; -------------------------------------------------------------------
; -- The ram/rom switch macros for decrunching ----------------------
; -------------------------------------------------------------------
.IF(r_target == 1)
; -------------------------------------------------------------------
; -- The ram/rom switch macros for Oric-1 ---------------------------
; -------------------------------------------------------------------
  .MACRO("b2d_nmi")
  .ENDMACRO
  .MACRO("b2d_ram")
    .IF(i_ram_during == c_ram_config_value)
        lda #$84
        sta $0314 ; -- %1xxxxx0x -- RAM at $c000-$10000 -------------
    .ELSE
        lda #$86
        sta $0314 ; -- %xxxxxx1x -- ROM at $c000-$10000 -------------
    .ENDIF
  .ENDMACRO
  .MACRO("d2io")
  .ENDMACRO
  .MACRO("io2d")
  .ENDMACRO
  .MACRO("d2r_ram")
    .IF(i_ram_exit == c_rom_config_value)
        lda #$86
        sta $0314 ; -- %xxxxxx1x -- ROM at $c000-$10000 -------------
    .ELSE
        lda #$84
        sta $0314 ; -- %1xxxxx0x -- RAM at $c000-$10000 -------------
    .ENDIF
  .ENDMACRO
  .MACRO("d2r_nmi")
  .ENDMACRO
.ELIF(r_target == 64)
; -------------------------------------------------------------------
; -- The ram/rom switch macros for c64 ------------------------------
; -------------------------------------------------------------------
  .MACRO("b2d_nmi")
  .ENDMACRO
  .MACRO("b2d_ram")
    .IF(i_ram_during == i_ram_enter + 1)
        inc <$01
    .ELIF(i_ram_during == i_ram_enter - 1)
        dec <$01
    .ELSE
        lda #i_ram_during
        sta <$01
    .ENDIF
  .ENDMACRO
  .MACRO("d2io")
    .IF(i_ram_during == $34 || i_ram_during == $38)
      .IF(i_irq_during == 1)
        sei
      .ENDIF
      .IF(i_ram_during == $34)
        inc <$01
      .ELIF(i_ram_during == $38)
        dec <$01
      .ENDIF
    .ENDIF
  .ENDMACRO
  .MACRO("io2d")
    .IF(i_ram_during == $34 || i_ram_during == $38)
      .IF(i_ram_during == $34)
        dec <$01
      .ELIF(i_ram_during == $38)
        inc <$01
      .ENDIF
      .IF(i_irq_during == 1)
        cli
      .ENDIF
    .ENDIF
  .ENDMACRO
  .MACRO("d2r_ram")
    .IF(i_ram_exit == i_ram_during + 1)
        inc <$01
    .ELIF(i_ram_exit == i_ram_during - 1)
        dec <$01
    .ELSE
        lda #i_ram_exit
        sta <$01
    .ENDIF
  .ENDMACRO
  .MACRO("d2r_nmi")
  .ENDMACRO
.ELIF(r_target == 65)
; -------------------------------------------------------------------
; -- The ram/rom switch macros for c65 ------------------------------
; -------------------------------------------------------------------
  .MACRO("b2d_nmi")
  .ENDMACRO
  .MACRO("b2d_ram")
    .IF(i_ram_during != $ff)
        ;; unmap RAM to be able to access IO
        ldx #0
        .BYTE ($a3, $00)        ; LDZ #$00
        .BYTE ($5c)             ; MAP
        nop                     ; EOM
    .ENDIF
    .IF(!.DEFINED(i_effect_custom) && i_effect2 == 0)
        inx                     ;cram @dc00 for right lower corner effect
    .ENDIF
        stx $d030               ;no C65 ROMs please, BANK0 state assumed
    .IF(i_ram_during == (i_ram_enter + 1) % 256)
        inc <$01
    .ELIF((i_ram_during + 1) % 256 == i_ram_enter)
        dec <$01
    .ELSE
        lda #i_ram_during
        sta <$01
    .ENDIF
    .IF(c_page0location != $0000)
        lda #c_page0location / 256
        .BYTE($5b)              ; TAB, set base page
    .ENDIF
  .ENDMACRO
  .MACRO("d2io")
    .IF(i_ram_during == 0)
      .IF(c_page0location == $0000)
        dec <$01
      .ELSE
        dec $0001
      .ENDIF
    .ENDIF
  .ENDMACRO
  .MACRO("io2d")
    .IF(i_ram_during == 0)
      .IF(c_page0location == $0000)
        inc <$01
      .ELSE
        inc $0001
      .ENDIF
    .ENDIF
  .ENDMACRO
  .MACRO("d2r_ram")
    .IF(c_page0location != $0000)
        lda #0
        .BYTE($5b)              ; TAB, restore base page to zero
    .ENDIF
    .IF(i_ram_exit == (i_ram_during + 1) % 256)
        inc <$01
    .ELIF((i_ram_exit + 1) % 256 == i_ram_during)
        dec <$01
    .ELSE
        lda #i_ram_exit
        sta <$01
    .ENDIF
    .IF(i_ram_exit == $ff)
        lda #$64
        sta $d030               ; enable default C65 ROMs again
        lda #0                  ; restore default ROM mapping
        ldx #$e3
        tay
        .BYTE ($a3, $b3)        ; LDZ #$b3
        .BYTE ($5c)             ; MAP
        nop                     ; EOM
    .ENDIF
  .ENDMACRO
  .MACRO("d2r_nmi")
  .ENDMACRO
.ELIF(r_target == 128)
; -------------------------------------------------------------------
; -- The ram/rom switch macros for c128 -----------------------------
; -------------------------------------------------------------------
  .MACRO("b2d_nmi")
  .ENDMACRO
  .MACRO("b2d_ram")
        lda #i_ram_during
        sta $ff00
  .ENDMACRO
  .MACRO("d2io")
    .IF(i_ram_during == $3f)
      .IF(i_irq_during == 1)
        sei
      .ENDIF
      .IF(i_effect2 == 1)
        pha
      .ENDIF
        lda #c_rom_config_value
        sta $ff00
      .IF(i_effect2 == 1)
        pla
      .ENDIF
    .ENDIF
  .ENDMACRO
  .MACRO("io2d")
    .IF(i_ram_during == $3f)
      .IF(i_effect2 == 1)
        pha
      .ENDIF
        lda #i_ram_during
        sta $ff00
      .IF(i_effect2 == 1)
        pla
      .ENDIF
      .IF(i_irq_during == 1)
        cli
      .ENDIF
    .ENDIF
  .ENDMACRO
  .MACRO("d2r_ram")
        lda #i_ram_exit
        sta $ff00
  .ENDMACRO
  .MACRO("d2r_nmi")
  .ENDMACRO
.ELIF(r_target == 16 || r_target == 4)
; -------------------------------------------------------------------
; -- The ram/rom switch macros for c16/+4 ---------------------------
; -------------------------------------------------------------------
  .MACRO("b2d_nmi")
  .ENDMACRO
  .MACRO("b2d_ram")
    .IF(i_ram_during == c_ram_config_value)
        sta $ff3f
    .ELSE
        sta $ff3e
    .ENDIF
  .ENDMACRO
  .MACRO("d2io")
  .ENDMACRO
  .MACRO("io2d")
  .ENDMACRO
  .MACRO("d2r_ram")
    .IF(i_ram_exit == c_rom_config_value)
        sta $ff3e
    .ELSE
        sta $ff3f
    .ENDIF
  .ENDMACRO
  .MACRO("d2r_nmi")
  .ENDMACRO
.ELIF(r_target == 20 || r_target == 23 || r_target == 52 || r_target == 55 ||
      r_target == 4032)
; -------------------------------------------------------------------
; -- The ram/rom switch macros for c20 and PET 4032 -----------------
; -------------------------------------------------------------------
  .MACRO("b2d_nmi")
  .ENDMACRO
  .MACRO("b2d_ram")
  .ENDMACRO
  .MACRO("d2io")
  .ENDMACRO
  .MACRO("io2d")
  .ENDMACRO
  .MACRO("d2r_ram")
  .ENDMACRO
  .MACRO("d2r_nmi")
  .ENDMACRO
.ELIF(r_target == $a2)
; -------------------------------------------------------------------
; -- The ram/rom switch macros for Apple ----------------------------
; -------------------------------------------------------------------
  .MACRO("b2d_nmi")
  .ENDMACRO
  .MACRO("b2d_ram")
  .ENDMACRO
  .MACRO("d2io")
  .ENDMACRO
  .MACRO("io2d")
  .ENDMACRO
  .MACRO("d2r_ram")
  .ENDMACRO
  .MACRO("d2r_nmi")
  .ENDMACRO
.ELIF(r_target == $a8)
; -------------------------------------------------------------------
; -- The ram/rom switch macros for a8 -------------------------------
; -------------------------------------------------------------------
  .MACRO("b2d_nmi")
        lda #i_nmi_during
        sta $d40e
  .ENDMACRO
  .MACRO("b2d_ram")
        lda #i_ram_during
        sta $d301
  .ENDMACRO
  .MACRO("d2io")
  .ENDMACRO
  .MACRO("io2d")
  .ENDMACRO
  .MACRO("d2r_ram")
        lda #i_ram_exit
        sta $d301
  .ENDMACRO
  .MACRO("d2r_nmi")
        lda #i_nmi_exit
        sta $d40e
  .ENDMACRO
.ELSE
.ELIF(r_target == $bbcb)
; -------------------------------------------------------------------
; -- The ram/rom switch macros for BBC Micro B ----------------------
; -------------------------------------------------------------------
  .MACRO("b2d_nmi")
  .ENDMACRO
  .MACRO("b2d_ram")
  .ENDMACRO
  .MACRO("d2io")
  .ENDMACRO
  .MACRO("io2d")
  .ENDMACRO
  .MACRO("d2r_ram")
  .ENDMACRO
  .MACRO("d2r_nmi")
  .ENDMACRO
  .ERROR("Unhandled target for macro definitions.")
.ENDIF
; -------------------------------------------------------------------
; -- End of bank switch definitions ---------------------------------
; -------------------------------------------------------------------
; -------------------------------------------------------------------
; -- Start of file header stuff -------------------------------------
; -------------------------------------------------------------------
.IF(r_target == 1)
; -------------------------------------------------------------------
; -- Oric-1 file header stuff ---------------------------------------
; -------------------------------------------------------------------
  .IF(r_start_addr == -2)
    .IF(!.DEFINED(i_raw) || i_raw == 0)
        .BYTE($16,$16,$16,$24,$00,$00,$00,$c7)
        .BYTE((o1_end - 1) / 256, (o1_end - 1) % 256)
        .BYTE(c_basic_start / 256, c_basic_start % 256)
        .BYTE(0, 0)
    .ENDIF
        .ORG(c_basic_start)
lowest_addr_out:
        .WORD(basic_end, i_line_number)
        .BYTE($bf, o1_start / 1000 % 10 + 48, o1_start / 100 % 10 + 48)
        .BYTE(o1_start / 10 % 10 + 48, o1_start % 10 + 48)
    .IF(.DEFINED(i_sys_epilogue))
        .INCLUDE("sys_epilogue")
    .ENDIF
        .BYTE(0)
basic_end:
        .BYTE(0,0)
  .ELSE
    .IF(!.DEFINED(i_raw) || i_raw == 0)
        .BYTE($16,$16,$16,$24,$00,$00,$80,$c7)
        .BYTE((o1_end - 1) / 256, (o1_end - 1) % 256)
        .BYTE(o1_start / 256, o1_start % 256)
        .BYTE(0, 0)
    .ENDIF
    .IF(!.DEFINED(i_load_addr))
        .ORG($0500)
    .ELSE
        .ORG(i_load_addr)
    .ENDIF
lowest_addr_out:
  .ENDIF
o1_start:
.ELIF(r_target == 20 || r_target == 23 || r_target == 52 || r_target == 55 ||
      r_target == 16 || r_target == 4 || r_target == 64 || r_target == 128 ||
      r_target == 4032 || r_target == 65)
; -------------------------------------------------------------------
; -- Commodore file header stuff ------------------------------------
; -------------------------------------------------------------------
  .IF(.DEFINED(i_load_addr))
    .IF(!.DEFINED(i_raw) || i_raw == 0)
        .WORD(i_load_addr)
    .ENDIF
        .ORG(i_load_addr)
lowest_addr_out:
  .ELSE
    .IF(!.DEFINED(i_raw) || i_raw == 0)
        .WORD(c_basic_start)
    .ENDIF
        .ORG(c_basic_start)
lowest_addr_out:
        .WORD(basic_end, i_line_number)
    .IF(r_target == 65)
        .BYTE($fe,$02,$30,$3a)          ; BANK0: to disable ROM before SYS
    .ENDIF
        .BYTE($9e, cbm_start / 1000 % 10 + 48, cbm_start / 100 % 10 + 48)
        .BYTE(cbm_start / 10 % 10 + 48, cbm_start % 10 + 48)
    .IF(.DEFINED(i_sys_epilogue))
        .INCLUDE("sys_epilogue")
    .ENDIF
        .BYTE(0)
basic_end:
        .BYTE(0,0)
; -------------------------------------------------------------------
cbm_start:
  .ENDIF
.ELIF(r_target == $a8)
; -------------------------------------------------------------------
; -- Atari file header stuff ------------------------------------
; -------------------------------------------------------------------
  .IF(!.DEFINED(i_raw) || i_raw == 0)
        .WORD($FFFF, a8_start, a8_end - 1)
  .ENDIF
  .IF(!.DEFINED(i_load_addr))
        .ORG($2000)
  .ELSE
        .ORG(i_load_addr)
  .ENDIF
lowest_addr_out:
a8_start:
.ELIF(r_target == $a2)
; -------------------------------------------------------------------
; -- Apple file header stuff ------------------------------------
; -------------------------------------------------------------------
  .IF(!.DEFINED(i_a2_file_type))
    .IF(.DEFINED(i_load_addr))
i_a2_file_type = 6              ; binary
    .ELSE
i_a2_file_type = $fc            ; Applesoft basic
    .ENDIF
  .ENDIF
  .IF(!.DEFINED(i_raw) || i_raw == 0)
        .ORG(0)
as_start:
        ;; prodos file, AppleSingle header
        ;; http://apple2online.com/web_documents/ft_e0.0001_applesingle.pdf
        ;; http://kaiser-edv.de/documents/AppleSingle_AppleDouble.pdf
        .BYTE($00, $05, $16, $00) ; magic
        .BYTE($00, $02, $00, $00) ; version
        .BYTE($00, $00, $00, $00, $00, $00, $00, $00) ; filler
        .BYTE($00, $00, $00, $00, $00, $00, $00, $00)
        .BYTE($00, $02)         ; number of entries
        ;; data entry descriptor
        .BYTE($00, $00, $00, $01) ; entry ID
        .BYTE($00, $00, $00, as_data_entry - as_start) ; offset
        .BYTE($00, $00,
              (a2_end - a2_load) / 256, (a2_end - a2_load) % 256) ; length
        ;; PRODOS entry descriptor
        .BYTE($00, $00, $00, $0b) ; entry ID
        .BYTE($00, $00, $00, as_prodos_entry - as_start) ; offset
        .BYTE($00, $00, $00, $08) ; length
as_prodos_entry:
        ;; PRODOS entry
        .BYTE($00, $c3) ; access
        .BYTE($00, i_a2_file_type) ; filetype
    .IF(.DEFINED(i_load_addr))
        .BYTE($00, $00,
              i_load_addr / 256, i_load_addr % 256) ; aux file type
    .ELSE
        ;; Applesoft basic file
        .BYTE($00, $00,
              c_basic_start / 256, c_basic_start % 256) ; aux file type
    .ENDIF
as_data_entry:
  .ENDIF
  .IF(.DEFINED(i_load_addr))
        .ORG(i_load_addr)
lowest_addr_out:
a2_load:
  .ELSE
        .ORG(c_basic_start)
lowest_addr_out:
a2_load:
        .WORD(basic_end, i_line_number)
        .BYTE($8c, a2_start / 1000 % 10 + 48, a2_start / 100 % 10 + 48)
        .BYTE(a2_start / 10 % 10 + 48, a2_start % 10 + 48)
      .IF(.DEFINED(i_sys_epilogue))
        .INCLUDE("sys_epilogue")
      .ENDIF
        .BYTE(0)
basic_end:
        .BYTE(0,0)
  .ENDIF
; -------------------------------------------------------------------
a2_start:
  .IF(.DEFINED(i_a2_disable_dos))
        lda #$00
        jsr $fe95
        lda #$00
        jsr $fe8b
  .ENDIF
.ELIF(r_target == $bbcb)
; -- No header at all for BBC b
  .IF(.DEFINED(i_load_addr))
        .ORG(i_load_addr)
  .ELSE
        .ORG(c_basic_start)
  .ENDIF
lowest_addr_out:
.ELSE
  .ERROR("Unhandled target for file header stuff")
.ENDIF
; -------------------------------------------------------------------
; -- End of file header stuff ---------------------------------------
; -------------------------------------------------------------------

; -------------------------------------------------------------------
; -- Start of the actual decruncher ---------------------------------
; -------------------------------------------------------------------

; -------------------------------------------------------------------
; -- required symbols:
; --
; --  zp_len_lo                 A zerpoage location used for a byte.
; --  zp_len_hi                 A zerpoage location used for a byte.
; --  zp_src_lo + zp_src_hi     A zeropage location used for a word.
; --  zp_bits_hi                A zeropage location used for a byte.
; --  v_safety_addr
; --  i_table_addr
; --
; -- optional symbols
; --  enter_hook                must exit with y == 0.
; --  fast_effect_hook          must not touch x or y.
; --  slow_effect_hook          must not touch x or y.
; --  exit_hook
; --  stage2_exit_hook          must exit with x == 0 and y == 0.
; --
; -- macros:
; --  enter_hook
; --  effect_hook
; --  exit_hook
; --  stage2_exit_hook
; -------------------------------------------------------------------

zp_bitbuf  = $fd
zp_dest_lo = zp_bitbuf + 1      ; dest addr lo
zp_dest_hi = zp_bitbuf + 2      ; dest addr hi

; -------------------------------------------------------------------
; -- start of stage 1 -----------------------------------------------
; -------------------------------------------------------------------
max_transfer_len = .INCLEN("crunched_data") - 5
        .IF(.DEFINED(enter_hook))
          .INCLUDE("enter_hook")
        .ENDIF
        .IF (c_page0location != $0000)
        ldx #stage3end - (stage3start - 4)
        .ELSE
        tsx
        .ENDIF
cploop:
        lda stage2end - 4,x
        sta c_page0location + $0100 - 4,x
        dex
        bne cploop
.IF(transfer_len > 256)
        ldx #transfer_len / 256 + 1
.ENDIF
        ldy #transfer_len % 256
.IF(transfer_len != max_transfer_len)
        jmp stage2start
.ENDIF
stage1end:
; -------------------------------------------------------------------
; -- end of stage 1 -------------------------------------------------
; -------------------------------------------------------------------
; -------------------------------------------------------------------
; -- start of file part 2 -------------------------------------------
; -------------------------------------------------------------------
file2start:
.IF(.DEFINED(file2_start_hook))
  .INCLUDE("file2_start_hook")
.ENDIF
.IF(!.DEFINED(raw_transfer_len))
  .IF(v_safety_addr > file2start || v_highest_addr < file2start)
raw_transfer_len = 0
lowest_addr = file2start
  .ELSE
raw_transfer_len = file2start - v_safety_addr
lowest_addr = v_safety_addr
  .ENDIF
.ENDIF

.IF(raw_transfer_len > max_transfer_len)
transfer_len = max_transfer_len
.ELSE
transfer_len = raw_transfer_len
.ENDIF
        .INCBIN("crunched_data", transfer_len + 2, max_transfer_len - transfer_len)
file2end:
; -------------------------------------------------------------------
; -- end of file part 2 ---------------------------------------------
; -------------------------------------------------------------------
; -------------------------------------------------------------------
; -- start of stage 2 -----------------------------------------------
; -------------------------------------------------------------------
.IF(transfer_len == 0)
stage2start:
.ELIF(transfer_len < 257)
stage2start:
copy1_loop:
  .IF(transfer_len == 256)
        lda file1start,y
        sta lowest_addr,y
  .ELSE
        lda file1start - 1,y
        sta lowest_addr - 1,y
  .ENDIF
        dey
        bne copy1_loop
.ELSE
        bne stage2start
copy2_loop1:
        dey
lda_fixup:
        lda file1start + transfer_len / 256 * 256,y
sta_fixup:
        sta lowest_addr + transfer_len / 256 * 256,y
stage2start:
        tya
        bne copy2_loop1
        dec lda_fixup + 2
        dec sta_fixup + 2
        dex
        bne copy2_loop1
.ENDIF
; -------------------------------------------------------------------
tabl_bi = i_table_addr
tabl_lo = i_table_addr + encoded_entries
tabl_hi = i_table_addr + 2 * encoded_entries
        clc
table_gen:
        tax
        tya
        and #$0f
        sta tabl_lo,y
        beq shortcut            ; start a new sequence
; -------------------------------------------------------------------
        txa
        adc tabl_lo - 1,y
        sta tabl_lo,y
        lda <zp_len_hi
        adc tabl_hi - 1,y
shortcut:
        sta tabl_hi,y
; -------------------------------------------------------------------
        lda #$01
        sta <zp_len_hi
        lda #$78                ; %01111000
        jsr get_bits
; -------------------------------------------------------------------
        lsr
        tax
        beq rolled
        php
rolle:
        asl <zp_len_hi
        sec
        ror
        dex
        bne rolle
        plp
rolled:
        ror
        sta tabl_bi,y
        bmi no_fixup_lohi
        lda <zp_len_hi
        stx <zp_len_hi
        .BYTE($24)             ; bit zero page
no_fixup_lohi:
        txa
; -------------------------------------------------------------------
        iny
        cpy #encoded_entries
        bne table_gen
        .IF(.DEFINED(stage2_exit_hook))
          .INCLUDE("stage2_exit_hook")
        .ENDIF
; ###################################################################
.IF(i_perf == -1)
; ###################################################################
; ## tiny start #####################################################
; ###################################################################
        ldy #(v_highest_addr - 1) % 256
        jmp begin
; -------------------------------------------------------------------
; -- end of stage 2 -------------------------------------------------
; -------------------------------------------------------------------
        .INCBIN("crunched_data", max_transfer_len + 2, 1) ; => zp_bitbuf
        .WORD((((v_highest_addr - 1) % 65536) / 256) * 256) ; => zp_dest_lo/hi
stage2end:
        .ORG(c_page0location + $0100)
; -------------------------------------------------------------------
; -- start of stage 3 -----------------------------------------------
; -------------------------------------------------------------------
stage3start:
; -------------------------------------------------------------------
; get bits (26 bytes)
;
get_bits:
        adc #$80                ; needs c=0, affects v
        asl
        bpl gb_skip
gb_next:
        asl <zp_bitbuf
        bne gb_no_refill
        pha
        jsr get_crunched_byte
        rol
        sta <zp_bitbuf
        pla
gb_no_refill:
        rol
        bmi gb_next
gb_skip:
        bvs gb_get_hi
        rts
gb_get_hi:
        sec
        sta <zp_bits_hi
; -------------------------------------------------------------------
; get crunched byte (15 bytes) + hooks
;
get_crunched_byte:
        .IF(.DEFINED(fast_effect_hook))
          .INCLUDE("effect_hook")
        .ENDIF
        lda get_byte_fixup + 1
        bne get_byte_skip_hi
        dec get_byte_fixup + 2
        .IF(.DEFINED(slow_effect_hook))
          .INCLUDE("effect_hook")
        .ENDIF
get_byte_skip_hi:
        dec get_byte_fixup + 1
get_byte_fixup:
        lda lowest_addr + max_transfer_len
        rts
; -------------------------------------------------------------------
; fetch sequence length index (13 bytes)
; x must be #0 when entering and contains the length index + 1
; when exiting or 0 for literal byte
begin:
.IF(.DEFINED(i_reuse_offset))
        ror <zp_ro_state
.ENDIF
next_bit:
        asl <zp_bitbuf
        bne nofetch8
        jsr get_crunched_byte
        rol
        sta <zp_bitbuf
nofetch8:
        inx
        bcc next_bit
; -------------------------------------------------------------------
; check for literal byte (4 bytes)
;
        cpx #1
        beq copy_next
; -------------------------------------------------------------------
; check for decrunch done and literal sequences (4 bytes)
;
        cpx #$12
        bcs exit_or_lit_seq
; -------------------------------------------------------------------
; calulate length of sequence (zp_len) (26(16) bytes)
; exit with len_lo in X
;
        lda #0
        sta <zp_bits_hi
        lda tabl_bi - 2,x
        jsr get_bits
        adc tabl_lo - 2,x       ; we have now calculated zp_len_lo
        sta <zp_len_lo
.IF(!.DEFINED(i_max_sequence_length_256))
        lda <zp_bits_hi
        adc tabl_hi - 2,x       ; c = 0 after this.
        sta <zp_len_hi
        ldx <zp_len_lo
.ELSE
        tax
.ENDIF
.IF(!.DEFINED(i_max_sequence_length_256))
        lda #0
.ENDIF
.IF(.DEFINED(i_reuse_offset))
; -------------------------------------------------------------------
; here we decide to reuse latest offset or not (13(15) bytes)
;
        bit <zp_ro_state
        bpl no_reuse
        bvs no_reuse
.IF(.DEFINED(i_max_sequence_length_256))
        lda #$00                ; fetch one bit
.ENDIF
        jsr gb_next
        bne copy_next           ; bit != 0 => C=0, reuse previous offset
no_reuse:
.ENDIF
; -------------------------------------------------------------------
; here we decide what offset table to use (17(15) bytes)
;
.IF(!.DEFINED(i_max_sequence_length_256))
        sta <zp_bits_hi
.ENDIF
        lda #$f1
.IF(.DEFINED(i_fourth_offset_table))
        cpx #$04
.ELSE
        cpx #$03
.ENDIF
        bcs gbnc2_next
        lda tabl_bit - 1,x
gbnc2_next:
        clv
        jsr gb_next
        clc
        tax
; -------------------------------------------------------------------
; calulate absolute offset (zp_src) (20 bytes)
;
        lda tabl_bi,x
        jsr get_bits
        adc tabl_lo,x
        sta <zp_src_lo
        lda <zp_bits_hi
        adc tabl_hi,x
        adc <zp_dest_hi
        sta <zp_src_hi
; -------------------------------------------------------------------
; prepare for copy loop (2 bytes)
;
pre_copy:
        ldx <zp_len_lo
; -------------------------------------------------------------------
; main copy loop (31(25) bytes)
;
copy_next:
        bcs get_literal_byte
        lda (zp_src_lo),y
literal_byte_gotten:
        sta (zp_dest_lo),y
        tya
        bne copy_skip_hi
        dec <zp_dest_hi
        dec <zp_src_hi
copy_skip_hi:
        dey
        dex
        bne copy_next
.IF(!.DEFINED(i_max_sequence_length_256))
        lda <zp_len_hi
.ENDIF
        beq begin
.IF(!.DEFINED(i_max_sequence_length_256))
        dec <zp_len_hi
        jmp copy_next
.ENDIF
get_literal_byte:
        jsr get_crunched_byte
        bcs literal_byte_gotten
; -------------------------------------------------------------------
; exit or literal sequence handling (16(12) bytes)
;
exit_or_lit_seq:
.IF(.DEFINED(i_literal_sequences_used))
        beq decr_exit
        jsr get_crunched_byte
  .IF(!.DEFINED(i_max_sequence_length_256))
        sta <zp_len_hi
  .ENDIF
        jsr get_crunched_byte
        tax
        bcs copy_next
decr_exit:
.ENDIF
.IF(.DEFINED(exit_hook))
  .INCLUDE("exit_hook")
.ELSE
        rts
.ENDIF
.IF(.DEFINED(i_fourth_offset_table))
; -------------------------------------------------------------------
; the static stable used for bits+offset for lengths 1, 2 and 3 (3 bytes)
; bits 2, 4, 4 and offsets 64, 48, 32 corresponding to
; %11010000, %11110011, %11110010
tabl_bit:
        .BYTE($d0, $f3, $f2)
.ELSE
; -------------------------------------------------------------------
; the static stable used for bits+offset for lengths 1 and 2 (2 bytes)
; bits 2, 4 and offsets 48, 32 corresponding to %11001100, %11110010
tabl_bit:
        .BYTE($cc, $f2)
.ENDIF
; ###################################################################
; ## tiny end #######################################################
; ###################################################################
.ELIF(i_perf == 0)
; ###################################################################
; ## small start ####################################################
; ###################################################################
        ldy #(v_highest_addr - 1) % 256
        txa
        jmp begin_stx
; -------------------------------------------------------------------
; -- end of stage 2 -------------------------------------------------
; -------------------------------------------------------------------
        .INCBIN("crunched_data", max_transfer_len + 2, 1) ; => zp_bitbuf
        .WORD((((v_highest_addr - 1) % 65536) / 256) * 256) ; => zp_dest_lo/hi
stage2end:
        .ORG(c_page0location + $0100)
; -------------------------------------------------------------------
; -- start of stage 3 -----------------------------------------------
; -------------------------------------------------------------------
stage3start:
; -------------------------------------------------------------------
; get bits (26 bytes)
;
get_bits:
        adc #$80                ; needs c=0, affects v
        asl
        bpl gb_skip
gb_next:
        asl <zp_bitbuf
        bne gb_no_refill
        pha
        jsr get_crunched_byte
        rol
        sta <zp_bitbuf
        pla
gb_no_refill:
        rol
        bmi gb_next
gb_skip:
        bvs gb_get_hi
        rts
gb_get_hi:
        sec
        sta <zp_bits_hi
; -------------------------------------------------------------------
; get crunched byte (15 bytes) + hooks
;
get_crunched_byte:
        .IF(.DEFINED(fast_effect_hook))
          .INCLUDE("effect_hook")
        .ENDIF
        lda get_byte_fixup + 1
        bne get_byte_skip_hi
        dec get_byte_fixup + 2
        .IF(.DEFINED(slow_effect_hook))
          .INCLUDE("effect_hook")
        .ENDIF
get_byte_skip_hi:
        dec get_byte_fixup + 1
get_byte_fixup:
        lda lowest_addr + max_transfer_len
        rts
; -------------------------------------------------------------------
; copy one literal byte to destination (11 bytes)
;
literal_start1:
        jsr get_crunched_byte
        sta (zp_dest_lo),y
        tya
        bne no_hi_decr
        dec <zp_dest_hi
.IF(.DEFINED(i_reuse_offset))
        dec <zp_src_hi
.ENDIF
no_hi_decr:
        dey
; -------------------------------------------------------------------
; fetch sequence length index (14(16) bytes)
; x must be #0 when entering and contains the length index + 1
; when exiting or 0 for literal byte
begin:
.IF(.DEFINED(i_reuse_offset))
        ror <zp_ro_state
.ENDIF
        dex
no_literal1:
        asl <zp_bitbuf
        bne nofetch8
        jsr get_crunched_byte
        rol
        sta <zp_bitbuf
nofetch8:
        inx
        bcc no_literal1
; -------------------------------------------------------------------
; check for literal byte (2 bytes)
;
        beq literal_start1
; -------------------------------------------------------------------
; check for decrunch done and literal sequences (4 bytes)
;
        cpx #$11
        bcs exit_or_lit_seq
; -------------------------------------------------------------------
; calulate length of sequence (zp_len) (22(12) bytes)
;
        lda tabl_bi - 1,x
        jsr get_bits
        adc tabl_lo - 1,x       ; we have now calculated zp_len_lo
        sta <zp_len_lo
.IF(!.DEFINED(i_max_sequence_length_256))
        lda <zp_bits_hi
        adc tabl_hi - 1,x       ; c = 0 after this.
        sta <zp_len_hi
        ldx <zp_len_lo
.ELSE
        tax
.ENDIF
.IF(!.DEFINED(i_max_sequence_length_256))
        lda #0
.ENDIF
.IF(.DEFINED(i_reuse_offset))
; -------------------------------------------------------------------
; here we decide to reuse latest offset or not (13(15) bytes)
;
        bit <zp_ro_state
        bpl no_reuse
        bvs no_reuse
.IF(.DEFINED(i_max_sequence_length_256))
        lda #$00                ; fetch one bit
.ENDIF
        jsr gb_next
        bne copy_next           ; bit != 0 => C=0, reuse previous offset
no_reuse:
.ENDIF
; -------------------------------------------------------------------
; here we decide what offset table to use (17(15) bytes)
;
.IF(!.DEFINED(i_max_sequence_length_256))
        sta <zp_bits_hi
.ENDIF
        lda #$f1
.IF(.DEFINED(i_fourth_offset_table))
        cpx #$04
.ELSE
        cpx #$03
.ENDIF
        bcs gbnc2_next
        lda tabl_bit - 1,x
gbnc2_next:
        clv
        jsr gb_next
        clc
        tax
; -------------------------------------------------------------------
; calulate absolute offset (zp_src) (24 bytes)
;
.IF(!.DEFINED(i_max_sequence_length_256))
        lda #0
        sta <zp_bits_hi
.ENDIF
        lda tabl_bi,x
        jsr get_bits
        adc tabl_lo,x
        sta <zp_src_lo
        lda <zp_bits_hi
        adc tabl_hi,x
        adc <zp_dest_hi
        sta <zp_src_hi
; -------------------------------------------------------------------
; prepare for copy loop (2 bytes)
;
pre_copy:
        ldx <zp_len_lo
; -------------------------------------------------------------------
; main copy loop (31(25) bytes)
;
copy_next:
.IF(.DEFINED(i_literal_sequences_used))
        bcs get_literal_byte
.ENDIF
        lda (zp_src_lo),y
literal_byte_gotten:
        sta (zp_dest_lo),y
        tya
        bne copy_skip_hi
        dec <zp_dest_hi
        dec <zp_src_hi
copy_skip_hi:
        dey
        dex
        bne copy_next
.IF(!.DEFINED(i_max_sequence_length_256))
        lda <zp_len_hi
.ENDIF
begin_stx:
        stx <zp_bits_hi
        beq begin
.IF(!.DEFINED(i_max_sequence_length_256))
        dec <zp_len_hi
        jmp copy_next
.ENDIF
.IF(.DEFINED(i_literal_sequences_used))
get_literal_byte:
        jsr get_crunched_byte
        bcs literal_byte_gotten
.ENDIF
; -------------------------------------------------------------------
; exit or literal sequence handling (16(12) bytes)
;
exit_or_lit_seq:
.IF(.DEFINED(i_literal_sequences_used))
        beq decr_exit
        jsr get_crunched_byte
  .IF(!.DEFINED(i_max_sequence_length_256))
        sta <zp_len_hi
  .ENDIF
        jsr get_crunched_byte
        tax
        bcs copy_next
decr_exit:
.ENDIF
.IF(.DEFINED(exit_hook))
  .INCLUDE("exit_hook")
.ELSE
        rts
.ENDIF
.IF(.DEFINED(i_fourth_offset_table))
; -------------------------------------------------------------------
; the static stable used for bits+offset for lengths 1, 2 and 3 (3 bytes)
; bits 2, 4, 4 and offsets 64, 48, 32 corresponding to
; %11010000, %11110011, %11110010
tabl_bit:
        .BYTE($d0, $f3, $f2)
.ELSE
; -------------------------------------------------------------------
; the static stable used for bits+offset for lengths 1 and 2 (2 bytes)
; bits 2, 4 and offsets 48, 32 corresponding to %11001100, %11110010
tabl_bit:
        .BYTE($cc, $f2)
.ENDIF
; ###################################################################
; ## small end ######################################################
; ###################################################################
.ELIF(i_perf == 1)
; ###################################################################
; ## quick start ####################################################
; ###################################################################
        ldy #(v_highest_addr - 1) % 256
        txa
        jmp begin_stx
; -------------------------------------------------------------------
; -- end of stage 2 -------------------------------------------------
; -------------------------------------------------------------------
        .INCBIN("crunched_data", max_transfer_len + 2, 1) ; => zp_bitbuf
        .WORD((((v_highest_addr - 1) % 65536) / 256) * 256) ; => zp_dest_lo/hi
stage2end:
        .ORG(c_page0location + $0100)
; -------------------------------------------------------------------
; -- start of stage 3 -----------------------------------------------
; -------------------------------------------------------------------
stage3start:
; -------------------------------------------------------------------
; get bits (26 bytes)
;
get_bits:
        adc #$80                ; needs c=0, affects v
        asl
        bpl gb_skip
gb_next:
        asl <zp_bitbuf
        bne gb_no_refill
        pha
        jsr get_crunched_byte
        rol
        sta <zp_bitbuf
        pla
gb_no_refill:
        rol
        bmi gb_next
gb_skip:
        bvs gb_get_hi
        rts
gb_get_hi:
        sec
        sta <zp_bits_hi
; -------------------------------------------------------------------
; get crunched byte (15 bytes) + hooks
;
get_crunched_byte:
        .IF(.DEFINED(fast_effect_hook))
          .INCLUDE("effect_hook")
        .ENDIF
        lda get_byte_fixup + 1
        bne get_byte_skip_hi
        dec get_byte_fixup + 2
        .IF(.DEFINED(slow_effect_hook))
          .INCLUDE("effect_hook")
        .ENDIF
get_byte_skip_hi:
        dec get_byte_fixup + 1
get_byte_fixup:
        lda lowest_addr + max_transfer_len
        rts
; -------------------------------------------------------------------
; copy one literal byte to destination (11 bytes)
;
literal_start1:
        jsr get_crunched_byte
        sta (zp_dest_lo),y
        tya
        bne no_hi_decr
        dec <zp_dest_hi
.IF(.DEFINED(i_reuse_offset))
        dec <zp_src_hi
.ENDIF
no_hi_decr:
        dey
; -------------------------------------------------------------------
; fetch sequence length index (14(16) bytes)
; x must be #0 when entering and contains the length index + 1
; when exiting or 0 for literal byte
begin:
.IF(.DEFINED(i_reuse_offset))
        ror <zp_ro_state
.ENDIF
        dex
no_literal1:
        asl <zp_bitbuf
        bne nofetch8
        jsr get_crunched_byte
        rol
        sta <zp_bitbuf
nofetch8:
        inx
        bcc no_literal1
; -------------------------------------------------------------------
; check for literal byte (2 bytes)
;
        beq literal_start1
; -------------------------------------------------------------------
; check for decrunch done and literal sequences (4 bytes)
;
        cpx #$11
        bcs exit_or_lit_seq
; -------------------------------------------------------------------
; calulate length of sequence (zp_len) (22(12) bytes)
;
        lda tabl_bi - 1,x
        jsr get_bits
        adc tabl_lo - 1,x       ; we have now calculated zp_len_lo
        sta <zp_len_lo
.IF(!.DEFINED(i_max_sequence_length_256))
        lda <zp_bits_hi
        adc tabl_hi - 1,x       ; c = 0 after this.
        sta <zp_len_hi
        ldx <zp_len_lo
.ELSE
        tax
.ENDIF
.IF(!.DEFINED(i_max_sequence_length_256))
        lda #0
.ENDIF
.IF(.DEFINED(i_reuse_offset))
; -------------------------------------------------------------------
; here we decide to reuse latest offset or not (4 bytes)
        bit <zp_ro_state
        bmi test_reuse
no_reuse:
.ENDIF
; -------------------------------------------------------------------
; here we decide what offset table to use (27(25) bytes)
;
.IF(!.DEFINED(i_max_sequence_length_256))
        sta <zp_bits_hi
.ENDIF
        lda #$e1
.IF(.DEFINED(i_fourth_offset_table))
        cpx #$04
.ELSE
        cpx #$03
.ENDIF
        bcs gbnc2_next
        lda tabl_bit - 1,x
gbnc2_next:
        asl <zp_bitbuf
        bne gbnc2_ok
        tax
        jsr get_crunched_byte
        rol
        sta <zp_bitbuf
        txa
gbnc2_ok:
        rol
        bcs gbnc2_next
        tax
; -------------------------------------------------------------------
; calulate absolute offset (zp_src) (20 bytes)
;
        lda tabl_bi,x
        jsr get_bits
        adc tabl_lo,x
        sta <zp_src_lo
        lda <zp_bits_hi
        adc tabl_hi,x
        adc <zp_dest_hi
        sta <zp_src_hi
; -------------------------------------------------------------------
; prepare for copy loop (4(2) bytes)
;
pre_copy:
        ldx <zp_len_lo
; -------------------------------------------------------------------
; main copy loop (31(25) bytes)
;
copy_next:
.IF(.DEFINED(i_literal_sequences_used))
        bcs get_literal_byte
.ENDIF
        lda (zp_src_lo),y
literal_byte_gotten:
        sta (zp_dest_lo),y
        tya
        bne copy_skip_hi
        dec <zp_dest_hi
        dec <zp_src_hi
copy_skip_hi:
        dey
        dex
        bne copy_next
.IF(!.DEFINED(i_max_sequence_length_256))
        lda <zp_len_hi
.ENDIF
begin_stx:
        stx <zp_bits_hi
        beq begin
.IF(!.DEFINED(i_max_sequence_length_256))
        dec <zp_len_hi
        jmp copy_next
.ENDIF
.IF(.DEFINED(i_literal_sequences_used))
get_literal_byte:
        jsr get_crunched_byte
        bcs literal_byte_gotten
.ENDIF
.IF(.DEFINED(i_reuse_offset))
; -------------------------------------------------------------------
; test for offset reuse (11 bytes)
;
test_reuse:
        bvs no_reuse
.IF(.DEFINED(i_max_sequence_length_256))
        lda #$00                ; fetch one bit
.ENDIF
        jsr gb_next
        beq no_reuse            ; bit == 0 => C=0, no reuse
        bne copy_next           ; bit != 0 => C=0, reuse previous offset
.ENDIF
; -------------------------------------------------------------------
; exit or literal sequence handling (16(12) bytes)
;
exit_or_lit_seq:
.IF(.DEFINED(i_literal_sequences_used))
        beq decr_exit
        jsr get_crunched_byte
  .IF(!.DEFINED(i_max_sequence_length_256))
        sta <zp_len_hi
  .ENDIF
        jsr get_crunched_byte
        tax
        bcs copy_next
decr_exit:
.ENDIF
.IF(.DEFINED(exit_hook))
  .INCLUDE("exit_hook")
.ELSE
        rts
.ENDIF
.IF(.DEFINED(i_fourth_offset_table))
; -------------------------------------------------------------------
; the static stable used for bits+offset for lengths 1, 2 and 3 (3 bytes)
; bits 2, 4, 4 and offsets 64, 48, 32 corresponding to
; %10010000, %11100011, %11100010
tabl_bit:
        .BYTE($90, $e3, $e2)
.ELSE
; -------------------------------------------------------------------
; the static stable used for bits+offset for lengths 1 and 2 (2 bytes)
; bits 2, 4 and offsets 48, 32 corresponding to %10001100, %11100010
tabl_bit:
        .BYTE($8c, $e2)
.ENDIF
; ###################################################################
; ## quick end ######################################################
; ###################################################################
.ELIF(i_perf == 2)
; ###################################################################
; ## fast start #####################################################
; ###################################################################
        ldy #v_highest_addr % 256
        txa
        jmp begin_stx
; -------------------------------------------------------------------
; -- end of stage 2 -------------------------------------------------
; -------------------------------------------------------------------
        .INCBIN("crunched_data", max_transfer_len + 2, 1) ; => zp_bitbuf
        .WORD(((v_highest_addr % 65536) / 256) * 256) ; => zp_dest_lo/hi
stage2end:
        .ORG(c_page0location + $0100)
; -------------------------------------------------------------------
; -- start of stage 3 -----------------------------------------------
; -------------------------------------------------------------------
stage3start:
; -------------------------------------------------------------------
; get bits (26 bytes)
;
get_bits:
        adc #$80                ; needs c=0, affects v
        asl
        bpl gb_skip
gb_next:
        asl <zp_bitbuf
        bne gb_no_refill
        pha
        jsr get_crunched_byte
        rol
        sta <zp_bitbuf
        pla
gb_no_refill:
        rol
        bmi gb_next
gb_skip:
        bvs gb_get_hi
        rts
gb_get_hi:
        sec
        sta <zp_bits_hi
; -------------------------------------------------------------------
; get crunched byte (15 bytes) + hooks
;
get_crunched_byte:
        .IF(.DEFINED(fast_effect_hook))
          .INCLUDE("effect_hook")
        .ENDIF
        lda get_byte_fixup + 1
        bne get_byte_skip_hi
        dec get_byte_fixup + 2
        .IF(.DEFINED(slow_effect_hook))
          .INCLUDE("effect_hook")
        .ENDIF
get_byte_skip_hi:
        dec get_byte_fixup + 1
get_byte_fixup:
        lda lowest_addr + max_transfer_len
        rts
; -------------------------------------------------------------------
; copy one literal byte to destination (11 bytes)
;
literal_start1:
        tya
        bne no_hi_decr
        dec <zp_dest_hi
.IF(.DEFINED(i_reuse_offset))
        dec <zp_src_hi
.ENDIF
no_hi_decr:
        dey
        jsr get_crunched_byte
        sta (zp_dest_lo),y
; -------------------------------------------------------------------
; fetch sequence length index (22(24) bytes)
; x must be #0 when entering and contains the length index + 1
; when exiting or 0 for literal byte
begin:
.IF(.DEFINED(i_reuse_offset))
        ror <zp_ro_state
.ENDIF
        asl <zp_bitbuf
        beq fetch8
        bcs literal_start1
        lda <zp_bitbuf
        inx
no_literal1:
        asl
        bne nofetch8
fetch8:
        jsr get_crunched_byte
        rol
nofetch8:
        inx
        bcc no_literal1
        sta <zp_bitbuf
        dex
; -------------------------------------------------------------------
; check for literal byte (2 bytes)
;
        beq literal_start1
; -------------------------------------------------------------------
; check for decrunch done and literal sequences (4 bytes)
;
        cpx #$11
        bcs exit_or_lit_seq
; -------------------------------------------------------------------
; calulate length of sequence (zp_len) (22(12) bytes)
;
        lda tabl_bi - 1,x
        jsr get_bits
        adc tabl_lo - 1,x       ; we have now calculated zp_len_lo
        sta <zp_len_lo
.IF(!.DEFINED(i_max_sequence_length_256))
        lda <zp_bits_hi
        adc tabl_hi - 1,x       ; c = 0 after this.
        sta <zp_len_hi
        ldx <zp_len_lo
.ELSE
        tax
.ENDIF
.IF(!.DEFINED(i_max_sequence_length_256))
        lda #0
.ENDIF
.IF(.DEFINED(i_reuse_offset))
; -------------------------------------------------------------------
; here we decide to reuse latest offset or not (4 bytes)
        bit <zp_ro_state
        bmi test_reuse
no_reuse:
.ENDIF
; -------------------------------------------------------------------
; here we decide what offset table to use (27(25) bytes)
;
.IF(!.DEFINED(i_max_sequence_length_256))
        sta <zp_bits_hi
.ENDIF
        lda #$e1
.IF(.DEFINED(i_fourth_offset_table))
        cpx #$04
.ELSE
        cpx #$03
.ENDIF
        bcs gbnc2_next
        lda tabl_bit - 1,x
gbnc2_next:
        asl <zp_bitbuf
        bne gbnc2_ok
        tax
        jsr get_crunched_byte
        rol
        sta <zp_bitbuf
        txa
gbnc2_ok:
        rol
        bcs gbnc2_next
        tax
; -------------------------------------------------------------------
; calulate absolute offset (zp_src) (20 bytes)
;
        lda tabl_bi,x
        jsr get_bits
        adc tabl_lo,x
        sta <zp_src_lo
        lda <zp_bits_hi
        adc tabl_hi,x
        adc <zp_dest_hi
        sta <zp_src_hi
; -------------------------------------------------------------------
; prepare for copy loop (4(2) bytes)
;
pre_copy:
        ldx <zp_len_lo
; -------------------------------------------------------------------
; main copy loop (24 bytes)
;
copy_next:
        tya
        beq copy_decr_hi
copy_skip_hi:
        dey
        beq tya_loop
.IF(.DEFINED(i_literal_sequences_used))
        bcs get_literal_byte1
.ENDIF
        lda (zp_src_lo),y
literal_byte_gotten1:
        sta (zp_dest_lo),y
        dex
        bne copy_skip_hi
after_len_lo:
.IF(!.DEFINED(i_max_sequence_length_256))
        lda <zp_len_hi
.ENDIF
begin_stx:
        stx <zp_bits_hi
        beq begin
.IF(!.DEFINED(i_max_sequence_length_256))
        dec <zp_len_hi
        jmp copy_next
.ENDIF
.IF(.DEFINED(i_literal_sequences_used))
get_literal_byte1:
        jsr get_crunched_byte
        bcs literal_byte_gotten1
.ENDIF
.IF(.DEFINED(i_reuse_offset))
; -------------------------------------------------------------------
; test for offset reuse (11 bytes)
;
test_reuse:
        bvs no_reuse
.IF(.DEFINED(i_max_sequence_length_256))
        lda #$00                ; fetch one bit
.ENDIF
        jsr gb_next
        beq no_reuse            ; bit == 0 => C=0, no reuse
        bne copy_next           ; bit != 0 => C=0, reuse previous offset
.ENDIF
; -------------------------------------------------------------------
; exit or literal sequence handling (16(12) bytes)
;
exit_or_lit_seq:
.IF(.DEFINED(i_literal_sequences_used))
        beq decr_exit
        jsr get_crunched_byte
  .IF(!.DEFINED(i_max_sequence_length_256))
        sta <zp_len_hi
  .ENDIF
        jsr get_crunched_byte
        tax
        bcs copy_next
decr_exit:
.ENDIF
.IF(.DEFINED(exit_hook))
  .INCLUDE("exit_hook")
.ELSE
        rts
.ENDIF
; -------------------------------------------------------------------
; main copy loop 2 (16 bytes)
;
tya_loop:
.IF(.DEFINED(i_literal_sequences_used))
        bcs get_literal_byte2
.ENDIF
        lda (zp_src_lo),y
literal_byte_gotten2:
        sta (zp_dest_lo),y
        dex
        beq after_len_lo
copy_decr_hi:
        dec <zp_dest_hi
        dec <zp_src_hi
        bne copy_skip_hi
.IF(.DEFINED(i_literal_sequences_used))
get_literal_byte2:
        jsr get_crunched_byte
        bcs literal_byte_gotten2
.ENDIF
.IF(.DEFINED(i_fourth_offset_table))
; -------------------------------------------------------------------
; the static stable used for bits+offset for lengths 1, 2 and 3 (3 bytes)
; bits 2, 4, 4 and offsets 64, 48, 32 corresponding to
; %10010000, %11100011, %11100010
tabl_bit:
        .BYTE($90, $e3, $e2)
.ELSE
; -------------------------------------------------------------------
; the static stable used for bits+offset for lengths 1 and 2 (2 bytes)
; bits 2, 4 and offsets 48, 32 corresponding to %10001100, %11100010
tabl_bit:
        .BYTE($8c, $e2)
.ENDIF
; ###################################################################
; ## fast end #######################################################
; ###################################################################
.ELSE
  .ERROR("Symbol i_perf must have a value of [-1 - 2].")
.ENDIF
; ###################################################################
; -------------------------------------------------------------------
stage3end:
; -------------------------------------------------------------------
; -- end of stage 3 -------------------------------------------------
; -------------------------------------------------------------------
        .ORG(stage2end + stage3end - stage3start)
; -------------------------------------------------------------------
; -- start of file part 1 -------------------------------------------
; -------------------------------------------------------------------
file1start:
        .INCBIN("crunched_data", 2, transfer_len)
file1end:
highest_addr_out:
v_highest_addr_out = highest_addr_out
; -------------------------------------------------------------------
; -- end of file part 1 ---------------------------------------------
; -------------------------------------------------------------------

; -------------------------------------------------------------------
; -- End of the actual decruncher -----------------------------------
; -------------------------------------------------------------------

; -------------------------------------------------------------------
; -- Start of file footer stuff -------------------------------------
; -------------------------------------------------------------------
.IF(r_target == 1)
; -------------------------------------------------------------------
; -- Start of Oric-1 file footer stuff ------------------------------
; -------------------------------------------------------------------
o1_end:
.ELIF(r_target == 20 || r_target == 23 || r_target == 52 || r_target == 55 ||
      r_target == 16 || r_target == 4 || r_target == 64 || r_target == 128 ||
      r_target == 4032 || r_target == 65)
; -------------------------------------------------------------------
; -- Start of Commodore file footer stuff ---------------------------
; -------------------------------------------------------------------
.ELIF(r_target == $a8)
; -------------------------------------------------------------------
; -- Start of Atari file footer stuff -------------------------------
; -------------------------------------------------------------------
a8_end:
.IF(!.DEFINED(i_raw) || i_raw == 0)
        .WORD($02e0, $02e1, a8_start)
.ENDIF
.ELIF(r_target == $a2)
; -------------------------------------------------------------------
; -- Start of Apple file footer stuff -------------------------------
; -------------------------------------------------------------------
a2_end:
.ELIF(r_target == $bbcb)
; -- No header at all for BBC b
.ELSE
  .ERROR("Unhandled target for file header stuff")
.ENDIF
; -------------------------------------------------------------------
; -- End of file footer stuff ---------------------------------------
; -------------------------------------------------------------------
