decompressor: gzip/tinf -> limlz
removes external dependency on tinf by replacing the compression algorithm with a simpler, faster, smaller and more auditable fixed-width LZ77 encoding purpose-tailored to x86 code mixed with data. before: decompressor.bin 2,492 bytes (tinf dependency) with .text 0x875 and .rodata 0x13c bytes each. after: decompressor.bin consists only of .text, 0xe6-byte decompressor; 90.8% reduction in decompressor volume. the dependency on gzip during compile-time is replaced by host/limlzpack.c, a Lempel-Ziv encoder in 275 SLoC that uses a suffix array matchfinder (prefix-doubling in mathcal O(n log^2 n) and Storer-Szymanski backwards parse. the fixed-width formats packets as [F][LLLL][MMM], favouring a literal-skewed distribution with F switching between one-byte and two-byte offsets (favouring recent statistics). integrity checking is done via crc32 with the polynomial 0xEDB88320, reflected. the effective loss in compression ratio by using a tremendously simpler and less packed with edge cases algorithm causes a compression ratio hit well below 1KB, factoring in the stub sizes. also adds new machinery for host cc detection per review.
diff --git a/.gitignore b/.gitignore
index bbecc3fe..73911602 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,7 +13,7 @@
*.exe
*.EFI
*.bin
-*.bin.gz
+*.bin.limlz
*.tar*
*.elf
*.hdd
@@ -32,10 +32,8 @@
/common/lib/stb_image.h
/common/cc-runtime.s2.c
/cc-runtime
-/decompressor/tinf
/decompressor/cc-runtime.c
/libfdt
-/tinf
/edk2-ovmf
/bochsout.txt
/bx_enh_dbg.ini
diff --git a/3RDPARTY.md b/3RDPARTY.md
index 1ab469c3..4a255511 100644
--- a/3RDPARTY.md
+++ b/3RDPARTY.md
@@ -49,9 +49,6 @@ below) provides headers and build-time support for UEFI.
in case of installed copies, assuming the file has not been otherwise
removed by the packager.
-- [tinf](https://github.com/jibsen/tinf) (Zlib) is used in early x86 BIOS
-stages for GZIP decompression of stage2.
-
- [Flanterm](https://github.com/Mintsuki/Flanterm) (BSD-2-Clause) is used for
text related screen drawing.
diff --git a/GNUmakefile.in b/GNUmakefile.in
index a7f515c2..d26fb01d 100644
--- a/GNUmakefile.in
+++ b/GNUmakefile.in
@@ -54,6 +54,8 @@ AWK := @AWK@
export AWK
CC := @CC@
+CC_FOR_BUILD := @CC_FOR_BUILD@
+CFLAGS_FOR_BUILD := @CFLAGS_FOR_BUILD@
CPPFLAGS := @CPPFLAGS@
CFLAGS := @CFLAGS@
@@ -185,7 +187,7 @@ uninstall:
rm -f '$(call SHESCAPE,$(DESTDIR)$(bindir))/limine'
rm -rf '$(call SHESCAPE,$(DESTDIR)$(datarootdir))/limine'
-$(call MKESCAPE,$(BUILDDIR))/stage1.stamp: $(STAGE1_FILES) $(call MKESCAPE,$(BUILDDIR))/decompressor-build/decompressor.bin $(call MKESCAPE,$(BUILDDIR))/common-bios/stage2.bin.gz
+$(call MKESCAPE,$(BUILDDIR))/stage1.stamp: $(STAGE1_FILES) $(call MKESCAPE,$(BUILDDIR))/decompressor-build/decompressor.bin $(call MKESCAPE,$(BUILDDIR))/common-bios/stage2.bin.limlz
$(MKDIR_P) '$(call SHESCAPE,$(BINDIR))'
cd '$(call SHESCAPE,$(SRCDIR))/stage1/hdd' && nasm bootsect.asm -Wall -w-unknown-warning -w-reloc $(WERROR_FLAG) -fbin -DBUILDDIR="'"'$(call NASMESCAPE,$(BUILDDIR))'"'" -o '$(call SHESCAPE,$(BINDIR))/limine-bios-hdd.bin'
ifneq ($(BUILD_BIOS_CD),no)
@@ -301,7 +303,6 @@ dist:
rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/picoefi/.gitignore"
rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/cc-runtime"
rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/libfdt/.git"
- rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/tinf"
rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/common/lib/stb_image.h.nopatch"
rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/.git"
rm -rf '$(call SHESCAPE,$(BUILDDIR))'/"$(DIST_OUTPUT)/.gitignore"
@@ -327,7 +328,7 @@ distclean: clean
.PHONY: maintainer-clean
maintainer-clean: distclean
- cd '$(call SHESCAPE,$(SRCDIR))' && rm -rf flanterm common/lib/stb_image.h.nopatch common/lib/stb_image.h decompressor/tinf tinf libfdt freestnd-c-hdrs cc-runtime common/cc-runtime.s2.c decompressor/cc-runtime.c limine-protocol picoefi configure timestamps build-aux *'~' autom4te.cache aclocal.m4 *.tar*
+ cd '$(call SHESCAPE,$(SRCDIR))' && rm -rf flanterm common/lib/stb_image.h.nopatch common/lib/stb_image.h libfdt freestnd-c-hdrs cc-runtime common/cc-runtime.s2.c decompressor/cc-runtime.c limine-protocol picoefi configure timestamps build-aux *'~' autom4te.cache aclocal.m4 *.tar*
.PHONY: common-uefi-x86-64
common-uefi-x86-64:
@@ -380,15 +381,20 @@ common-uefi-ia32-clean:
rm -rf '$(call SHESCAPE,$(BUILDDIR))/common-uefi-ia32'
.PHONY: common-bios
-common-bios:
+common-bios: $(call MKESCAPE,$(BINDIR))/limlzpack
$(MAKE) -C '$(call SHESCAPE,$(SRCDIR))/common' -f common.mk \
TARGET=bios \
- BUILDDIR='$(call SHESCAPE,$(BUILDDIR))/common-bios'
+ BUILDDIR='$(call SHESCAPE,$(BUILDDIR))/common-bios' \
+ LIMLZPACK='$(call SHESCAPE,$(BINDIR))/limlzpack'
.PHONY: common-bios-clean
common-bios-clean:
rm -rf '$(call SHESCAPE,$(BUILDDIR))/common-bios'
+$(call MKESCAPE,$(BINDIR))/limlzpack: $(call MKESCAPE,$(SRCDIR))/tools/limlzpack.c
+ $(MKDIR_P) '$(call SHESCAPE,$(BINDIR))'
+ $(CC_FOR_BUILD) $(CFLAGS_FOR_BUILD) -std=c99 -Wall -Wextra $(WERROR_FLAG) '$(call SHESCAPE,$<)' -o '$(call SHESCAPE,$@)'
+
.PHONY: decompressor
decompressor:
$(MAKE) -C '$(call SHESCAPE,$(SRCDIR))/decompressor' -f decompressor.mk \
diff --git a/INSTALL.md b/INSTALL.md
index 573b59f4..21307cff 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -7,7 +7,7 @@
In order to build Limine, the following programs have to be installed:
common UNIX tools (also known as `coreutils`),
-`GNU make`, `grep`, `sed`, `find`, `awk`, `gzip`, `nasm`, `mtools`
+`GNU make`, `grep`, `sed`, `find`, `awk`, `nasm`, `mtools`
(optional, necessary to build `limine-uefi-cd.bin`).
Furthermore, `gcc` or `llvm/clang` must also be installed, alongside
the respective binutils.
diff --git a/bootstrap b/bootstrap
index 051425b1..591e6a0a 100755
--- a/bootstrap
+++ b/bootstrap
@@ -92,15 +92,6 @@ if ! test -f version; then
picoefi \
9c99f66e4c15aebfd72e7becb72358473b484fcf
- clone_repo_commit \
- https://github.com/jibsen/tinf.git \
- tinf \
- 57ffa1f1d5e3dde19011b2127bd26d01689b694b
- mkdir -p decompressor/tinf
- cp tinf/src/tinf.h tinf/src/tinflate.c tinf/src/tinfgzip.c tinf/src/crc32.c decompressor/tinf/
- patch -p0 < decompressor/tinf.patch
- rm -f decompressor/tinf/*.orig
-
clone_repo_commit \
https://github.com/Mintsuki/Flanterm.git \
flanterm \
diff --git a/common/common.mk b/common/common.mk
index 56f3f414..f44601ef 100644
--- a/common/common.mk
+++ b/common/common.mk
@@ -304,7 +304,7 @@ override HEADER_DEPS := $(addprefix $(call MKESCAPE,$(BUILDDIR))/, $(C_FILES:.c=
.PHONY: all
ifeq ($(TARGET),bios)
-all: $(call MKESCAPE,$(BUILDDIR))/limine-bios.sys $(call MKESCAPE,$(BUILDDIR))/stage2.bin.gz
+all: $(call MKESCAPE,$(BUILDDIR))/limine-bios.sys $(call MKESCAPE,$(BUILDDIR))/stage2.bin.limlz
endif
ifeq ($(TARGET),uefi-x86-64)
all: $(call MKESCAPE,$(BUILDDIR))/BOOTX64.EFI
@@ -324,8 +324,8 @@ endif
ifeq ($(TARGET),bios)
-$(call MKESCAPE,$(BUILDDIR))/stage2.bin.gz: $(call MKESCAPE,$(BUILDDIR))/stage2.bin
- gzip -n -9 < '$(call SHESCAPE,$<)' > '$(call SHESCAPE,$@)'
+$(call MKESCAPE,$(BUILDDIR))/stage2.bin.limlz: $(call MKESCAPE,$(BUILDDIR))/stage2.bin $(LIMLZPACK)
+ '$(call SHESCAPE,$(LIMLZPACK))' '$(call SHESCAPE,$<)' '$(call SHESCAPE,$@)'
$(call MKESCAPE,$(BUILDDIR))/stage2.bin: $(call MKESCAPE,$(BUILDDIR))/limine-bios.sys
dd if='$(call SHESCAPE,$<)' bs=$$(( 0x$$("$(READELF_FOR_TARGET)" -S '$(call SHESCAPE,$(BUILDDIR))/limine.elf' | $(GREP) '\.text\.stage3' | $(SED) 's/^.*] //' | $(AWK) '{print $$3}' | $(SED) 's/^0*//') - 0xf000 )) count=1 of='$(call SHESCAPE,$@)' 2>/dev/null
diff --git a/configure.ac b/configure.ac
index b2e5d3c4..0b70f5b7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3,6 +3,7 @@ AC_INIT([Limine], [m4_esyscmd([./version.sh])], [https://github.com/Limine-Bootl
AC_PREREQ([2.69])
AC_CONFIG_AUX_DIR([build-aux])
+AC_CONFIG_MACRO_DIRS([m4])
SRCDIR="$(cd "$srcdir" && pwd -P)"
BUILDDIR="$(pwd -P)"
@@ -17,6 +18,7 @@ AC_SUBST([SOURCE_DATE_EPOCH])
AC_SUBST([SOURCE_DATE_EPOCH_TOUCH])
AC_CANONICAL_HOST
+AC_CANONICAL_BUILD
# Portably convert relative paths into absolute paths.
rel2abs() {
@@ -52,6 +54,9 @@ AC_LANG([C])
AC_PROG_CC
CC="$(rel2abs "$CC")"
+AX_PROG_CC_FOR_BUILD
+CC_FOR_BUILD="$(rel2abs "$CC_FOR_BUILD")"
+
werror_state="no"
AC_ARG_ENABLE([werror],
[AS_HELP_STRING([--enable-werror], [treat warnings as errors])],
@@ -208,7 +213,6 @@ if test "x$BUILD_BIOS" = "xno"; then
else
BUILD_BIOS="limine-bios"
NEED_NASM=yes
- NEED_GZIP=yes
fi
AC_SUBST([BUILD_BIOS])
@@ -311,12 +315,6 @@ if test "x$NEED_NASM" = "xyes"; then
fi
fi
-if test "x$NEED_GZIP" = "xyes"; then
- AC_CHECK_PROG([GZIP_FOUND], [gzip], [yes])
- if ! test "x$GZIP_FOUND" = "xyes"; then
- AC_MSG_ERROR([gzip not found, please install gzip before configuring])
- fi
-fi
BORROWED_CFLAGS=""
for cflag in $CFLAGS; do
diff --git a/decompressor/decompressor.asm b/decompressor/decompressor.asm
new file mode 100644
index 00000000..1155689a
--- /dev/null
+++ b/decompressor/decompressor.asm
@@ -0,0 +1,139 @@
+; limlz: Copyright (C) 2026 Kamila Szewczyk <k@iczelia.net>
+; limine: Copyright (C) 2019-2026 Mintsuki and contributors.
+;
+; Redistribution and use in source and binary forms, with or without
+; modification, are permitted provided that the following conditions are met:
+;
+; 1. Redistributions of source code must retain the above copyright notice, this
+; list of conditions and the following disclaimer.
+;
+; 2. Redistributions in binary form must reproduce the above copyright notice,
+; this list of conditions and the following disclaimer in the documentation
+; and/or other materials provided with the distribution.
+;
+; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+bits 32
+
+section .entry progbits alloc exec nowrite align=16
+
+global _start
+_start:
+ cld
+ ; On stack (cdecl): [esp+4]=compressed_stage2, [esp+8]=stage2_size,
+ ; [esp+12]=boot_drive (byte), [esp+16]=pxe
+ mov ebx, dword [esp+0x4] ; compressed_stage2
+ mov ebp, dword [ebx] ; expected_crc = *(uint32_t *)compressed_stage2
+ lea edx, [ebx+0x4] ; ip = compressed_stage2 + 4
+ add ebx, dword [esp+0x8] ; ipe = compressed_stage2 + stage2_size
+ mov edi, 0xf000 ; op = dest
+ ; LZ decompression loop
+.Ltoken:
+ movzx ecx, byte [edx]
+ lea esi, [edx+0x1]
+ mov eax, ecx ; save token
+ shr ecx, 0x3
+ and ecx, 0xf ; literal length = (token >> 3) & 15
+ cmp ecx, 0xf
+ jne .Llitcopy
+ movzx ecx, byte [edx+0x1]
+ lea esi, [edx+0x2]
+ add ecx, 0xf ; length += extra byte + 15
+.Llitcopy:
+ rep movsb ; copy literals
+ cmp esi, ebx
+ jae .Lcrc ; if ip >= ipe, done
+ test al, al
+ jns .Loffset1 ; bit 7 clear => 1-byte offset
+ lea edx, [esi+0x2]
+ movzx esi, word [esi] ; 2-byte offset
+ jmp .Lmatchlen
+.Loffset1:
+ lea edx, [esi+0x1]
+ movzx esi, byte [esi] ; 1-byte offset
+.Lmatchlen:
+ and al, 0x7
+ cmp al, 0x7
+ je .Lmatchextra
+ movzx eax, al
+ jmp .Ldomatch
+.Lmatchextra:
+ movzx eax, byte [edx]
+ inc edx
+ add eax, 0x7 ; matchlen += extra byte + 7
+.Ldomatch:
+ mov ecx, edi
+ sub ecx, esi ; match = op - offset
+ mov esi, ecx
+ lea ecx, [eax+0x4] ; count = matchlen + 4
+ rep movsb ; copy match
+ jmp .Ltoken
+ ; CRC32 verification
+.Lcrc:
+ mov edx, 0xf000 ; ptr = dest
+ mov esi, edx ; (also reused for esp later)
+ xor eax, eax
+ dec eax
+.Lcrc_byte:
+ cmp edx, edi
+ je .Lcrc_done
+ lea ecx, [edx+0x1]
+ movzx edx, byte [edx]
+ xor eax, edx
+ push 0x08
+ pop edx ; 8 bits per byte
+.Lcrc_bit:
+ mov ebx, eax
+ and eax, 0x1
+ shr ebx, 1
+ neg eax
+ and eax, 0xedb88320
+ xor eax, ebx
+ dec edx
+ jne .Lcrc_bit
+ mov edx, ecx
+ jmp .Lcrc_byte
+.Lcrc_done:
+ not eax
+ cmp eax, ebp
+ jne .Lerror
+ ; Jump to decompressed stage2
+ movzx eax, byte [esp+0xc] ; boot_drive
+ mov ecx, dword [esp+0x10] ; pxe
+ mov esp, esi
+ xor ebp, ebp
+ push ecx
+ push eax
+ push ebp
+ push esi
+ ret ; jump to 0xf000
+ ; Error: display message and cli/hlt
+.Lerror:
+ mov edx, errmsg
+ mov eax, 0xb8000
+.Lerror_loop:
+ movzx ecx, byte [edx]
+ add eax, 0x2
+ inc edx
+ or ch, 0x4f
+ mov word [eax-0x2], cx
+ cmp eax, 0xB8000 + errmsg.len * 2
+ jne .Lerror_loop
+ cli
+ hlt
+
+section .rodata progbits alloc noexec nowrite align=1
+
+errmsg: db "limine integrity error"
+.len: equ $ - errmsg - 1
+
+section .note.GNU-stack noalloc noexec nowrite progbits
diff --git a/decompressor/decompressor.mk b/decompressor/decompressor.mk
index 718d07f3..1e6a3a43 100644
--- a/decompressor/decompressor.mk
+++ b/decompressor/decompressor.mk
@@ -39,7 +39,6 @@ override CFLAGS_FOR_TARGET += \
override CPPFLAGS_FOR_TARGET := \
-I . \
- -I tinf \
-isystem ../freestnd-c-hdrs/include \
$(CPPFLAGS_FOR_TARGET) \
-MMD \
diff --git a/decompressor/entry.asm b/decompressor/entry.asm
deleted file mode 100644
index 70aa0cfc..00000000
--- a/decompressor/entry.asm
+++ /dev/null
@@ -1,20 +0,0 @@
-extern bss_begin
-extern bss_end
-extern entry
-
-section .entry progbits alloc exec nowrite align=16
-
-global _start
-_start:
- cld
-
- ; Zero out .bss
- xor al, al
- mov edi, bss_begin
- mov ecx, bss_end
- sub ecx, bss_begin
- rep stosb
-
- jmp entry
-
-section .note.GNU-stack noalloc noexec nowrite progbits
diff --git a/decompressor/main.c b/decompressor/main.c
deleted file mode 100644
index 4bea9300..00000000
--- a/decompressor/main.c
+++ /dev/null
@@ -1,37 +0,0 @@
-#include <stdint.h>
-#include <stddef.h>
-#include <stdnoreturn.h>
-#include <tinf.h>
-
-noreturn void entry(uint8_t *compressed_stage2, size_t stage2_size, uint8_t boot_drive, int pxe) {
- // The decompressor should decompress compressed_stage2 to address 0xf000.
- // The output buffer extends up to 0x70000 where the decompressor itself lives.
- uint8_t *dest = (uint8_t *)0xf000;
- unsigned int destLen = 0x70000 - 0xf000;
-
- if (tinf_gzip_uncompress(dest, &destLen, compressed_stage2, stage2_size) != 0) {
- const char *msg = "Limine decomp error";
- volatile uint16_t *vga = (volatile uint16_t *)0xB8000;
- for (size_t i = 0; msg[i]; i++) {
- vga[i] = 0x4F00 | (uint8_t)msg[i];
- }
- for (;;) {
- asm volatile ("cli; hlt");
- }
- }
-
- asm volatile (
- "movl $0xf000, %%esp\n\t"
- "xorl %%ebp, %%ebp\n\t"
- "pushl %1\n\t"
- "pushl %0\n\t"
- "pushl $0\n\t"
- "pushl $0xf000\n\t"
- "ret\n\t"
- :
- : "r" ((uint32_t)boot_drive), "r" (pxe)
- : "memory"
- );
-
- __builtin_unreachable();
-}
diff --git a/decompressor/memory.c b/decompressor/memory.c
deleted file mode 100644
index a5353b2b..00000000
--- a/decompressor/memory.c
+++ /dev/null
@@ -1,53 +0,0 @@
-#include <stdint.h>
-#include <stddef.h>
-
-void *memcpy(void *restrict dest, const void *restrict src, size_t n) {
- uint8_t *restrict pdest = (uint8_t *restrict)dest;
- const uint8_t *restrict psrc = (const uint8_t *restrict)src;
-
- for (size_t i = 0; i < n; i++) {
- pdest[i] = psrc[i];
- }
-
- return dest;
-}
-
-void *memset(void *s, int c, size_t n) {
- uint8_t *p = (uint8_t *)s;
-
- for (size_t i = 0; i < n; i++) {
- p[i] = (uint8_t)c;
- }
-
- return s;
-}
-
-void *memmove(void *dest, const void *src, size_t n) {
- uint8_t *pdest = (uint8_t *)dest;
- const uint8_t *psrc = (const uint8_t *)src;
-
- if ((uintptr_t)src > (uintptr_t)dest) {
- for (size_t i = 0; i < n; i++) {
- pdest[i] = psrc[i];
- }
- } else if ((uintptr_t)src < (uintptr_t)dest) {
- for (size_t i = n; i > 0; i--) {
- pdest[i-1] = psrc[i-1];
- }
- }
-
- return dest;
-}
-
-int memcmp(const void *s1, const void *s2, size_t n) {
- const uint8_t *p1 = (const uint8_t *)s1;
- const uint8_t *p2 = (const uint8_t *)s2;
-
- for (size_t i = 0; i < n; i++) {
- if (p1[i] != p2[i]) {
- return p1[i] < p2[i] ? -1 : 1;
- }
- }
-
- return 0;
-}
diff --git a/decompressor/tinf.patch b/decompressor/tinf.patch
deleted file mode 100644
index d955918e..00000000
--- a/decompressor/tinf.patch
+++ /dev/null
@@ -1,54 +0,0 @@
---- tinf-clean/tinfgzip.c 2026-03-31 13:52:17.360241095 +0200
-+++ decompressor/tinf/tinfgzip.c 2026-03-31 14:00:21.885490126 +0200
-@@ -101,7 +101,7 @@
- /* Skip file name if present */
- if (flg & FNAME) {
- do {
-- if (start - src >= sourceLen) {
-+ if (((unsigned int)(start - src)) >= sourceLen) {
- return TINF_DATA_ERROR;
- }
- } while (*start++);
-@@ -110,7 +110,7 @@
- /* Skip file comment if present */
- if (flg & FCOMMENT) {
- do {
-- if (start - src >= sourceLen) {
-+ if (((unsigned int)(start - src)) >= sourceLen) {
- return TINF_DATA_ERROR;
- }
- } while (*start++);
-@@ -120,7 +120,7 @@
- if (flg & FHCRC) {
- unsigned int hcrc;
-
-- if (start - src > sourceLen - 2) {
-+ if ((unsigned int)(start - src) > sourceLen - 2) {
- return TINF_DATA_ERROR;
- }
-
---- tinf-clean/tinflate.c 2026-03-31 13:52:17.360241095 +0200
-+++ decompressor/tinf/tinflate.c 2026-03-31 14:03:04.603900859 +0200
-@@ -25,7 +25,7 @@
-
- #include "tinf.h"
-
--#include <assert.h>
-+#define assert(...)
- #include <limits.h>
-
- #if defined(UINT_MAX) && (UINT_MAX) < 0xFFFFFFFFUL
-@@ -501,11 +501,11 @@
-
- d->source += 4;
-
-- if (d->source_end - d->source < length) {
-+ if ((unsigned int)(d->source_end - d->source) < length) {
- return TINF_DATA_ERROR;
- }
-
-- if (d->dest_end - d->dest < length) {
-+ if ((unsigned int)(d->dest_end - d->dest) < length) {
- return TINF_BUF_ERROR;
- }
-
diff --git a/m4/ax_prog_cc_for_build.m4 b/m4/ax_prog_cc_for_build.m4
new file mode 100644
index 00000000..4d1de993
--- /dev/null
+++ b/m4/ax_prog_cc_for_build.m4
@@ -0,0 +1,175 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_prog_cc_for_build.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_PROG_CC_FOR_BUILD
+#
+# DESCRIPTION
+#
+# This macro searches for a C compiler that generates native executables,
+# that is a C compiler that surely is not a cross-compiler. This can be
+# useful if you have to generate source code at compile-time like for
+# example GCC does.
+#
+# The macro sets the CC_FOR_BUILD and CPP_FOR_BUILD macros to anything
+# needed to compile or link (CC_FOR_BUILD) and preprocess (CPP_FOR_BUILD).
+# The value of these variables can be overridden by the user by specifying
+# a compiler with an environment variable (like you do for standard CC).
+#
+# It also sets BUILD_EXEEXT and BUILD_OBJEXT to the executable and object
+# file extensions for the build platform, and GCC_FOR_BUILD to `yes' if
+# the compiler we found is GCC. All these variables but GCC_FOR_BUILD are
+# substituted in the Makefile.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Paolo Bonzini <bonzini@gnu.org>
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 26
+
+AU_ALIAS([AC_PROG_CC_FOR_BUILD], [AX_PROG_CC_FOR_BUILD])
+AC_DEFUN([AX_PROG_CC_FOR_BUILD], [dnl
+AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([AC_PROG_CPP])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+
+dnl Use the standard macros, but make them use other variable names
+dnl
+pushdef([ac_cv_prog_CPP], ac_cv_build_prog_CPP)dnl
+pushdef([ac_cv_prog_gcc], ac_cv_build_prog_gcc)dnl
+pushdef([ac_cv_prog_cc_c89], ac_cv_build_prog_cc_c89)dnl
+pushdef([ac_cv_prog_cc_c99], ac_cv_build_prog_cc_c99)dnl
+pushdef([ac_cv_prog_cc_c11], ac_cv_build_prog_cc_c11)dnl
+pushdef([ac_cv_prog_cc_c23], ac_cv_build_prog_cc_c23)dnl
+pushdef([ac_cv_prog_cc_stdc], ac_cv_build_prog_cc_stdc)dnl
+pushdef([ac_cv_prog_cc_works], ac_cv_build_prog_cc_works)dnl
+pushdef([ac_cv_prog_cc_cross], ac_cv_build_prog_cc_cross)dnl
+pushdef([ac_cv_prog_cc_g], ac_cv_build_prog_cc_g)dnl
+pushdef([ac_prog_cc_stdc], ac_build_prog_cc_stdc)dnl
+pushdef([ac_exeext], ac_build_exeext)dnl
+pushdef([ac_objext], ac_build_objext)dnl
+pushdef([CC], CC_FOR_BUILD)dnl
+pushdef([CPP], CPP_FOR_BUILD)dnl
+pushdef([GCC], GCC_FOR_BUILD)dnl
+pushdef([CFLAGS], CFLAGS_FOR_BUILD)dnl
+pushdef([CPPFLAGS], CPPFLAGS_FOR_BUILD)dnl
+pushdef([LDFLAGS], LDFLAGS_FOR_BUILD)dnl
+pushdef([host], build)dnl
+pushdef([host_alias], build_alias)dnl
+pushdef([host_cpu], build_cpu)dnl
+pushdef([host_vendor], build_vendor)dnl
+pushdef([host_os], build_os)dnl
+pushdef([ac_cv_host], ac_cv_build)dnl
+pushdef([ac_cv_host_alias], ac_cv_build_alias)dnl
+pushdef([ac_cv_host_cpu], ac_cv_build_cpu)dnl
+pushdef([ac_cv_host_vendor], ac_cv_build_vendor)dnl
+pushdef([ac_cv_host_os], ac_cv_build_os)dnl
+pushdef([ac_tool_prefix], ac_build_tool_prefix)dnl
+pushdef([am_cv_CC_dependencies_compiler_type], am_cv_build_CC_dependencies_compiler_type)dnl
+pushdef([am_cv_prog_cc_c_o], am_cv_build_prog_cc_c_o)dnl
+pushdef([cross_compiling], cross_compiling_build)dnl
+dnl
+dnl These variables are problematic to rename by M4 macros, so we save
+dnl their values in alternative names, and restore the values later.
+dnl
+dnl _AC_COMPILER_EXEEXT and _AC_COMPILER_OBJEXT internally call
+dnl AC_SUBST which prevents the renaming of EXEEXT and OBJEXT
+dnl variables. It's not a good idea to rename ac_cv_exeext and
+dnl ac_cv_objext either as they're related.
+dnl Renaming ac_exeext and ac_objext is safe though.
+dnl
+ac_cv_host_exeext=$ac_cv_exeext
+AS_VAR_SET_IF([ac_cv_build_exeext],
+ [ac_cv_exeext=$ac_cv_build_exeext],
+ [AS_UNSET([ac_cv_exeext])])
+ac_cv_host_objext=$ac_cv_objext
+AS_VAR_SET_IF([ac_cv_build_objext],
+ [ac_cv_objext=$ac_cv_build_objext],
+ [AS_UNSET([ac_cv_objext])])
+dnl
+dnl ac_cv_c_compiler_gnu is used in _AC_LANG_COMPILER_GNU (called by
+dnl AC_PROG_CC) indirectly.
+dnl
+ac_cv_host_c_compiler_gnu=$ac_cv_c_compiler_gnu
+AS_VAR_SET_IF([ac_cv_build_c_compiler_gnu],
+ [ac_cv_c_compiler_gnu=$ac_cv_build_c_compiler_gnu],
+ [AS_UNSET([ac_cv_c_compiler_gnu])])
+
+cross_compiling_build=no
+
+ac_build_tool_prefix=
+AS_IF([test -n "$build"], [ac_build_tool_prefix="$build-"],
+ [test -n "$build_alias"],[ac_build_tool_prefix="$build_alias-"])
+
+AC_LANG_PUSH([C])
+AC_PROG_CC
+_AC_COMPILER_EXEEXT
+_AC_COMPILER_OBJEXT
+AC_PROG_CPP
+
+BUILD_EXEEXT=$ac_cv_exeext
+BUILD_OBJEXT=$ac_cv_objext
+
+dnl Restore the old definitions
+dnl
+popdef([cross_compiling])dnl
+popdef([am_cv_prog_cc_c_o])dnl
+popdef([am_cv_CC_dependencies_compiler_type])dnl
+popdef([ac_tool_prefix])dnl
+popdef([ac_cv_host_os])dnl
+popdef([ac_cv_host_vendor])dnl
+popdef([ac_cv_host_cpu])dnl
+popdef([ac_cv_host_alias])dnl
+popdef([ac_cv_host])dnl
+popdef([host_os])dnl
+popdef([host_vendor])dnl
+popdef([host_cpu])dnl
+popdef([host_alias])dnl
+popdef([host])dnl
+popdef([LDFLAGS])dnl
+popdef([CPPFLAGS])dnl
+popdef([CFLAGS])dnl
+popdef([GCC])dnl
+popdef([CPP])dnl
+popdef([CC])dnl
+popdef([ac_objext])dnl
+popdef([ac_exeext])dnl
+popdef([ac_prog_cc_stdc])dnl
+popdef([ac_cv_prog_cc_g])dnl
+popdef([ac_cv_prog_cc_cross])dnl
+popdef([ac_cv_prog_cc_works])dnl
+popdef([ac_cv_prog_cc_stdc])dnl
+popdef([ac_cv_prog_cc_c23])dnl
+popdef([ac_cv_prog_cc_c11])dnl
+popdef([ac_cv_prog_cc_c99])dnl
+popdef([ac_cv_prog_cc_c89])dnl
+popdef([ac_cv_prog_gcc])dnl
+popdef([ac_cv_prog_CPP])dnl
+dnl
+ac_cv_exeext=$ac_cv_host_exeext
+EXEEXT=$ac_cv_host_exeext
+ac_cv_objext=$ac_cv_host_objext
+OBJEXT=$ac_cv_host_objext
+ac_cv_c_compiler_gnu=$ac_cv_host_c_compiler_gnu
+ac_compiler_gnu=$ac_cv_host_c_compiler_gnu
+
+dnl restore global variables ac_ext, ac_cpp, ac_compile,
+dnl ac_link, ac_compiler_gnu (dependent on the current
+dnl language after popping):
+AC_LANG_POP([C])
+
+dnl Finally, set Makefile variables
+dnl
+AC_SUBST([BUILD_EXEEXT])dnl
+AC_SUBST([BUILD_OBJEXT])dnl
+AC_SUBST([CFLAGS_FOR_BUILD])dnl
+AC_SUBST([CPPFLAGS_FOR_BUILD])dnl
+AC_SUBST([LDFLAGS_FOR_BUILD])dnl
+])
diff --git a/stage1/cd/bootsect.asm b/stage1/cd/bootsect.asm
index 6170f395..073acc6a 100644
--- a/stage1/cd/bootsect.asm
+++ b/stage1/cd/bootsect.asm
@@ -100,7 +100,7 @@ incbin DECOMPRESSOR_PATH
align 16
stage2:
-%strcat STAGE2_PATH BUILDDIR, '/common-bios/stage2.bin.gz'
+%strcat STAGE2_PATH BUILDDIR, '/common-bios/stage2.bin.limlz'
incbin STAGE2_PATH
.size: equ $ - stage2
diff --git a/stage1/hdd/bootsect.asm b/stage1/hdd/bootsect.asm
index 0d2b47e1..c6b64d28 100644
--- a/stage1/hdd/bootsect.asm
+++ b/stage1/hdd/bootsect.asm
@@ -148,6 +148,6 @@ incbin DECOMPRESSOR_PATH
align 16
stage2:
-%strcat STAGE2_PATH BUILDDIR, '/common-bios/stage2.bin.gz'
+%strcat STAGE2_PATH BUILDDIR, '/common-bios/stage2.bin.limlz'
incbin STAGE2_PATH
.size: equ $ - stage2
diff --git a/stage1/pxe/bootsect.asm b/stage1/pxe/bootsect.asm
index c1b96fb3..e65370fd 100644
--- a/stage1/pxe/bootsect.asm
+++ b/stage1/pxe/bootsect.asm
@@ -54,7 +54,7 @@ incbin DECOMPRESSOR_PATH
align 16
stage2:
-%strcat STAGE2_PATH BUILDDIR, '/common-bios/stage2.bin.gz'
+%strcat STAGE2_PATH BUILDDIR, '/common-bios/stage2.bin.limlz'
incbin STAGE2_PATH
.size: equ $ - stage2
.fullsize: equ $ - decompressor
diff --git a/tools/limlzpack.c b/tools/limlzpack.c
new file mode 100644
index 00000000..4fb0291c
--- /dev/null
+++ b/tools/limlzpack.c
@@ -0,0 +1,316 @@
+/* limlz: Copyright (C) 2026 Kamila Szewczyk <k@iczelia.net>
+ * limine: Copyright (C) 2019-2026 Mintsuki and contributors.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+typedef unsigned char byte;
+
+/* Higher -> better compression with exponentally dimnishing gains. */
+#define LIMLZ_SA_NEIGHBORS 32
+
+struct sa_cmp_ctx { int * rank; size_t n, k; };
+static struct sa_cmp_ctx g_sa_ctx;
+
+static int sa_cmp_idx(int i, int j) {
+ int ri, rj;
+ if (g_sa_ctx.rank[i] != g_sa_ctx.rank[j])
+ return g_sa_ctx.rank[i] - g_sa_ctx.rank[j];
+ ri = (i + (int)g_sa_ctx.k < (int)g_sa_ctx.n) ? g_sa_ctx.rank[i + g_sa_ctx.k] : -1;
+ rj = (j + (int)g_sa_ctx.k < (int)g_sa_ctx.n) ? g_sa_ctx.rank[j + g_sa_ctx.k] : -1;
+ return ri - rj;
+}
+
+static int sa_qsort_cmp(const void * a, const void * b) {
+ int i = *(const int *) a, j = *(const int *) b;
+ return sa_cmp_idx(i, j);
+}
+
+static int saca(const byte * s, size_t n, int * sa, int * rank, int * tmp) {
+ size_t i;
+ if (!n)
+ return 0;
+ for (i = 0; i < n; ++i) {
+ sa[i] = (int)i; rank[i] = (int)s[i];
+ }
+ for (g_sa_ctx.k = 1;; g_sa_ctx.k <<= 1) {
+ g_sa_ctx.rank = rank; g_sa_ctx.n = n;
+ qsort(sa, n, sizeof(sa[0]), sa_qsort_cmp);
+ tmp[sa[0]] = 0;
+ for (i = 1; i < n; ++i)
+ tmp[sa[i]] = tmp[sa[i - 1]] + (sa_cmp_idx(sa[i - 1], sa[i]) < 0);
+ for (i = 0; i < n; ++i)
+ rank[i] = tmp[i];
+ if ((size_t)rank[sa[n - 1]] == n - 1)
+ break;
+ }
+ return 0;
+}
+
+static size_t lcp_bytes(const byte * s, size_t n, size_t i, size_t j) {
+ size_t l = 0, m = n - (i > j ? i : j);
+ for (; l < m && s[i + l] == s[j + l]; ++l);
+ return l;
+}
+
+struct match_choice { uint32_t len; uint16_t off; };
+struct parse_choice { uint32_t lit, mlen; uint16_t off; };
+
+static int longest_matches(const byte * src, size_t n, struct match_choice * mch) {
+ int * sa, * rank, * tmp, * inv;
+ size_t i;
+ if (!n)
+ return 0;
+ sa = malloc(n * sizeof(*sa));
+ rank = malloc(n * sizeof(*rank));
+ tmp = malloc(n * sizeof(*tmp));
+ inv = malloc(n * sizeof(*inv));
+ if (!sa || !rank || !tmp || !inv || saca(src, n, sa, rank, tmp)) {
+ free(sa); free(rank); free(tmp); free(inv);
+ return -1;
+ }
+ for (i = 0; i < n; ++i)
+ inv[sa[i]] = (int)i;
+ for (i = 0; i < n; ++i) {
+ int r = inv[i], d, rr;
+ size_t best_len = 0;
+ uint16_t best_off = 0;
+ for (d = -LIMLZ_SA_NEIGHBORS; d <= LIMLZ_SA_NEIGHBORS; ++d) {
+ size_t j, l, off;
+ if (!d)
+ continue;
+ rr = r + d;
+ if (rr < 0 || rr >= (int)n)
+ continue;
+ j = (size_t)sa[rr];
+ if (j >= i)
+ continue;
+ off = i - j;
+ if (off == 0 || off > 65535)
+ continue;
+ l = lcp_bytes(src, n, i, j);
+ if (l > best_len) {
+ best_len = l;
+ best_off = (uint16_t)off;
+ }
+ }
+ if (best_len >= 4) {
+ mch[i].len = (uint32_t)best_len;
+ mch[i].off = best_off;
+ } else {
+ mch[i].len = mch[i].off = 0;
+ }
+ }
+ free(sa); free(rank); free(tmp); free(inv);
+ return 0;
+}
+
+static int encode_len_tail(byte ** outp, byte * out_end, size_t n) {
+ byte *out = * outp;
+ if (n >= 15) {
+ if (out >= out_end) return -1;
+ *out++ = (byte)(n - 15);
+ }
+ *outp = out;
+ return 0;
+}
+
+static int encode_len_tail_ml(byte ** outp, byte * out_end, size_t n) {
+ byte * out = *outp;
+ if (n >= 7) {
+ if (out >= out_end)
+ return -1;
+ *out++ = (byte)(n - 7);
+ }
+ *outp = out;
+ return 0;
+}
+
+static size_t limlzpack(void * dst, size_t dstcap, const void * srcv, size_t srcsz) {
+ const byte * src = (const byte *) srcv;
+ byte * dstp = (byte *) dst;
+ byte * out = dstp, * out_end = dstp + dstcap;
+ struct match_choice * mch, * bestm;
+ struct parse_choice * pick;
+ size_t i, * dp;
+ if (!srcsz) {
+ if (dstcap < 1)
+ return 0;
+ dstp[0] = 0;
+ return 1;
+ }
+ mch = calloc(srcsz, sizeof(*mch));
+ pick = calloc(srcsz + 1, sizeof(*pick));
+ bestm = calloc(srcsz, sizeof(*bestm));
+ dp = malloc((srcsz + 1) * sizeof(*dp));
+ if (!mch || !pick || !bestm || !dp || longest_matches(src, srcsz, mch))
+ goto fail;
+ dp[srcsz] = 0;
+ pick[srcsz].lit = pick[srcsz].mlen = pick[srcsz].off = 0;
+ for (i = srcsz; i-- > 0;) {
+ size_t j, best_cost;
+ uint32_t best_lit, best_len;
+ uint16_t best_off;
+ bestm[i].len = bestm[i].off = 0;
+ if (mch[i].len >= 4) {
+ size_t ml, lim = mch[i].len;
+ if (lim > 266) lim = 266;
+ size_t off_bytes = (mch[i].off > 255) ? 2 : 1;
+ size_t mcost = (size_t)-1;
+ uint32_t mlen = 0;
+ if (i + lim > srcsz)
+ lim = srcsz - i;
+ for (ml = 4; ml <= lim; ++ml) {
+ size_t c = off_bytes + (ml - 4 >= 7) + dp[i + ml];
+ if (c < mcost) {
+ mcost = c; mlen = (uint32_t)ml;
+ }
+ }
+ if (mlen) {
+ bestm[i].len = mlen; bestm[i].off = mch[i].off;
+ }
+ }
+ if (srcsz - i <= 270) { // 256 + 15 - 1
+ best_cost = 1 + (srcsz - i) + (srcsz - i >= 15);
+ best_lit = (uint32_t)(srcsz - i);
+ } else {
+ best_cost = (size_t)-1;
+ best_lit = 0;
+ }
+ best_len = best_off = 0;
+ for (j = i; j < srcsz && j - i <= 270; ++j) {
+ size_t lit = j - i, off_bytes_j, c;
+ if (!bestm[j].len)
+ continue;
+ off_bytes_j = (bestm[j].off > 255) ? 2 : 1;
+ c = 1 + lit + (lit >= 15) +
+ (off_bytes_j + (bestm[j].len - 4 >= 7) + dp[j + bestm[j].len]);
+ if (c < best_cost) {
+ best_cost = c; best_lit = (uint32_t)lit;
+ best_len = bestm[j].len; best_off = bestm[j].off;
+ }
+ }
+ dp[i] = best_cost; pick[i].lit = best_lit;
+ pick[i].mlen = best_len; pick[i].off = best_off;
+ }
+ for (i = 0; i < srcsz; ) {
+ byte * tokenp;
+ size_t lit = pick[i].lit, ml = pick[i].mlen;
+ uint16_t off = pick[i].off;
+ unsigned token_hi, token_lo;
+ if (i + lit > srcsz)
+ goto fail;
+ if (i + lit < srcsz && ml < 4)
+ goto fail;
+ tokenp = out;
+ if (out >= out_end)
+ goto fail;
+ *out++ = 0;
+ token_hi = (lit < 15) ? (unsigned)lit : 15u;
+ if (encode_len_tail(&out, out_end, lit))
+ goto fail;
+ if ((size_t)(out_end - out) < lit)
+ goto fail;
+ memcpy(out, src + i, lit);
+ out += lit;
+ i += lit;
+ if (i >= srcsz) {
+ *tokenp = (byte)(token_hi << 3);
+ break;
+ }
+ unsigned mode_bit = (off > 255) ? 1u : 0u;
+ token_lo = (ml - 4 < 7) ? (unsigned)(ml - 4) : 7u;
+ *tokenp = (byte)((mode_bit << 7) | (token_hi << 3) | token_lo);
+ if (off > 255) {
+ if (out_end - out < 2)
+ goto fail;
+ *out++ = (byte)(off & 255);
+ *out++ = (byte)(off >> 8);
+ } else {
+ if (out >= out_end)
+ goto fail;
+ *out++ = (byte)off;
+ }
+ if (encode_len_tail_ml(&out, out_end, ml - 4))
+ goto fail;
+ i += ml;
+ }
+ free(mch); free(pick); free(bestm); free(dp);
+ return (size_t)(out - dstp);
+fail:
+ free(mch); free(pick); free(bestm); free(dp);
+ return 0;
+}
+
+static const uint32_t tab[16] = {
+ 0x00000000u, 0x1DB71064u, 0x3B6E20C8u, 0x26D930ACu,
+ 0x76DC4190u, 0x6B6B51F4u, 0x4DB26158u, 0x5005713Cu,
+ 0xEDB88320u, 0xF00F9344u, 0xD6D6A3E8u, 0xCB61B38Cu,
+ 0x9B64C2B0u, 0x86D3D2D4u, 0xA00AE278u, 0xBDBDF21Cu
+};
+
+static uint32_t crc32_nibble(const byte *data, size_t len) {
+ uint32_t crc = ~0u; // faster than decompressor.asm bit-by-bit, same result.
+ while (len--) {
+ crc ^= *data++;
+ crc = (crc >> 4) ^ tab[crc & 0x0Fu];
+ crc = (crc >> 4) ^ tab[crc & 0x0Fu];
+ }
+ return ~crc;
+}
+
+int main(int argc, char *argv[]) {
+ if (argc != 3) {
+ fprintf(stderr, "? %s <input> <output>\n", argv[0]); return 1;
+ }
+ FILE * fin = fopen(argv[1], "rb");
+ FILE * fout = fopen(argv[2], "wb");
+ byte * inbuf, *outbuf;
+ size_t insz, outsz;
+ if (!fin || !fout) {
+ fprintf(stderr, "? fopen\n"); return 1;
+ }
+ fseek(fin, 0, SEEK_END);
+ insz = ftell(fin);
+ fseek(fin, 0, SEEK_SET);
+ inbuf = malloc(insz); outbuf = malloc(insz * 2);
+ if (!inbuf || !outbuf) {
+ fprintf(stderr, "? malloc\n"); return 1;
+ }
+ fread(inbuf, 1, insz, fin);
+ fclose(fin);
+ outsz = limlzpack(outbuf, insz * 2, inbuf, insz);
+ if (!outsz) {
+ fprintf(stderr, "? limlzpack\n"); return 1;
+ }
+ uint32_t crc = crc32_nibble(inbuf, insz);
+ fwrite(&crc, sizeof(crc), 1, fout);
+ fwrite(outbuf, 1, outsz, fout);
+ fclose(fout);
+ free(inbuf); free(outbuf);
+}
