--- /dev/null
+/*
+ * qr_restore.c - Paper backup restore program for qr-backup
+ *
+ * A standalone C program to restore qr-backup paper backups from PBM images.
+ * No dependencies other than libc (malloc, stdio, string).
+ *
+ * Licensed under CC0 (Public Domain)
+ *
+ * Restore phases:
+ * 1. PBM image reading
+ * 2. QR code detection and decoding
+ * 3. Chunk organization (parse labels, sort, dedupe)
+ * 4. Base64 decoding
+ * 5. Reed-Solomon erasure coding recovery
+ * 6. Data assembly (concat, remove length/padding)
+ * 7. AES decryption (GPG symmetric mode) [optional]
+ * 8. Gzip decompression [optional]
+ * 9. SHA256 checksum output
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <math.h>
+
+#ifdef UNIT_TEST_BUILD
+/* Forward declaration for unit tests - defined in unit_tests.c */
+int run_unit_tests(void);
+/* Make internal functions visible for testing */
+#define STATIC
+#else
+#define STATIC static
+#endif /* UNIT_TEST_BUILD */
+
+/* Simple 2D point structure */
+typedef struct {
+ float x, y;
+} Point2D;
+
+/* Homography matrix (3x3) for projective transform */
+typedef struct {
+ float h[3][3]; /* h[2][2] is typically normalized to 1 */
+} HomographyMatrix;
+
+/* Structure for centralized transform handling */
+typedef struct {
+ /* Original finder pattern positions */
+ float tl_x, tl_y; /* Top-left finder pattern */
+ float tr_x, tr_y; /* Top-right finder pattern */
+ float bl_x, bl_y; /* Bottom-left finder pattern */
+
+ /* QR code properties */
+ int version; /* QR code version (1-40) */
+ int size; /* Size in modules (17 + 4*version) */
+ float module_size; /* Average module size in pixels */
+
+ /* Transform parameters */
+ float scale_x; /* Horizontal scaling factor */
+ float scale_y; /* Vertical scaling factor */
+ float angle; /* Rotation angle in radians */
+
+ /* Corners of the QR code */
+ float qr_tl_x, qr_tl_y; /* Top-left corner */
+ float qr_tr_x, qr_tr_y; /* Top-right corner */
+ float qr_bl_x, qr_bl_y; /* Bottom-left corner */
+ float qr_br_x, qr_br_y; /* Bottom-right corner (derived) */
+
+ /* Homography matrix for more accurate mapping */
+ HomographyMatrix homography;
+ int use_homography; /* Flag to indicate if homography is available */
+} QRTransform;
+
+/* Simple square root for float - Newton's method with better initial guess */
+static float fsqrt(float x) {
+ if (x <= 0) return 0;
+ /* Use bit manipulation for a good initial guess (fast inverse square root inspired) */
+ union { float f; uint32_t i; } conv = { .f = x };
+ conv.i = 0x1fbd1df5 + (conv.i >> 1); /* Approximate sqrt via bit manipulation */
+ float guess = conv.f;
+ /* Newton-Raphson iterations for accuracy */
+ for (int i = 0; i < 4; i++) {
+ guess = (guess + x / guess) * 0.5f;
+ }
+ return guess;
+}
+
+/* ============================================================================
+ * CONFIGURATION
+ * ============================================================================ */
+
+#define MAX_QR_VERSION 40
+#define MAX_QR_MODULES (17 + 4 * MAX_QR_VERSION) /* 177 for version 40 */
+#define MAX_CHUNKS 1024
+#define MAX_CHUNK_SIZE 4096
+#define MAX_DATA_SIZE (MAX_CHUNKS * MAX_CHUNK_SIZE)
+
+/* ============================================================================
+ * DATA STRUCTURES
+ * ============================================================================ */
+
+/* Bitmap image */
+typedef struct {
+ int width;
+ int height;
+ uint8_t *data; /* 1 = black, 0 = white */
+} Image;
+
+/* Decoded QR code chunk */
+typedef struct {
+ char type; /* 'N' for normal, 'P' for parity */
+ int index; /* chunk number (1-based) */
+ int total; /* total chunks of this type */
+ uint8_t *data; /* decoded data */
+ int data_len;
+} Chunk;
+
+/* Collection of chunks */
+typedef struct {
+ Chunk *chunks;
+ int count;
+ int capacity;
+} ChunkList;
+
+/* ============================================================================
+ * PHASE 1: PBM/PGM IMAGE READER
+ * ============================================================================ */
+
+static int skip_whitespace_and_comments(FILE *f) {
+ int c;
+ while ((c = fgetc(f)) != EOF) {
+ if (c == '#') {
+ /* Skip comment line */
+ while ((c = fgetc(f)) != EOF && c != '\n');
+ } else if (c > ' ') {
+ ungetc(c, f);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* Otsu's method for automatic threshold selection - can be used as alternative to adaptive */
+static int otsu_threshold(uint8_t *gray, int size) __attribute__((unused));
+static int otsu_threshold(uint8_t *gray, int size) {
+ /* Build histogram */
+ int hist[256] = {0};
+ for (int i = 0; i < size; i++) {
+ hist[gray[i]]++;
+ }
+
+ /* Total pixels and sum */
+ int total = size;
+ int sum = 0;
+ for (int i = 0; i < 256; i++) {
+ sum += i * hist[i];
+ }
+
+ /* Find optimal threshold */
+ int sumB = 0;
+ int wB = 0;
+ float max_var = 0;
+ int threshold = 128;
+
+ for (int t = 0; t < 256; t++) {
+ wB += hist[t];
+ if (wB == 0) continue;
+
+ int wF = total - wB;
+ if (wF == 0) break;
+
+ sumB += t * hist[t];
+
+ float mB = (float)sumB / wB;
+ float mF = (float)(sum - sumB) / wF;
+
+ float var = (float)wB * (float)wF * (mB - mF) * (mB - mF);
+
+ if (var > max_var) {
+ max_var = var;
+ threshold = t;
+ }
+ }
+
+ return threshold;
+}
+
+/* Adaptive local thresholding for better results with uneven lighting */
+static void adaptive_threshold(uint8_t *gray, uint8_t *binary, int width, int height) {
+ /* Use block-based adaptive thresholding */
+ int block_size = 32; /* Size of local blocks */
+
+ for (int by = 0; by < height; by += block_size) {
+ for (int bx = 0; bx < width; bx += block_size) {
+ /* Calculate local threshold for this block */
+ int bw = (bx + block_size * 2 < width) ? block_size * 2 : width - bx;
+ int bh = (by + block_size * 2 < height) ? block_size * 2 : height - by;
+
+ /* Compute local mean */
+ int sum = 0, count = 0;
+ for (int y = by; y < by + bh && y < height; y++) {
+ for (int x = bx; x < bx + bw && x < width; x++) {
+ sum += gray[y * width + x];
+ count++;
+ }
+ }
+ int local_mean = count > 0 ? sum / count : 128;
+
+ /* Threshold slightly below mean for better black detection */
+ int threshold = local_mean - 10;
+ if (threshold < 20) threshold = 20;
+
+ /* Apply threshold to central block */
+ int ew = (bx + block_size < width) ? block_size : width - bx;
+ int eh = (by + block_size < height) ? block_size : height - by;
+ for (int y = by; y < by + eh; y++) {
+ for (int x = bx; x < bx + ew; x++) {
+ /* 1 = black (dark), 0 = white (light) */
+ binary[y * width + x] = (gray[y * width + x] < threshold) ? 1 : 0;
+ }
+ }
+ }
+ }
+}
+
+static Image *image_read_pbm(const char *filename) {
+ FILE *f = fopen(filename, "rb");
+ if (!f) return NULL;
+
+ Image *img = malloc(sizeof(Image));
+ if (!img) { fclose(f); return NULL; }
+
+ /* Read magic number */
+ char magic[3];
+ if (fread(magic, 1, 2, f) != 2) { free(img); fclose(f); return NULL; }
+ magic[2] = 0;
+
+ int format = 0; /* 1=P1, 2=P2, 4=P4, 5=P5 */
+ if (strcmp(magic, "P1") == 0) {
+ format = 1; /* ASCII PBM */
+ } else if (strcmp(magic, "P4") == 0) {
+ format = 4; /* Binary PBM */
+ } else if (strcmp(magic, "P2") == 0) {
+ format = 2; /* ASCII PGM (greyscale) */
+ } else if (strcmp(magic, "P5") == 0) {
+ format = 5; /* Binary PGM (greyscale) */
+ } else {
+ free(img); fclose(f); return NULL;
+ }
+
+ /* Read dimensions */
+ if (!skip_whitespace_and_comments(f)) { free(img); fclose(f); return NULL; }
+ if (fscanf(f, "%d", &img->width) != 1) { free(img); fclose(f); return NULL; }
+ if (!skip_whitespace_and_comments(f)) { free(img); fclose(f); return NULL; }
+ if (fscanf(f, "%d", &img->height) != 1) { free(img); fclose(f); return NULL; }
+
+ /* PGM formats have maxval */
+ int maxval = 1;
+ if (format == 2 || format == 5) {
+ if (!skip_whitespace_and_comments(f)) { free(img); fclose(f); return NULL; }
+ if (fscanf(f, "%d", &maxval) != 1) { free(img); fclose(f); return NULL; }
+ if (maxval <= 0) maxval = 255;
+ }
+
+ /* Skip single whitespace after header */
+ fgetc(f);
+
+ /* Allocate pixel data */
+ int npixels = img->width * img->height;
+ img->data = malloc(npixels);
+ if (!img->data) { free(img); fclose(f); return NULL; }
+
+ if (format == 4) {
+ /* Binary PBM: 8 pixels per byte, MSB first */
+ int row_bytes = (img->width + 7) / 8;
+ uint8_t *row = malloc(row_bytes);
+ if (!row) { free(img->data); free(img); fclose(f); return NULL; }
+
+ for (int y = 0; y < img->height; y++) {
+ if (fread(row, 1, row_bytes, f) != (size_t)row_bytes) {
+ free(row); free(img->data); free(img); fclose(f); return NULL;
+ }
+ for (int x = 0; x < img->width; x++) {
+ int byte_idx = x / 8;
+ int bit_idx = 7 - (x % 8);
+ img->data[y * img->width + x] = (row[byte_idx] >> bit_idx) & 1;
+ }
+ }
+ free(row);
+ } else if (format == 1) {
+ /* ASCII PBM: one character per pixel */
+ for (int i = 0; i < npixels; i++) {
+ int c;
+ while ((c = fgetc(f)) != EOF && (c == ' ' || c == '\t' || c == '\n' || c == '\r'));
+ if (c == EOF) { free(img->data); free(img); fclose(f); return NULL; }
+ img->data[i] = (c == '1') ? 1 : 0;
+ }
+ } else if (format == 5) {
+ /* Binary PGM: one byte per pixel (greyscale) */
+ uint8_t *gray = malloc(npixels);
+ if (!gray) { free(img->data); free(img); fclose(f); return NULL; }
+
+ if (fread(gray, 1, npixels, f) != (size_t)npixels) {
+ free(gray); free(img->data); free(img); fclose(f); return NULL;
+ }
+
+ /* Scale to 0-255 if maxval != 255 */
+ if (maxval != 255) {
+ for (int i = 0; i < npixels; i++) {
+ gray[i] = (uint8_t)((gray[i] * 255) / maxval);
+ }
+ }
+
+ /* Apply adaptive thresholding */
+ adaptive_threshold(gray, img->data, img->width, img->height);
+ free(gray);
+ } else if (format == 2) {
+ /* ASCII PGM: one number per pixel (greyscale) */
+ uint8_t *gray = malloc(npixels);
+ if (!gray) { free(img->data); free(img); fclose(f); return NULL; }
+
+ for (int i = 0; i < npixels; i++) {
+ int val;
+ if (fscanf(f, "%d", &val) != 1) {
+ free(gray); free(img->data); free(img); fclose(f); return NULL;
+ }
+ gray[i] = (uint8_t)((val * 255) / maxval);
+ }
+
+ /* Apply adaptive thresholding */
+ adaptive_threshold(gray, img->data, img->width, img->height);
+ free(gray);
+ }
+
+ fclose(f);
+ return img;
+}
+
+static void image_free(Image *img) {
+ if (img) {
+ free(img->data);
+ free(img);
+ }
+}
+
+STATIC int image_get(Image *img, int x, int y) {
+ if (x < 0 || x >= img->width || y < 0 || y >= img->height) return 0;
+ return img->data[y * img->width + x];
+}
+
+/* ============================================================================
+ * PHASE 2: QR CODE DETECTION AND DECODING
+ * ============================================================================ */
+
+/* QR Code finder pattern detection */
+typedef struct {
+ int x, y; /* Center position */
+ float module_size; /* Estimated module size (geometric mean of x/y) */
+ float module_size_x; /* Horizontal module size (for non-uniform scaling) */
+ float module_size_y; /* Vertical module size (for non-uniform scaling) */
+} FinderPattern;
+
+/* Finder line - a scan line segment that matches 1:1:3:1:1 pattern */
+typedef struct {
+ int pos; /* Position of scan line (y for horizontal, x for vertical) */
+ int bound_min; /* Start of pattern on scan line */
+ int bound_max; /* End of pattern on scan line */
+} FinderLine;
+
+/* Finder range - cluster of consecutive parallel finder lines (same finder pattern) */
+typedef struct {
+ int pos_min, pos_max; /* Range of scan positions (y range for horiz, x range for vert) */
+ int center_min, center_max; /* Min/max of centers (for estimating finder extent) */
+ int center_sum; /* Sum of centers for averaging */
+ int count; /* Number of lines in this range */
+ float module_sum; /* Sum of module sizes for averaging */
+} FinderRange;
+
+/* Finder line/range collections */
+#define MAX_FINDER_LINES 16000
+#define INITIAL_FINDER_RANGES 128
+
+/* Debug flag - set by -v -v */
+static int debug_finder = 0; // DEBUG
+
+/* Debug mode - write intermediate files to disk */
+static int debug_mode = 0; // DEBUG
+static int debug_qr_counter = 0; /* Counter for debug file naming (BASIC style: 10, 20, ...) */ // DEBUG
+ // DEBUG
+/* Debug file output helpers */ // DEBUG
+static void debug_dump_finders(FinderPattern *patterns, int count) { // DEBUG
+ if (!debug_mode) return; // DEBUG
+ char filename[64]; // DEBUG
+ snprintf(filename, sizeof(filename), "debug_%02d_finders.txt", debug_qr_counter); // DEBUG
+ FILE *f = fopen(filename, "w"); // DEBUG
+ if (!f) return; // DEBUG
+ // DEBUG
+ fprintf(f, "# Finder patterns found: %d\n", count); // DEBUG
+ fprintf(f, "# Format: index x y module_size\n"); // DEBUG
+ for (int i = 0; i < count; i++) { // DEBUG
+ fprintf(f, "%3d %4d %4d %.2f\n", // DEBUG
+ i, patterns[i].x, patterns[i].y, patterns[i].module_size); // DEBUG
+ } // DEBUG
+ fclose(f); // DEBUG
+ fprintf(stderr, " Debug: wrote %s\n", filename); // DEBUG
+} // DEBUG
+
+static void debug_dump_grid(uint8_t grid[MAX_QR_MODULES][MAX_QR_MODULES], int size, // DEBUG
+ int version, const char *stage) { // DEBUG
+ if (!debug_mode) return; // DEBUG
+ char filename[64]; // DEBUG
+ snprintf(filename, sizeof(filename), "debug_%02d_%s.txt", debug_qr_counter, stage); // DEBUG
+ FILE *f = fopen(filename, "w"); // DEBUG
+ if (!f) return; // DEBUG
+ // DEBUG
+ fprintf(f, "# QR grid %s: version=%d size=%d\n", stage, version, size); // DEBUG
+ fprintf(f, "# Legend: # = black (1), . = white (0)\n\n"); // DEBUG
+ // DEBUG
+ /* Header with column numbers */ // DEBUG
+ fprintf(f, " "); // DEBUG
+ for (int x = 0; x < size; x++) { // DEBUG
+ if (x % 10 == 0) fprintf(f, "%d", (x / 10) % 10); // DEBUG
+ else fprintf(f, " "); // DEBUG
+ } // DEBUG
+ fprintf(f, "\n "); // DEBUG
+ for (int x = 0; x < size; x++) { // DEBUG
+ fprintf(f, "%d", x % 10); // DEBUG
+ } // DEBUG
+ fprintf(f, "\n\n"); // DEBUG
+ // DEBUG
+ for (int y = 0; y < size; y++) { // DEBUG
+ fprintf(f, "%3d ", y); // DEBUG
+ for (int x = 0; x < size; x++) { // DEBUG
+ fprintf(f, "%c", grid[y][x] ? '#' : '.'); // DEBUG
+ } // DEBUG
+ fprintf(f, "\n"); // DEBUG
+ } // DEBUG
+ fclose(f); // DEBUG
+ fprintf(stderr, " Debug: wrote %s\n", filename); // DEBUG
+} // DEBUG
+
+static void debug_dump_bits(uint8_t *data, int len, int version, int ecc_level) { // DEBUG
+ if (!debug_mode) return; // DEBUG
+ char filename[64]; // DEBUG
+ snprintf(filename, sizeof(filename), "debug_%02d_bits.txt", debug_qr_counter); // DEBUG
+ FILE *f = fopen(filename, "w"); // DEBUG
+ if (!f) return; // DEBUG
+ // DEBUG
+ fprintf(f, "# Raw bits extracted: %d bytes, version=%d ecc=%d\n", len, version, ecc_level); // DEBUG
+ fprintf(f, "# Format: offset hex binary\n\n"); // DEBUG
+ // DEBUG
+ for (int i = 0; i < len; i++) { // DEBUG
+ fprintf(f, "%4d %02x ", i, data[i]); // DEBUG
+ for (int b = 7; b >= 0; b--) { // DEBUG
+ fprintf(f, "%c", (data[i] >> b) & 1 ? '1' : '0'); // DEBUG
+ } // DEBUG
+ if (i < 16) { // DEBUG
+ /* Annotate first bytes: mode indicator, char count, etc. */ // DEBUG
+ if (i == 0) fprintf(f, " # mode + start of count"); // DEBUG
+ } // DEBUG
+ fprintf(f, "\n"); // DEBUG
+ } // DEBUG
+ fclose(f); // DEBUG
+ fprintf(stderr, " Debug: wrote %s\n", filename); // DEBUG
+} // DEBUG
+
+static void debug_dump_codewords(uint8_t *raw, int raw_len, uint8_t *deint, int deint_len, // DEBUG
+ int version, int ecc_level) { // DEBUG
+ if (!debug_mode) return; // DEBUG
+ char filename[64]; // DEBUG
+ snprintf(filename, sizeof(filename), "debug_%02d_codewords.txt", debug_qr_counter); // DEBUG
+ FILE *f = fopen(filename, "w"); // DEBUG
+ if (!f) return; // DEBUG
+ // DEBUG
+ fprintf(f, "# Codewords: version=%d ecc=%d\n", version, ecc_level); // DEBUG
+ fprintf(f, "# Raw (interleaved): %d bytes\n", raw_len); // DEBUG
+ fprintf(f, "# Deinterleaved data: %d bytes\n\n", deint_len); // DEBUG
+ // DEBUG
+ fprintf(f, "## Raw codewords (as extracted from zigzag):\n"); // DEBUG
+ for (int i = 0; i < raw_len; i++) { // DEBUG
+ if (i % 16 == 0) fprintf(f, "%04d: ", i); // DEBUG
+ fprintf(f, "%02x ", raw[i]); // DEBUG
+ if (i % 16 == 15) fprintf(f, "\n"); // DEBUG
+ } // DEBUG
+ if (raw_len % 16 != 0) fprintf(f, "\n"); // DEBUG
+ // DEBUG
+ fprintf(f, "\n## Deinterleaved data codewords:\n"); // DEBUG
+ for (int i = 0; i < deint_len; i++) { // DEBUG
+ if (i % 16 == 0) fprintf(f, "%04d: ", i); // DEBUG
+ fprintf(f, "%02x ", deint[i]); // DEBUG
+ if (i % 16 == 15) fprintf(f, "\n"); // DEBUG
+ } // DEBUG
+ if (deint_len % 16 != 0) fprintf(f, "\n"); // DEBUG
+ // DEBUG
+ fclose(f); // DEBUG
+ fprintf(stderr, " Debug: wrote %s\n", filename); // DEBUG
+} // DEBUG
+
+static void debug_dump_decoded(const char *content, int len, char type, int index, int total) { // DEBUG
+ if (!debug_mode) return; // DEBUG
+ char filename[64]; // DEBUG
+ snprintf(filename, sizeof(filename), "debug_%02d_decoded.txt", debug_qr_counter); // DEBUG
+ FILE *f = fopen(filename, "w"); // DEBUG
+ if (!f) return; // DEBUG
+ // DEBUG
+ fprintf(f, "# Decoded QR content: %d characters\n", len); // DEBUG
+ fprintf(f, "# Chunk: %c%d/%d\n\n", type, index, total); // DEBUG
+ fprintf(f, "%s\n", content); // DEBUG
+ // DEBUG
+ fclose(f); // DEBUG
+ fprintf(stderr, " Debug: wrote %s\n", filename); // DEBUG
+} // DEBUG
+
+/* Debug dump for chunk list (after collection/dedup) */
+static void debug_dump_chunks(ChunkList *cl, const char *stage) { // DEBUG
+ if (!debug_mode) return; // DEBUG
+ char filename[64]; // DEBUG
+ snprintf(filename, sizeof(filename), "debug_60_chunks_%s.txt", stage); // DEBUG
+ FILE *f = fopen(filename, "w"); // DEBUG
+ if (!f) return; // DEBUG
+ // DEBUG
+ fprintf(f, "# Chunks %s: %d total\n\n", stage, cl->count); // DEBUG
+ fprintf(f, "# Type Index Total Size First 16 bytes (hex)\n"); // DEBUG
+ fprintf(f, "# ---- ----- ----- ---- --------------------\n"); // DEBUG
+ // DEBUG
+ for (int i = 0; i < cl->count; i++) { // DEBUG
+ Chunk *c = &cl->chunks[i]; // DEBUG
+ fprintf(f, " %c %3d %3d %4d ", // DEBUG
+ c->type, c->index, c->total, c->data_len); // DEBUG
+ for (int j = 0; j < 16 && j < c->data_len; j++) { // DEBUG
+ fprintf(f, "%02x ", c->data[j]); // DEBUG
+ } // DEBUG
+ if (c->data_len > 16) fprintf(f, "..."); // DEBUG
+ fprintf(f, "\n"); // DEBUG
+ } // DEBUG
+ // DEBUG
+ fclose(f); // DEBUG
+ fprintf(stderr, "Debug: wrote %s\n", filename); // DEBUG
+} // DEBUG
+
+/* Debug dump for erasure recovery - extended version with recovered data */
+static void debug_dump_erasure_ex(int total_n, int total_p, int have_n, int have_p, // DEBUG
+ int *missing_indices, int missing_count, int recovered, // DEBUG
+ uint8_t **recovered_data, int *recovered_lens, // DEBUG
+ const char *method) { // DEBUG
+ if (!debug_mode) return; // DEBUG
+ FILE *f = fopen("debug_70_erasure.txt", "w"); // DEBUG
+ if (!f) return; // DEBUG
+ // DEBUG
+ fprintf(f, "# Erasure Recovery Status\n\n"); // DEBUG
+ fprintf(f, "Total N chunks expected: %d\n", total_n); // DEBUG
+ fprintf(f, "Total P chunks expected: %d\n", total_p); // DEBUG
+ fprintf(f, "N chunks available: %d\n", have_n); // DEBUG
+ fprintf(f, "P chunks available: %d\n", have_p); // DEBUG
+ fprintf(f, "Missing N chunks: %d\n", missing_count); // DEBUG
+ // DEBUG
+ if (missing_count > 0) { // DEBUG
+ fprintf(f, "Missing indices: "); // DEBUG
+ for (int i = 0; i < missing_count; i++) { // DEBUG
+ fprintf(f, "%s%d", i > 0 ? ", " : "", missing_indices[i]); // DEBUG
+ } // DEBUG
+ fprintf(f, "\n"); // DEBUG
+ } // DEBUG
+ // DEBUG
+ fprintf(f, "\nRecovery: %s\n", recovered ? "SUCCEEDED" : "FAILED"); // DEBUG
+ if (method) { // DEBUG
+ fprintf(f, "Method: %s\n", method); // DEBUG
+ } // DEBUG
+ // DEBUG
+ /* Dump recovered chunk data */ // DEBUG
+ if (recovered && recovered_data && missing_count > 0) { // DEBUG
+ fprintf(f, "\n## Recovered Chunk Data:\n\n"); // DEBUG
+ for (int i = 0; i < missing_count; i++) { // DEBUG
+ if (recovered_data[i] && recovered_lens[i] > 0) { // DEBUG
+ fprintf(f, "### N%d (recovered, %d bytes):\n", missing_indices[i], recovered_lens[i]); // DEBUG
+ for (int j = 0; j < recovered_lens[i]; j++) { // DEBUG
+ if (j % 16 == 0) fprintf(f, "%04x: ", j); // DEBUG
+ fprintf(f, "%02x ", recovered_data[i][j]); // DEBUG
+ if (j % 16 == 15) { // DEBUG
+ fprintf(f, " |"); // DEBUG
+ for (int k = j - 15; k <= j; k++) { // DEBUG
+ char c = recovered_data[i][k]; // DEBUG
+ fprintf(f, "%c", (c >= 32 && c < 127) ? c : '.'); // DEBUG
+ } // DEBUG
+ fprintf(f, "|\n"); // DEBUG
+ } // DEBUG
+ } // DEBUG
+ if (recovered_lens[i] % 16 != 0) fprintf(f, "\n"); // DEBUG
+ fprintf(f, "\n"); // DEBUG
+ } // DEBUG
+ } // DEBUG
+ } // DEBUG
+ // DEBUG
+ fclose(f); // DEBUG
+ fprintf(stderr, "Debug: wrote debug_70_erasure.txt\n"); // DEBUG
+} // DEBUG
+
+/* Simple version for initial state / failure cases */
+static void debug_dump_erasure(int total_n, int total_p, int have_n, int have_p, // DEBUG
+ int *missing_indices, int missing_count, int recovered) { // DEBUG
+ debug_dump_erasure_ex(total_n, total_p, have_n, have_p, missing_indices, // DEBUG
+ missing_count, recovered, NULL, NULL, NULL); // DEBUG
+} // DEBUG
+
+/* Debug dump for assembled binary data */
+static void debug_dump_assembled(uint8_t *data, int len) { // DEBUG
+ if (!debug_mode) return; // DEBUG
+ // DEBUG
+ /* Write binary dump */ // DEBUG
+ FILE *f = fopen("debug_80_assembled.bin", "wb"); // DEBUG
+ if (f) { // DEBUG
+ fwrite(data, 1, len, f); // DEBUG
+ fclose(f); // DEBUG
+ fprintf(stderr, "Debug: wrote debug_80_assembled.bin (%d bytes)\n", len); // DEBUG
+ } // DEBUG
+ // DEBUG
+ /* Write hex dump for inspection */ // DEBUG
+ f = fopen("debug_80_assembled.txt", "w"); // DEBUG
+ if (f) { // DEBUG
+ fprintf(f, "# Assembled data: %d bytes\n", len); // DEBUG
+ fprintf(f, "# First 2 bytes: %02x %02x (%s)\n\n", // DEBUG
+ len >= 2 ? data[0] : 0, len >= 2 ? data[1] : 0, // DEBUG
+ (len >= 2 && data[0] == 0x1f && data[1] == 0x8b) ? "gzip" : "raw"); // DEBUG
+ // DEBUG
+ for (int i = 0; i < len; i++) { // DEBUG
+ if (i % 16 == 0) fprintf(f, "%04x: ", i); // DEBUG
+ fprintf(f, "%02x ", data[i]); // DEBUG
+ if (i % 16 == 15) { // DEBUG
+ fprintf(f, " |"); // DEBUG
+ for (int j = i - 15; j <= i; j++) { // DEBUG
+ char c = data[j]; // DEBUG
+ fprintf(f, "%c", (c >= 32 && c < 127) ? c : '.'); // DEBUG
+ } // DEBUG
+ fprintf(f, "|\n"); // DEBUG
+ } // DEBUG
+ } // DEBUG
+ if (len % 16 != 0) { // DEBUG
+ int pad = 16 - (len % 16); // DEBUG
+ for (int i = 0; i < pad; i++) fprintf(f, " "); // DEBUG
+ fprintf(f, " |"); // DEBUG
+ int start = len - (len % 16); // DEBUG
+ for (int j = start; j < len; j++) { // DEBUG
+ char c = data[j]; // DEBUG
+ fprintf(f, "%c", (c >= 32 && c < 127) ? c : '.'); // DEBUG
+ } // DEBUG
+ fprintf(f, "|\n"); // DEBUG
+ } // DEBUG
+ // DEBUG
+ fclose(f); // DEBUG
+ fprintf(stderr, "Debug: wrote debug_80_assembled.txt\n"); // DEBUG
+ } // DEBUG
+} // DEBUG
+
+/* Debug dump for decompressed data */
+static void debug_dump_decompressed(uint8_t *data, int len) { // DEBUG
+ if (!debug_mode) return; // DEBUG
+ // DEBUG
+ /* Write binary dump */ // DEBUG
+ FILE *f = fopen("debug_90_decompressed.bin", "wb"); // DEBUG
+ if (f) { // DEBUG
+ fwrite(data, 1, len, f); // DEBUG
+ fclose(f); // DEBUG
+ fprintf(stderr, "Debug: wrote debug_90_decompressed.bin (%d bytes)\n", len); // DEBUG
+ } // DEBUG
+ // DEBUG
+ /* Write text preview */ // DEBUG
+ f = fopen("debug_90_decompressed.txt", "w"); // DEBUG
+ if (f) { // DEBUG
+ fprintf(f, "# Decompressed data: %d bytes\n\n", len); // DEBUG
+ // DEBUG
+ /* Check if it's mostly text */ // DEBUG
+ int printable = 0; // DEBUG
+ for (int i = 0; i < len && i < 1000; i++) { // DEBUG
+ if ((data[i] >= 32 && data[i] < 127) || data[i] == '\n' || data[i] == '\r' || data[i] == '\t') { // DEBUG
+ printable++; // DEBUG
+ } // DEBUG
+ } // DEBUG
+ // DEBUG
+ if (printable > len * 0.8 || printable > 800) { // DEBUG
+ fprintf(f, "## Content (text):\n"); // DEBUG
+ fwrite(data, 1, len, f); // DEBUG
+ } else { // DEBUG
+ fprintf(f, "## Content (hex, first 256 bytes):\n"); // DEBUG
+ for (int i = 0; i < len && i < 256; i++) { // DEBUG
+ if (i % 16 == 0) fprintf(f, "%04x: ", i); // DEBUG
+ fprintf(f, "%02x ", data[i]); // DEBUG
+ if (i % 16 == 15) fprintf(f, "\n"); // DEBUG
+ } // DEBUG
+ if (len > 256) fprintf(f, "\n... (%d more bytes)\n", len - 256); // DEBUG
+ } // DEBUG
+ // DEBUG
+ fclose(f); // DEBUG
+ fprintf(stderr, "Debug: wrote debug_90_decompressed.txt\n"); // DEBUG
+ } // DEBUG
+} // DEBUG
+
+/* Check if a run of pixels matches the 1:1:3:1:1 finder pattern ratio
+ * tolerance: 0.0 to 1.0 (e.g., 0.5 = 50% tolerance)
+ * Returns module size estimate or 0 if not a match */
+STATIC float check_finder_ratio_ex(int *counts, float tolerance) {
+ int total = 0;
+ for (int i = 0; i < 5; i++) {
+ if (counts[i] == 0) return 0;
+ total += counts[i];
+ }
+
+ /* Need minimum size to be meaningful */
+ if (total < 7) return 0;
+
+ float module = total / 7.0f;
+
+ /* Use adaptive tolerance based on the pattern size
+ * For larger finder patterns, we can be more lenient with distortion
+ * For smaller patterns, we need to be more strict to avoid false positives */
+ float adaptive_tolerance = tolerance;
+ if (module > 3.0f) {
+ /* Increase tolerance for larger patterns, up to 1.5 times the base tolerance */
+ adaptive_tolerance = tolerance * (1.0f + (module - 3.0f) * 0.1f);
+ if (adaptive_tolerance > tolerance * 1.5f) {
+ adaptive_tolerance = tolerance * 1.5f;
+ }
+ }
+
+ float variance = module * adaptive_tolerance;
+
+ /* Use a more flexible check for the center (3x) portion */
+ float center_min = 2.0f * module; /* Allow as low as 2x instead of 3x for severe distortion */
+ float center_max = 4.0f * module; /* Allow up to 4x for severe distortion */
+
+ int ok = (counts[0] >= module - variance && counts[0] <= module + variance * 1.2f) &&
+ (counts[1] >= module - variance && counts[1] <= module + variance * 1.2f) &&
+ (counts[2] >= center_min && counts[2] <= center_max) &&
+ (counts[3] >= module - variance && counts[3] <= module + variance * 1.2f) &&
+ (counts[4] >= module - variance && counts[4] <= module + variance * 1.2f);
+
+ /* Secondary check for aspect ratio - the 1:1:3:1:1 finder pattern should maintain
+ * a specific relationship even when distorted. The side segments (1+1+1+1=4)
+ * should be about 4/7 of the total, and the center segment should be about 3/7. */
+ float side_ratio = (float)(counts[0] + counts[1] + counts[3] + counts[4]) / total;
+ float center_ratio = (float)counts[2] / total;
+
+ int ratio_ok = (side_ratio >= 0.4f && side_ratio <= 0.7f) &&
+ (center_ratio >= 0.25f && center_ratio <= 0.55f);
+
+ /* Both the individual segment test and ratio test must pass */
+ int final_ok = ok && ratio_ok;
+
+ if (debug_finder >= 2) { // DEBUG
+ /* Only show patterns that are close to valid */
+ float c2_expected = 3 * module;
+ int roughly_valid = (counts[2] > c2_expected * 0.5 && counts[2] < c2_expected * 1.5);
+ if (roughly_valid || ok) {
+ fprintf(stderr, " ratio: [%d,%d,%d,%d,%d] total=%d module=%.1f side_ratio=%.2f center_ratio=%.2f tol=%.2f -> %s\n",
+ counts[0], counts[1], counts[2], counts[3], counts[4],
+ total, module, side_ratio, center_ratio, adaptive_tolerance,
+ final_ok ? "YES" : "no");
+ }
+ }
+
+ return final_ok ? module : 0;
+}
+
+/* Legacy wrapper with 50% tolerance - kept for potential future use */
+static int check_finder_ratio(int *counts) __attribute__((unused));
+static int check_finder_ratio(int *counts) {
+ return check_finder_ratio_ex(counts, 0.5f) > 0;
+}
+
+/* Perform vertical cross-check at given position and return module size (0 if failed) */
+static float vertical_crosscheck(Image *img, int cx, int cy, float h_module __attribute__((unused)), int *out_cy) {
+ int h = img->height;
+ int vc[5] = {0};
+ int vstate = 2;
+ int vy_top = cy, vy_bot = cy;
+
+ /* Scan upward from cy */
+ for (int dy = 0; cy - dy >= 0; dy++) {
+ int pix = image_get(img, cx, cy - dy);
+ if (vstate == 2) {
+ if (pix) { vc[2]++; vy_top = cy - dy; }
+ else { vstate = 1; vc[1]++; }
+ } else if (vstate == 1) {
+ if (!pix) { vc[1]++; }
+ else { vstate = 0; vc[0]++; vy_top = cy - dy; }
+ } else {
+ if (pix) { vc[0]++; vy_top = cy - dy; }
+ else break;
+ }
+ }
+
+ vstate = 2;
+ /* Scan downward from cy */
+ for (int dy = 1; cy + dy < h; dy++) {
+ int pix = image_get(img, cx, cy + dy);
+ if (vstate == 2) {
+ if (pix) { vc[2]++; vy_bot = cy + dy; }
+ else { vstate = 3; vc[3]++; }
+ } else if (vstate == 3) {
+ if (!pix) { vc[3]++; }
+ else { vstate = 4; vc[4]++; vy_bot = cy + dy; }
+ } else {
+ if (pix) { vc[4]++; vy_bot = cy + dy; }
+ else break;
+ }
+ }
+
+ /* Use much higher tolerance for vertical checks with severely rotated codes */
+ float v_module = check_finder_ratio_ex(vc, 0.8f);
+ if (v_module > 0) {
+ *out_cy = (vy_top + vy_bot) / 2;
+ return v_module;
+ }
+ return 0;
+}
+
+/* Perform horizontal cross-check at given position and return module size (0 if failed) */
+static float horizontal_crosscheck(Image *img, int cx, int cy, float v_module __attribute__((unused)), int *out_cx) {
+ int w = img->width;
+ int hc[5] = {0};
+ int hstate = 2;
+ int hx_left = cx, hx_right = cx;
+
+ /* Scan leftward from cx */
+ for (int dx = 0; cx - dx >= 0; dx++) {
+ int pix = image_get(img, cx - dx, cy);
+ if (hstate == 2) {
+ if (pix) { hc[2]++; hx_left = cx - dx; }
+ else { hstate = 1; hc[1]++; }
+ } else if (hstate == 1) {
+ if (!pix) { hc[1]++; }
+ else { hstate = 0; hc[0]++; hx_left = cx - dx; }
+ } else {
+ if (pix) { hc[0]++; hx_left = cx - dx; }
+ else break;
+ }
+ }
+
+ hstate = 2;
+ /* Scan rightward from cx */
+ for (int dx = 1; cx + dx < w; dx++) {
+ int pix = image_get(img, cx + dx, cy);
+ if (hstate == 2) {
+ if (pix) { hc[2]++; hx_right = cx + dx; }
+ else { hstate = 3; hc[3]++; }
+ } else if (hstate == 3) {
+ if (!pix) { hc[3]++; }
+ else { hstate = 4; hc[4]++; hx_right = cx + dx; }
+ } else {
+ if (pix) { hc[4]++; hx_right = cx + dx; }
+ else break;
+ }
+ }
+
+ /* Use much higher tolerance for horizontal checks with severely rotated codes */
+ float h_module = check_finder_ratio_ex(hc, 0.8f);
+ if (h_module > 0) {
+ *out_cx = (hx_left + hx_right) / 2;
+ return h_module;
+ }
+ return 0;
+}
+
+/* Add a finder line to a list */
+static int add_finder_line(FinderLine *lines, int count, int max_lines,
+ int pos, int bound_min, int bound_max) {
+ if (count < max_lines) {
+ lines[count].pos = pos;
+ lines[count].bound_min = bound_min;
+ lines[count].bound_max = bound_max;
+ return count + 1;
+ }
+ return count;
+}
+
+/* Check if two ranges overlap significantly.
+ * Requires at least 50% of the smaller range to be in the overlap.
+ * This prevents merging adjacent but distinct patterns that barely touch. */
+static int ranges_overlap(int a_min, int a_max, int b_min, int b_max) {
+ int overlap_min = (a_min > b_min) ? a_min : b_min;
+ int overlap_max = (a_max < b_max) ? a_max : b_max;
+ if (overlap_min > overlap_max) return 0; /* No overlap */
+
+ int overlap_size = overlap_max - overlap_min;
+ int a_size = a_max - a_min;
+ int b_size = b_max - b_min;
+ int smaller_size = (a_size < b_size) ? a_size : b_size;
+
+ /* Require overlap to be at least 50% of the smaller range */
+ return overlap_size >= smaller_size / 2;
+}
+
+/* Cluster finder lines into ranges using consecutive-overlap approach.
+ * Lines must be sorted by pos. A line joins a cluster if:
+ * 1. Its pos is exactly consecutive (pos == last_pos + 1)
+ * 2. Its bounds overlap with the last line's bounds
+ * This naturally handles rotation (bounds drift gradually) while
+ * rejecting unrelated patterns (bounds don't overlap).
+ *
+ * The ranges array is dynamically grown as needed. Caller must pass pointers
+ * to the array and its capacity, and must free the array when done. */
+static int cluster_finder_lines(FinderLine *lines, int nlines,
+ FinderRange **ranges_ptr, int *capacity_ptr) {
+ if (nlines == 0) return 0;
+
+ FinderRange *ranges = *ranges_ptr;
+ int capacity = *capacity_ptr;
+ int nranges = 0;
+
+ /* Track active clusters - clusters that ended at recent positions.
+ * Each active cluster remembers its last line's bounds for overlap checking,
+ * and how many positions ago it was last updated (gap_count). */
+ typedef struct {
+ int range_idx; /* Index into ranges array */
+ int last_bound_min; /* Last line's bound_min */
+ int last_bound_max; /* Last line's bound_max */
+ int last_pos; /* Position where this cluster was last updated */
+ } ActiveCluster;
+
+ /* Allow gaps of up to MAX_GAP positions in a cluster.
+ * This handles rotated finder patterns where some scan lines
+ * don't produce valid 1:1:3:1:1 ratios. */
+ #define MAX_GAP 3
+
+ ActiveCluster active[128];
+ int nactive = 0;
+ int last_pos = -999; /* Position of last processed line */
+
+ for (int i = 0; i < nlines; i++) {
+ FinderLine *line = &lines[i];
+
+ /* If we've moved to a new position, prune clusters that are too old */
+ if (line->pos != last_pos) {
+ /* Remove clusters that haven't been updated in MAX_GAP+1 positions */
+ int write_idx = 0;
+ for (int a = 0; a < nactive; a++) {
+ if (line->pos - active[a].last_pos <= MAX_GAP + 1) {
+ if (write_idx != a) {
+ active[write_idx] = active[a];
+ }
+ write_idx++;
+ }
+ }
+ nactive = write_idx;
+ last_pos = line->pos;
+ }
+
+ /* Try to find an active cluster whose last bounds overlap with this line.
+ * For clusters with gaps, also check that the line center is close to
+ * the cluster's running average center to prevent merging distant patterns. */
+ int matched = -1;
+ int line_center = (line->bound_min + line->bound_max) / 2;
+ for (int a = 0; a < nactive; a++) {
+ if (ranges_overlap(line->bound_min, line->bound_max,
+ active[a].last_bound_min, active[a].last_bound_max)) {
+ /* Check if there's a gap since last update */
+ int gap = line->pos - active[a].last_pos;
+ if (gap > 1) {
+ /* With a gap, require the line center to be within the cluster's
+ * accumulated bounds. This prevents merging separate patterns
+ * that happen to have overlapping bounds after drifting. */
+ FinderRange *r = &ranges[active[a].range_idx];
+ int cluster_center_min = r->center_min;
+ int cluster_center_max = r->center_max;
+ if (line_center < cluster_center_min - 10 ||
+ line_center > cluster_center_max + 10) {
+ continue; /* Line center too far from cluster */
+ }
+ }
+ matched = a;
+ break;
+ }
+ }
+
+ if (matched >= 0) {
+ /* Add to existing cluster */
+ int ri = active[matched].range_idx;
+ FinderRange *r = &ranges[ri];
+ r->pos_max = line->pos;
+ if (line->bound_min < r->center_min) r->center_min = line->bound_min;
+ if (line->bound_max > r->center_max) r->center_max = line->bound_max;
+ r->center_sum += (line->bound_min + line->bound_max) / 2;
+ r->module_sum += (line->bound_max - line->bound_min) / 7.0f;
+ r->count++;
+ /* Update the cluster's bounds and position */
+ active[matched].last_bound_min = line->bound_min;
+ active[matched].last_bound_max = line->bound_max;
+ active[matched].last_pos = line->pos;
+ } else if (nactive < 128) {
+ /* Start a new cluster - grow array if needed */
+ if (nranges >= capacity) {
+ capacity *= 2;
+ ranges = realloc(ranges, capacity * sizeof(FinderRange));
+ if (!ranges) {
+ fprintf(stderr, "Out of memory growing ranges array\n");
+ *ranges_ptr = NULL;
+ *capacity_ptr = 0;
+ return 0;
+ }
+ *ranges_ptr = ranges;
+ *capacity_ptr = capacity;
+ }
+ FinderRange *r = &ranges[nranges];
+ r->pos_min = r->pos_max = line->pos;
+ r->center_min = line->bound_min;
+ r->center_max = line->bound_max;
+ r->center_sum = (line->bound_min + line->bound_max) / 2;
+ r->module_sum = (line->bound_max - line->bound_min) / 7.0f;
+ r->count = 1;
+ /* Add to active clusters */
+ active[nactive].range_idx = nranges;
+ active[nactive].last_bound_min = line->bound_min;
+ active[nactive].last_bound_max = line->bound_max;
+ active[nactive].last_pos = line->pos;
+ nactive++;
+ nranges++;
+ }
+ }
+
+ /* Validate ranges and compact - require minimum line count */
+ int valid_count = 0;
+ for (int i = 0; i < nranges; i++) {
+ FinderRange *r = &ranges[i];
+ float avg_module = r->module_sum / r->count;
+ int pos_span = r->pos_max - r->pos_min + 1;
+
+ /* Require enough lines to be confident this is a real finder.
+ * A finder pattern is 7 modules wide, so we expect ~7*module_size lines.
+ * For rotated patterns, not every scan line produces a valid 1:1:3:1:1 ratio,
+ * so we only require ~1/5 of the expected lines. */
+ int min_lines = (int)(avg_module * 7 * 0.2f);
+ if (min_lines < 3) min_lines = 3;
+
+ if (debug_finder && r->count >= 3) { // DEBUG
+ fprintf(stderr, " Cluster: pos=[%d-%d] bounds=[%d-%d] count=%d module=%.1f min_lines=%d span=%d -> %s\n",
+ r->pos_min, r->pos_max, r->center_min, r->center_max,
+ r->count, avg_module, min_lines, pos_span,
+ (r->count >= min_lines && pos_span >= 5) ? "ACCEPT" : "REJECT");
+ }
+ if (r->count >= min_lines && pos_span >= 5) {
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " Range: pos=[%d-%d] bounds=[%d-%d] avg_center=%d count=%d module=%.1f\n", // DEBUG
+ r->pos_min, r->pos_max, r->center_min, r->center_max, // DEBUG
+ r->center_sum / r->count, r->count, avg_module); // DEBUG
+ } // DEBUG
+ if (valid_count != i) {
+ ranges[valid_count] = *r;
+ }
+ valid_count++;
+ }
+ }
+
+ return valid_count;
+}
+
+/* Compare function for sorting finder lines by position, then center */
+static int compare_finder_lines_by_pos(const void *a, const void *b) {
+ const FinderLine *la = (const FinderLine *)a;
+ const FinderLine *lb = (const FinderLine *)b;
+ return la->pos - lb->pos;
+}
+
+/* Compare function for sorting finder lines by center, then position */
+static int compare_finder_lines_by_bound_min(const void *a, const void *b) {
+ const FinderLine *la = (const FinderLine *)a;
+ const FinderLine *lb = (const FinderLine *)b;
+ if (la->bound_min != lb->bound_min) return la->bound_min - lb->bound_min;
+ return la->pos - lb->pos;
+}
+
+/* Find all finder patterns using range intersection method (zbar-style) */
+static int find_finder_patterns(Image *img, FinderPattern *patterns, int max_patterns) {
+ int w = img->width;
+ int h = img->height;
+
+ /* Allocate line storage */
+ FinderLine *h_lines = malloc(MAX_FINDER_LINES * sizeof(FinderLine));
+ FinderLine *v_lines = malloc(MAX_FINDER_LINES * sizeof(FinderLine));
+ int h_ranges_cap = INITIAL_FINDER_RANGES;
+ int v_ranges_cap = INITIAL_FINDER_RANGES;
+ FinderRange *h_ranges = malloc(h_ranges_cap * sizeof(FinderRange));
+ FinderRange *v_ranges = malloc(v_ranges_cap * sizeof(FinderRange));
+
+ if (!h_lines || !v_lines || !h_ranges || !v_ranges) {
+ free(h_lines); free(v_lines); free(h_ranges); free(v_ranges);
+ return 0;
+ }
+
+ int nh_lines = 0, nv_lines = 0;
+
+ /* ====== PASS 1: Collect horizontal finder lines ====== */
+ for (int y = 0; y < h; y++) {
+ int counts[5] = {0};
+ int state = 0;
+
+ for (int x = 0; x < w; x++) {
+ int pixel = image_get(img, x, y);
+
+ if (state == 0) {
+ if (pixel) { state = 1; counts[0] = 1; }
+ } else if (state == 1) {
+ if (pixel) counts[0]++;
+ else { state = 2; counts[1] = 1; }
+ } else if (state == 2) {
+ if (!pixel) counts[1]++;
+ else { state = 3; counts[2] = 1; }
+ } else if (state == 3) {
+ if (pixel) counts[2]++;
+ else { state = 4; counts[3] = 1; }
+ } else if (state == 4) {
+ if (!pixel) counts[3]++;
+ else { state = 5; counts[4] = 1; }
+ } else if (state == 5) {
+ if (pixel) counts[4]++;
+ else {
+ float module = check_finder_ratio_ex(counts, 0.8f);
+ if (module > 0) {
+ /* Record the bounds of the pattern */
+ int total_width = counts[0] + counts[1] + counts[2] + counts[3] + counts[4];
+ int bound_min = x - total_width;
+ int bound_max = x - 1; /* x is now on the white pixel after the pattern */
+ nh_lines = add_finder_line(h_lines, nh_lines, MAX_FINDER_LINES,
+ y, bound_min, bound_max);
+ }
+ counts[0] = counts[2];
+ counts[1] = counts[3];
+ counts[2] = counts[4];
+ counts[3] = 1;
+ counts[4] = 0;
+ state = 4;
+ }
+ }
+ }
+ }
+
+ /* ====== PASS 2: Collect vertical finder lines ====== */
+ for (int x = 0; x < w; x++) {
+ int counts[5] = {0};
+ int state = 0;
+
+ for (int y = 0; y < h; y++) {
+ int pixel = image_get(img, x, y);
+
+ if (state == 0) {
+ if (pixel) { state = 1; counts[0] = 1; }
+ } else if (state == 1) {
+ if (pixel) counts[0]++;
+ else { state = 2; counts[1] = 1; }
+ } else if (state == 2) {
+ if (!pixel) counts[1]++;
+ else { state = 3; counts[2] = 1; }
+ } else if (state == 3) {
+ if (pixel) counts[2]++;
+ else { state = 4; counts[3] = 1; }
+ } else if (state == 4) {
+ if (!pixel) counts[3]++;
+ else { state = 5; counts[4] = 1; }
+ } else if (state == 5) {
+ if (pixel) counts[4]++;
+ else {
+ float module = check_finder_ratio_ex(counts, 0.8f);
+ if (module > 0) {
+ /* Record the bounds of the pattern */
+ int total_width = counts[0] + counts[1] + counts[2] + counts[3] + counts[4];
+ int bound_min = y - total_width;
+ int bound_max = y - 1; /* y is now on the white pixel after the pattern */
+ nv_lines = add_finder_line(v_lines, nv_lines, MAX_FINDER_LINES,
+ x, bound_min, bound_max);
+ }
+ counts[0] = counts[2];
+ counts[1] = counts[3];
+ counts[2] = counts[4];
+ counts[3] = 1;
+ counts[4] = 0;
+ state = 4;
+ }
+ }
+ }
+ }
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " Collected %d horizontal lines, %d vertical lines\n", nh_lines, nv_lines); // DEBUG
+ /* Debug: show first 50 horizontal lines */ // DEBUG
+ for (int i = 0; i < nh_lines && i < 50; i++) { // DEBUG
+ fprintf(stderr, " h_line: y=%d x=[%d-%d]\n", // DEBUG
+ h_lines[i].pos, h_lines[i].bound_min, h_lines[i].bound_max); // DEBUG
+ } // DEBUG
+ } // DEBUG
+
+ /* ====== PASS 3: Sort by pos and cluster lines into ranges ====== */
+ /* Sort by pos (scan position) so lines from same finder pattern are adjacent */
+ qsort(h_lines, nh_lines, sizeof(FinderLine), compare_finder_lines_by_pos);
+ qsort(v_lines, nv_lines, sizeof(FinderLine), compare_finder_lines_by_pos);
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " Horizontal ranges:\n"); // DEBUG
+ } // DEBUG
+ int nh_ranges = cluster_finder_lines(h_lines, nh_lines, &h_ranges, &h_ranges_cap);
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " Vertical ranges:\n"); // DEBUG
+ } // DEBUG
+ int nv_ranges = cluster_finder_lines(v_lines, nv_lines, &v_ranges, &v_ranges_cap);
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " Clustered into %d horizontal ranges, %d vertical ranges\n", nh_ranges, nv_ranges); // DEBUG
+ } // DEBUG
+
+ /* ====== PASS 4: Find range intersections ====== */
+ int count = 0;
+
+ for (int hi = 0; hi < nh_ranges && count < max_patterns; hi++) {
+ FinderRange *hr = &h_ranges[hi];
+ int h_center_x = hr->center_sum / hr->count;
+ float h_module = hr->module_sum / hr->count;
+
+ for (int vi = 0; vi < nv_ranges && count < max_patterns; vi++) {
+ FinderRange *vr = &v_ranges[vi];
+ int v_center_y = vr->center_sum / vr->count;
+ float v_module = vr->module_sum / vr->count;
+
+ /* Check if ranges overlap spatially */
+ /* Horizontal range: y from pos_min to pos_max, x around h_center_x */
+ /* Vertical range: x from pos_min to pos_max, y around v_center_y */
+
+ /* Use adaptive tolerance based on module size - larger patterns can be more distorted
+ * But keep tolerance reasonable to avoid matching wrong finder patterns.
+ * A finder pattern is 7 modules wide, so the center should be within ~3.5 modules
+ * of the range. Use 1.5 modules tolerance to allow for some distortion while
+ * avoiding matching wrong finders in dense multi-QR images. */
+ float x_tolerance = h_module * 1.5f;
+ float y_tolerance = v_module * 1.5f;
+
+ /* Does the vertical range's x span include h_center_x? */
+ int vx_min = vr->pos_min;
+ int vx_max = vr->pos_max;
+
+ if (h_center_x < vx_min - x_tolerance || h_center_x > vx_max + x_tolerance)
+ continue;
+
+ /* Does the horizontal range's y span include v_center_y? */
+ int hy_min = hr->pos_min;
+ int hy_max = hr->pos_max;
+
+ if (v_center_y < hy_min - y_tolerance || v_center_y > hy_max + y_tolerance)
+ continue;
+
+ /* Module sizes can vary significantly due to perspective distortion
+ * Allow a much wider range, especially for larger modules */
+ float module_ratio = (h_module > v_module) ? h_module / v_module : v_module / h_module;
+ float max_ratio = 2.5f; /* Increased from 2.0f */
+
+ /* For larger modules, allow even greater distortion ratios */
+ if (h_module > 3.0f || v_module > 3.0f) {
+ max_ratio = 3.0f; /* Allow up to 3x difference for large modules */
+ }
+
+ if (module_ratio > max_ratio)
+ continue;
+
+ /* Found intersection! Compute finder center */
+ /* Use weighted center calculation for more accurate center estimation
+ * with distorted patterns. Weight by proximity to the center for better results
+ * with perspective distortion */
+
+ /* For x-coordinate, use the average center value from horizontal range */
+ /* (horizontal scan finds where along x-axis the pattern center is) */
+ int finder_x = hr->center_sum / hr->count;
+
+ /* For y-coordinate, use the average center value from vertical range */
+ /* (vertical scan finds where along y-axis the pattern center is) */
+ int finder_y = vr->center_sum / vr->count;
+
+ /* Store both horizontal and vertical module sizes to capture aspect ratio distortion */
+ float finder_module = fsqrt(h_module * v_module); /* Geometric mean for overall size */
+
+ /* Check for duplicate */
+ int is_dup = 0;
+ for (int i = 0; i < count; i++) {
+ int dx = patterns[i].x - finder_x;
+ int dy = patterns[i].y - finder_y;
+ if (dx*dx + dy*dy < finder_module * finder_module * 16) {
+ is_dup = 1;
+ break;
+ }
+ }
+
+ if (!is_dup) {
+ patterns[count].x = finder_x;
+ patterns[count].y = finder_y;
+ patterns[count].module_size = finder_module;
+ patterns[count].module_size_x = h_module;
+ patterns[count].module_size_y = v_module;
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " FOUND FINDER #%d at (%d,%d) module=%.1f (x=%.1f, y=%.1f) " // DEBUG
+ "h_range=[%d-%d] v_range=[%d-%d]\n", // DEBUG
+ count, finder_x, finder_y, finder_module, h_module, v_module, // DEBUG
+ hr->pos_min, hr->pos_max, vr->pos_min, vr->pos_max); // DEBUG
+ } // DEBUG
+ count++;
+ }
+ }
+ }
+
+ free(h_lines);
+ free(v_lines);
+ free(h_ranges);
+ free(v_ranges);
+
+ return count;
+}
+
+/* QR code version/size info */
+STATIC int qr_version_to_size(int version) {
+ return 17 + 4 * version;
+}
+
+
+
+/* Extract QR code grid from image */
+typedef struct {
+ uint8_t modules[MAX_QR_MODULES][MAX_QR_MODULES];
+ int size;
+ int version;
+} QRCode;
+
+/* Alignment pattern positions for each version */
+static const int alignment_positions[][8] = {
+ {0}, /* Version 1 - no alignment */
+ {6, 18, 0}, /* Version 2 */
+ {6, 22, 0}, /* Version 3 */
+ {6, 26, 0}, /* Version 4 */
+ {6, 30, 0}, /* Version 5 */
+ {6, 34, 0}, /* Version 6 */
+ {6, 22, 38, 0}, /* Version 7 */
+ {6, 24, 42, 0}, /* Version 8 */
+ {6, 26, 46, 0}, /* Version 9 */
+ {6, 28, 50, 0}, /* Version 10 */
+ {6, 30, 54, 0}, /* Version 11 */
+ {6, 32, 58, 0}, /* Version 12 */
+ {6, 34, 62, 0}, /* Version 13 */
+ {6, 26, 46, 66, 0}, /* Version 14 */
+ {6, 26, 48, 70, 0}, /* Version 15 */
+ {6, 26, 50, 74, 0}, /* Version 16 */
+ {6, 30, 54, 78, 0}, /* Version 17 */
+ {6, 30, 56, 82, 0}, /* Version 18 */
+ {6, 30, 58, 86, 0}, /* Version 19 */
+ {6, 34, 62, 90, 0}, /* Version 20 */
+ {6, 28, 50, 72, 94, 0}, /* Version 21 */
+ {6, 26, 50, 74, 98, 0}, /* Version 22 */
+ {6, 30, 54, 78, 102, 0}, /* Version 23 */
+ {6, 28, 54, 80, 106, 0}, /* Version 24 */
+ {6, 32, 58, 84, 110, 0}, /* Version 25 */
+ {6, 30, 58, 86, 114, 0}, /* Version 26 */
+ {6, 34, 62, 90, 118, 0}, /* Version 27 */
+ {6, 26, 50, 74, 98, 122, 0}, /* Version 28 */
+ {6, 30, 54, 78, 102, 126, 0}, /* Version 29 */
+ {6, 26, 52, 78, 104, 130, 0}, /* Version 30 */
+ {6, 30, 56, 82, 108, 134, 0}, /* Version 31 */
+ {6, 34, 60, 86, 112, 138, 0}, /* Version 32 */
+ {6, 30, 58, 86, 114, 142, 0}, /* Version 33 */
+ {6, 34, 62, 90, 118, 146, 0}, /* Version 34 */
+ {6, 30, 54, 78, 102, 126, 150, 0}, /* Version 35 */
+ {6, 24, 50, 76, 102, 128, 154, 0}, /* Version 36 */
+ {6, 28, 54, 80, 106, 132, 158, 0}, /* Version 37 */
+ {6, 32, 58, 84, 110, 136, 162, 0}, /* Version 38 */
+ {6, 26, 54, 82, 110, 138, 166, 0}, /* Version 39 */
+ {6, 30, 58, 86, 114, 142, 170, 0}, /* Version 40 */
+};
+
+/* Check if a module is a function pattern (not data) */
+STATIC int is_function_module(int version, int x, int y) {
+ int size = qr_version_to_size(version);
+
+ /* Finder patterns + separators + format info */
+ if ((x < 9 && y < 9) || /* Top-left */
+ (x < 8 && y >= size - 8) || /* Bottom-left (not including format info col) */
+ (x >= size - 8 && y < 9)) { /* Top-right */
+ return 1;
+ }
+
+ /* Format info areas */
+ if ((y == 8 && (x < 9 || x >= size - 8)) || /* Row 8 format info */
+ (x == 8 && (y < 9 || y >= size - 8))) { /* Col 8 format info */
+ return 1;
+ }
+
+ /* Timing patterns */
+ if (x == 6 || y == 6) return 1;
+
+ /* Alignment patterns */
+ if (version >= 2) {
+ const int *pos = alignment_positions[version - 1];
+ for (int i = 0; pos[i]; i++) {
+ for (int j = 0; pos[j]; j++) {
+ int ax = pos[i], ay = pos[j];
+ /* Skip if overlapping with finder patterns */
+ if ((ax < 9 && ay < 9) ||
+ (ax < 9 && ay >= size - 8) ||
+ (ax >= size - 8 && ay < 9)) continue;
+
+ if (x >= ax - 2 && x <= ax + 2 && y >= ay - 2 && y <= ay + 2) {
+ return 1;
+ }
+ }
+ }
+ }
+
+ /* Version info (versions 7+) */
+ if (version >= 7) {
+ if ((x < 6 && y >= size - 11 && y < size - 8) ||
+ (y < 6 && x >= size - 11 && x < size - 8)) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/* Read format information from QR code (from pre-sampled modules array) */
+static int read_format_info(QRCode *qr) {
+ /* Format info is stored around finder patterns
+ *
+ * Following quirc's approach: read 15 bits MSB-first from specific positions.
+ * Positions are indexed from top-left (0,0).
+ *
+ * quirc reads in order i=14..0 using:
+ * xs[15] = {8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0}
+ * ys[15] = {0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8}
+ * format = (format << 1) | grid_bit(code, xs[i], ys[i])
+ *
+ * So reading i from 14 to 0:
+ * i=14: (0,8) i=13: (1,8) i=12: (2,8) i=11: (3,8) i=10: (4,8) i=9: (5,8)
+ * i=8: (7,8) i=7: (8,8) i=6: (8,7) i=5: (8,5) i=4: (8,4)
+ * i=3: (8,3) i=2: (8,2) i=1: (8,1) i=0: (8,0)
+ *
+ * Note: quirc uses (col, row) = (x, y), our modules[row][col]
+ */
+ static const int xs[15] = {8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0};
+ static const int ys[15] = {0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8};
+
+ uint32_t format1 = 0;
+
+ /* Read MSB-first, matching quirc's order */
+ for (int i = 14; i >= 0; i--) {
+ int col = xs[i];
+ int row = ys[i];
+ format1 = (format1 << 1) | (qr->modules[row][col] & 1);
+ }
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " format bits (raw): row8[0-5]=%d%d%d%d%d%d row8[7]=%d row8[8]=%d row7[8]=%d\n", // DEBUG
+ qr->modules[8][0], qr->modules[8][1], qr->modules[8][2], // DEBUG
+ qr->modules[8][3], qr->modules[8][4], qr->modules[8][5], // DEBUG
+ qr->modules[8][7], qr->modules[8][8], qr->modules[7][8]); // DEBUG
+ fprintf(stderr, " format bits cont: col8[5-0]=%d%d%d%d%d%d raw=0x%04x\n", // DEBUG
+ qr->modules[5][8], qr->modules[4][8], qr->modules[3][8], // DEBUG
+ qr->modules[2][8], qr->modules[1][8], qr->modules[0][8], format1); // DEBUG
+ } // DEBUG
+
+ /* XOR with mask pattern */
+ format1 ^= 0x5412;
+
+ return format1;
+}
+
+/* Forward declaration for sample_module_transform_ex */
+static int sample_module_transform_ex(Image *img, const QRTransform *transform, int mx, int my, int use_antialiasing);
+
+/* Read format information directly from image without anti-aliasing.
+ * This is more accurate for format bits which can be on module boundaries. */
+static int read_format_info_direct(Image *img, const QRTransform *transform) {
+ static const int xs[15] = {8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0};
+ static const int ys[15] = {0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8};
+
+ uint32_t format1 = 0;
+
+ /* Read MSB-first without anti-aliasing for better accuracy */
+ for (int i = 14; i >= 0; i--) {
+ int col = xs[i];
+ int row = ys[i];
+ int bit = sample_module_transform_ex(img, transform, col, row, 0);
+ format1 = (format1 << 1) | bit;
+ }
+
+ /* XOR with mask pattern */
+ format1 ^= 0x5412;
+
+ return format1;
+}
+
+/* Valid format info values AFTER unmasking (XOR with 0x5412)
+ * These are the 32 valid BCH(15,5) codewords for format info.
+ * Index = (ecc_level << 3) | mask_pattern, where ecc: 0=M, 1=L, 2=H, 3=Q */
+static const uint16_t valid_format_unmasked[32] = {
+ /* M-0 to M-7: unmasked values */
+ 0x0000, 0x0537, 0x0A6E, 0x0F59, 0x11EB, 0x14DC, 0x1B85, 0x1EB2,
+ /* L-0 to L-7 */
+ 0x23D6, 0x26E1, 0x29B8, 0x2C8F, 0x323D, 0x370A, 0x3853, 0x3D64,
+ /* H-0 to H-7 */
+ 0x429B, 0x47AC, 0x48F5, 0x4DC2, 0x5370, 0x5647, 0x591E, 0x5C29,
+ /* Q-0 to Q-7 */
+ 0x614D, 0x647A, 0x6B23, 0x6E14, 0x70A6, 0x7591, 0x7AC8, 0x7FFF
+};
+
+/* Validate and correct format info using BCH(15,5) code
+ * BCH(15,5) can correct up to 3 bit errors
+ * Input is the format info AFTER XOR with 0x5412 (unmasked)
+ * Returns the corrected format info, or -1 if uncorrectable */
+static int validate_format_info_with_correction(int format_info) {
+ /* First check if already valid (syndrome = 0) */
+ int remainder = format_info;
+ for (int i = 14; i >= 10; i--) {
+ if (remainder & (1 << i)) {
+ remainder ^= (0x537 << (i - 10));
+ }
+ }
+ if (remainder == 0) return format_info; /* Already valid */
+
+ /* Not valid - try to find closest valid format by Hamming distance
+ * BCH(15,5) can correct up to 3 errors. We accept up to 4 errors to handle
+ * sampling inaccuracies at module boundaries */
+ int best_match = -1;
+ int best_distance = 5; /* Accept distance <= 4 */
+
+ for (int i = 0; i < 32; i++) {
+ int diff = format_info ^ valid_format_unmasked[i];
+ int distance = 0;
+ while (diff) {
+ distance += diff & 1;
+ diff >>= 1;
+ }
+ if (distance < best_distance) {
+ best_distance = distance;
+ best_match = valid_format_unmasked[i];
+ }
+ }
+
+ return best_match; /* Returns -1 if no match within distance 3 */
+}
+
+/* Legacy validation function - just checks if valid, no correction */
+static int validate_format_info(int format_info) {
+ return validate_format_info_with_correction(format_info) == format_info;
+}
+
+/* Galois field arithmetic for Reed-Solomon */
+static uint8_t gf_exp[512];
+static uint8_t gf_log[256];
+static int gf_initialized = 0;
+
+STATIC void gf_init(void) {
+ if (gf_initialized) return;
+
+ int x = 1;
+ for (int i = 0; i < 255; i++) {
+ gf_exp[i] = x;
+ gf_log[x] = i;
+ x <<= 1;
+ if (x & 0x100) x ^= 0x11d; /* QR code polynomial */
+ }
+ for (int i = 255; i < 512; i++) {
+ gf_exp[i] = gf_exp[i - 255];
+ }
+ gf_initialized = 1;
+}
+
+STATIC uint8_t gf_mul(uint8_t a, uint8_t b) {
+ if (a == 0 || b == 0) return 0;
+ return gf_exp[gf_log[a] + gf_log[b]];
+}
+
+/* GF division - used for Reed-Solomon error correction */
+STATIC uint8_t gf_div(uint8_t a, uint8_t b) {
+ if (b == 0) return 0; /* Division by zero */
+ if (a == 0) return 0;
+ return gf_exp[(gf_log[a] + 255 - gf_log[b]) % 255];
+}
+
+/* Reed-Solomon syndrome calculation for QR codes
+ * Syndromes are S_i = R(alpha^i) for i = 0 to ecc_len-1
+ * where R(x) is the received polynomial */
+STATIC void rs_calc_syndromes(uint8_t *data, int total_len, int ecc_len, uint8_t *syndromes) {
+ gf_init();
+
+ for (int i = 0; i < ecc_len; i++) {
+ uint8_t s = 0;
+ for (int j = 0; j < total_len; j++) {
+ s = gf_mul(s, gf_exp[i]) ^ data[j];
+ }
+ syndromes[i] = s;
+ }
+}
+
+/* Check if all syndromes are zero (no errors) */
+static int rs_check_syndromes(uint8_t *data, int data_len, int ecc_len) {
+ gf_init();
+
+ uint8_t syndromes[256];
+ rs_calc_syndromes(data, data_len + ecc_len, ecc_len, syndromes);
+
+ for (int i = 0; i < ecc_len; i++) {
+ if (syndromes[i] != 0) return 0;
+ }
+ return 1; /* Returns 1 if no errors */
+}
+
+/* Berlekamp-Massey algorithm to find the error locator polynomial
+ * Returns the degree of the error locator polynomial (number of errors)
+ * sigma[] contains the coefficients: sigma(x) = 1 + sigma[1]*x + sigma[2]*x^2 + ...
+ */
+static int rs_berlekamp_massey(uint8_t *syndromes, int ecc_len, uint8_t *sigma) {
+ gf_init();
+
+ uint8_t C[256] = {0}; /* Connection polynomial */
+ uint8_t B[256] = {0}; /* Previous connection polynomial */
+
+ C[0] = 1;
+ B[0] = 1;
+
+ int L = 0; /* Current length of LFSR */
+ int m = 1; /* Number of iterations since L was updated */
+ uint8_t b = 1; /* Previous discrepancy */
+
+ for (int n = 0; n < ecc_len; n++) {
+ /* Calculate discrepancy */
+ uint8_t d = syndromes[n];
+ for (int i = 1; i <= L; i++) {
+ d ^= gf_mul(C[i], syndromes[n - i]);
+ }
+
+ if (d == 0) {
+ m++;
+ } else if (2 * L <= n) {
+ /* Update L */
+ uint8_t T[256];
+ memcpy(T, C, sizeof(T));
+
+ uint8_t coef = gf_div(d, b);
+ for (int i = 0; i < ecc_len - m; i++) {
+ C[i + m] ^= gf_mul(coef, B[i]);
+ }
+
+ memcpy(B, T, sizeof(B));
+ L = n + 1 - L;
+ b = d;
+ m = 1;
+ } else {
+ /* Just update C */
+ uint8_t coef = gf_div(d, b);
+ for (int i = 0; i < ecc_len - m; i++) {
+ C[i + m] ^= gf_mul(coef, B[i]);
+ }
+ m++;
+ }
+ }
+
+ /* Copy result to sigma */
+ memcpy(sigma, C, ecc_len + 1);
+
+ return L;
+}
+
+/* Chien search to find error positions
+ * Returns number of errors found, or -1 if inconsistent
+ * positions[] contains the error positions (as powers of alpha) */
+static int rs_chien_search(uint8_t *sigma, int num_errors, int n, int *positions) {
+ gf_init();
+
+ int found = 0;
+
+ for (int i = 0; i < n; i++) {
+ /* Evaluate sigma at alpha^(-i) = alpha^(255-i) */
+ uint8_t sum = sigma[0]; /* = 1 */
+ for (int j = 1; j <= num_errors; j++) {
+ uint8_t power = ((255 - i) * j) % 255;
+ sum ^= gf_mul(sigma[j], gf_exp[power]);
+ }
+
+ if (sum == 0) {
+ /* Found a root - error at position i */
+ positions[found++] = n - 1 - i; /* Convert to byte index */
+ }
+ }
+
+ /* Check we found the right number of errors */
+ return (found == num_errors) ? found : -1;
+}
+
+/* Forney's algorithm to calculate error values
+ * omega(x) = S(x) * sigma(x) mod x^(2t) where S is syndrome polynomial
+ * e_i = omega(X_i^-1) / sigma'(X_i^-1) where X_i = alpha^(pos[i])
+ */
+static void rs_forney(uint8_t *syndromes, uint8_t *sigma, int num_errors,
+ int *positions, int n, uint8_t *values) {
+ gf_init();
+
+ /* Calculate omega(x) = S(x) * sigma(x) truncated to num_errors terms
+ * S(x) = S_0 + S_1*x + S_2*x^2 + ... */
+ uint8_t omega[256] = {0};
+ for (int i = 0; i < num_errors; i++) {
+ uint8_t v = 0;
+ for (int j = 0; j <= i; j++) {
+ v ^= gf_mul(syndromes[i - j], sigma[j]);
+ }
+ omega[i] = v;
+ }
+
+ /* Calculate formal derivative of sigma: sigma'(x) = sigma_1 + sigma_3*x^2 + sigma_5*x^4 + ... */
+ uint8_t sigma_prime[256] = {0};
+ for (int i = 1; i <= num_errors; i += 2) {
+ sigma_prime[i - 1] = sigma[i];
+ }
+
+ /* Calculate error values */
+ for (int i = 0; i < num_errors; i++) {
+ int pos = positions[i];
+ int Xi_inv_power = (n - 1 - pos) % 255; /* Power of alpha for X_i^-1 */
+
+ /* Evaluate omega at X_i^-1 */
+ uint8_t omega_val = 0;
+ for (int j = 0; j < num_errors; j++) {
+ omega_val ^= gf_mul(omega[j], gf_exp[(Xi_inv_power * j) % 255]);
+ }
+
+ /* Evaluate sigma' at X_i^-1 */
+ uint8_t sigma_prime_val = 0;
+ for (int j = 0; j < num_errors; j++) {
+ sigma_prime_val ^= gf_mul(sigma_prime[j], gf_exp[(Xi_inv_power * j) % 255]);
+ }
+
+ /* Error value = omega / sigma' */
+ if (sigma_prime_val != 0) {
+ values[i] = gf_div(omega_val, sigma_prime_val);
+ } else {
+ values[i] = 0;
+ }
+ }
+}
+
+/* Full Reed-Solomon error correction
+ * data: received data (data + ecc bytes)
+ * data_len: number of data bytes (not including ECC)
+ * ecc_len: number of ECC bytes
+ * Returns: number of errors corrected, or -1 if uncorrectable */
+static int rs_correct_errors(uint8_t *data, int data_len, int ecc_len) {
+ gf_init();
+
+ int total_len = data_len + ecc_len;
+ int max_errors = ecc_len / 2;
+
+ /* Calculate syndromes */
+ uint8_t syndromes[256];
+ rs_calc_syndromes(data, total_len, ecc_len, syndromes);
+
+ /* Check if already correct */
+ int all_zero = 1;
+ for (int i = 0; i < ecc_len; i++) {
+ if (syndromes[i] != 0) {
+ all_zero = 0;
+ break;
+ }
+ }
+ if (all_zero) return 0; /* No errors */
+
+ /* Find error locator polynomial using Berlekamp-Massey */
+ uint8_t sigma[256] = {0};
+ int num_errors = rs_berlekamp_massey(syndromes, ecc_len, sigma);
+
+ if (num_errors > max_errors) {
+ return -1; /* Too many errors */
+ }
+
+ /* Find error positions using Chien search */
+ int positions[256];
+ int found = rs_chien_search(sigma, num_errors, total_len, positions);
+
+ if (found != num_errors) {
+ return -1; /* Inconsistent - uncorrectable */
+ }
+
+ /* Calculate error values using Forney's algorithm */
+ uint8_t values[256];
+ rs_forney(syndromes, sigma, num_errors, positions, total_len, values);
+
+ /* Apply corrections */
+ for (int i = 0; i < num_errors; i++) {
+ if (positions[i] >= 0 && positions[i] < total_len) {
+ data[positions[i]] ^= values[i];
+ }
+ }
+
+ /* Verify correction by recalculating syndromes */
+ rs_calc_syndromes(data, total_len, ecc_len, syndromes);
+ for (int i = 0; i < ecc_len; i++) {
+ if (syndromes[i] != 0) {
+ return -1; /* Correction failed */
+ }
+ }
+
+ return num_errors;
+}
+
+/* Unmask QR code data */
+static void unmask_qr(QRCode *qr, int mask_pattern) {
+ int size = qr->size;
+
+ for (int y = 0; y < size; y++) {
+ for (int x = 0; x < size; x++) {
+ if (is_function_module(qr->version, x, y)) continue;
+
+ int should_flip = 0;
+ switch (mask_pattern) {
+ case 0: should_flip = (y + x) % 2 == 0; break;
+ case 1: should_flip = y % 2 == 0; break;
+ case 2: should_flip = x % 3 == 0; break;
+ case 3: should_flip = (y + x) % 3 == 0; break;
+ case 4: should_flip = (y/2 + x/3) % 2 == 0; break;
+ case 5: should_flip = (y*x) % 2 + (y*x) % 3 == 0; break;
+ case 6: should_flip = ((y*x) % 2 + (y*x) % 3) % 2 == 0; break;
+ case 7: should_flip = ((y+x) % 2 + (y*x) % 3) % 2 == 0; break;
+ }
+
+ if (should_flip) {
+ qr->modules[y][x] ^= 1;
+ }
+ }
+ }
+}
+
+/* Number of data codewords per version/error correction level
+ * Index: [version][ecc_raw], where ecc_raw is the 2-bit value from format info:
+ * 00 = M, 01 = L, 10 = H, 11 = Q
+ * So array order is: [M, L, H, Q] = indices [0, 1, 2, 3] */
+static const int data_codewords[41][4] = {
+ {0, 0, 0, 0}, /* Version 0 (invalid) */
+ {16, 19, 9, 13}, /* Version 1: M=16, L=19, H=9, Q=13 */
+ {28, 34, 16, 22}, /* Version 2: M=28, L=34, H=16, Q=22 */
+ {44, 55, 26, 34}, /* Version 3: M=44, L=55, H=26, Q=34 */
+ {64, 80, 36, 48}, /* Version 4: M=64, L=80, H=36, Q=48 */
+ {86, 108, 46, 62}, /* Version 5: M=86, L=108, H=46, Q=62 */
+ {108, 136, 60, 76}, /* Version 6: M=108, L=136, H=60, Q=76 */
+ {124, 156, 66, 88}, /* Version 7: M=124, L=156, H=66, Q=88 */
+ {154, 194, 86, 110}, /* Version 8: M=154, L=194, H=86, Q=110 */
+ {182, 232, 100, 132}, /* Version 9: M=182, L=232, H=100, Q=132 */
+ {216, 274, 122, 154}, /* Version 10: M=216, L=274, H=122, Q=154 */
+ {254, 324, 140, 180}, /* Version 11: M=254, L=324, H=140, Q=180 */
+ {290, 370, 158, 206}, /* Version 12: M=290, L=370, H=158, Q=206 */
+ {334, 428, 180, 244}, /* Version 13: M=334, L=428, H=180, Q=244 */
+ {365, 461, 197, 261}, /* Version 14: M=365, L=461, H=197, Q=261 */
+ {415, 523, 223, 295}, /* Version 15: M=415, L=523, H=223, Q=295 */
+ {453, 589, 253, 325}, /* Version 16: M=453, L=589, H=253, Q=325 */
+ {507, 647, 283, 367}, /* Version 17: M=507, L=647, H=283, Q=367 */
+ {563, 721, 313, 397}, /* Version 18: M=563, L=721, H=313, Q=397 */
+ {627, 795, 341, 445}, /* Version 19: M=627, L=795, H=341, Q=445 */
+ {669, 861, 385, 485}, /* Version 20: M=669, L=861, H=385, Q=485 */
+ {714, 932, 406, 512}, /* Version 21: M=714, L=932, H=406, Q=512 */
+ {782, 1006, 442, 568}, /* Version 22: M=782, L=1006, H=442, Q=568 */
+ {860, 1094, 464, 614}, /* Version 23: M=860, L=1094, H=464, Q=614 */
+ {914, 1174, 514, 664}, /* Version 24: M=914, L=1174, H=514, Q=664 */
+ {1000, 1276, 538, 718}, /* Version 25: M=1000, L=1276, H=538, Q=718 */
+ {1062, 1370, 596, 754}, /* Version 26: M=1062, L=1370, H=596, Q=754 */
+ {1128, 1468, 628, 808}, /* Version 27: M=1128, L=1468, H=628, Q=808 */
+ {1193, 1531, 661, 871}, /* Version 28: M=1193, L=1531, H=661, Q=871 */
+ {1267, 1631, 701, 911}, /* Version 29: M=1267, L=1631, H=701, Q=911 */
+ {1373, 1735, 745, 985}, /* Version 30: M=1373, L=1735, H=745, Q=985 */
+ {1455, 1843, 793, 1033}, /* Version 31: M=1455, L=1843, H=793, Q=1033 */
+ {1541, 1955, 845, 1115}, /* Version 32: M=1541, L=1955, H=845, Q=1115 */
+ {1631, 2071, 901, 1171}, /* Version 33: M=1631, L=2071, H=901, Q=1171 */
+ {1725, 2191, 961, 1231}, /* Version 34: M=1725, L=2191, H=961, Q=1231 */
+ {1812, 2306, 986, 1286}, /* Version 35: M=1812, L=2306, H=986, Q=1286 */
+ {1914, 2434, 1054, 1354}, /* Version 36: M=1914, L=2434, H=1054, Q=1354 */
+ {1992, 2566, 1096, 1426}, /* Version 37: M=1992, L=2566, H=1096, Q=1426 */
+ {2102, 2702, 1142, 1502}, /* Version 38: M=2102, L=2702, H=1142, Q=1502 */
+ {2216, 2812, 1222, 1582}, /* Version 39: M=2216, L=2812, H=1222, Q=1582 */
+ {2334, 2956, 1276, 1666}, /* Version 40: M=2334, L=2956, H=1276, Q=1666 */
+};
+
+/* Reed-Solomon block parameters for deinterleaving
+ * bs = block size (total codewords), dw = data codewords, ns = number of short blocks
+ * Some versions have short and long blocks (long blocks have 1 extra data codeword)
+ */
+typedef struct {
+ int bs; /* Block size (total codewords per block) */
+ int dw; /* Data codewords per block */
+ int ns; /* Number of blocks of this size */
+} RSBlockParams;
+
+typedef struct {
+ int data_bytes; /* Total data codewords */
+ RSBlockParams ecc[4]; /* ECC params for M, L, H, Q (indexed by format bits) */
+} VersionInfo;
+
+/* Version database - indexed by [version][ecc_level] where ecc_level is format bits:
+ * 0=M, 1=L, 2=H, 3=Q */
+static const VersionInfo version_info[41] = {
+ {0}, /* Version 0 (invalid) */
+ { /* Version 1 */
+ .data_bytes = 26,
+ .ecc = {
+ {.bs = 26, .dw = 16, .ns = 1}, /* M */
+ {.bs = 26, .dw = 19, .ns = 1}, /* L */
+ {.bs = 26, .dw = 9, .ns = 1}, /* H */
+ {.bs = 26, .dw = 13, .ns = 1} /* Q */
+ }
+ },
+ { /* Version 2 */
+ .data_bytes = 44,
+ .ecc = {
+ {.bs = 44, .dw = 28, .ns = 1},
+ {.bs = 44, .dw = 34, .ns = 1},
+ {.bs = 44, .dw = 16, .ns = 1},
+ {.bs = 44, .dw = 22, .ns = 1}
+ }
+ },
+ { /* Version 3 */
+ .data_bytes = 70,
+ .ecc = {
+ {.bs = 70, .dw = 44, .ns = 1},
+ {.bs = 70, .dw = 55, .ns = 1},
+ {.bs = 35, .dw = 13, .ns = 2},
+ {.bs = 35, .dw = 17, .ns = 2}
+ }
+ },
+ { /* Version 4 */
+ .data_bytes = 100,
+ .ecc = {
+ {.bs = 50, .dw = 32, .ns = 2},
+ {.bs = 100, .dw = 80, .ns = 1},
+ {.bs = 25, .dw = 9, .ns = 4},
+ {.bs = 50, .dw = 24, .ns = 2}
+ }
+ },
+ { /* Version 5 */
+ .data_bytes = 134,
+ .ecc = {
+ {.bs = 67, .dw = 43, .ns = 2},
+ {.bs = 134, .dw = 108, .ns = 1},
+ {.bs = 33, .dw = 11, .ns = 2},
+ {.bs = 33, .dw = 15, .ns = 2}
+ }
+ },
+ { /* Version 6 */
+ .data_bytes = 172,
+ .ecc = {
+ {.bs = 43, .dw = 27, .ns = 4},
+ {.bs = 86, .dw = 68, .ns = 2},
+ {.bs = 43, .dw = 15, .ns = 4},
+ {.bs = 43, .dw = 19, .ns = 4}
+ }
+ },
+ { /* Version 7 */
+ .data_bytes = 196,
+ .ecc = {
+ {.bs = 49, .dw = 31, .ns = 4},
+ {.bs = 98, .dw = 78, .ns = 2},
+ {.bs = 39, .dw = 13, .ns = 4},
+ {.bs = 32, .dw = 14, .ns = 2}
+ }
+ },
+ { /* Version 8 */
+ .data_bytes = 242,
+ .ecc = {
+ {.bs = 60, .dw = 38, .ns = 2},
+ {.bs = 121, .dw = 97, .ns = 2},
+ {.bs = 40, .dw = 14, .ns = 4},
+ {.bs = 40, .dw = 18, .ns = 4}
+ }
+ },
+ { /* Version 9 */
+ .data_bytes = 292,
+ .ecc = {
+ {.bs = 58, .dw = 36, .ns = 3},
+ {.bs = 146, .dw = 116, .ns = 2},
+ {.bs = 36, .dw = 12, .ns = 4},
+ {.bs = 36, .dw = 16, .ns = 4}
+ }
+ },
+ { /* Version 10 */
+ .data_bytes = 346,
+ .ecc = {
+ {.bs = 69, .dw = 43, .ns = 4},
+ {.bs = 86, .dw = 68, .ns = 2},
+ {.bs = 43, .dw = 15, .ns = 6},
+ {.bs = 43, .dw = 19, .ns = 6}
+ }
+ },
+ { /* Version 11 */
+ .data_bytes = 404,
+ .ecc = {
+ {.bs = 80, .dw = 50, .ns = 1},
+ {.bs = 101, .dw = 81, .ns = 4},
+ {.bs = 36, .dw = 12, .ns = 3},
+ {.bs = 50, .dw = 22, .ns = 4}
+ }
+ },
+ { /* Version 12 */
+ .data_bytes = 466,
+ .ecc = {
+ {.bs = 58, .dw = 36, .ns = 6},
+ {.bs = 116, .dw = 92, .ns = 2},
+ {.bs = 42, .dw = 14, .ns = 7},
+ {.bs = 46, .dw = 20, .ns = 4}
+ }
+ },
+ { /* Version 13 */
+ .data_bytes = 532,
+ .ecc = {
+ {.bs = 59, .dw = 37, .ns = 8},
+ {.bs = 133, .dw = 107, .ns = 4},
+ {.bs = 33, .dw = 11, .ns = 12},
+ {.bs = 44, .dw = 20, .ns = 8}
+ }
+ },
+ { /* Version 14 */
+ .data_bytes = 581,
+ .ecc = {
+ {.bs = 64, .dw = 40, .ns = 4},
+ {.bs = 145, .dw = 115, .ns = 3},
+ {.bs = 36, .dw = 12, .ns = 11},
+ {.bs = 36, .dw = 16, .ns = 11}
+ }
+ },
+ { /* Version 15 */
+ .data_bytes = 655,
+ .ecc = {
+ {.bs = 65, .dw = 41, .ns = 5},
+ {.bs = 109, .dw = 87, .ns = 5},
+ {.bs = 36, .dw = 12, .ns = 11},
+ {.bs = 54, .dw = 24, .ns = 5}
+ }
+ },
+ { /* Version 16 */
+ .data_bytes = 733,
+ .ecc = {
+ {.bs = 73, .dw = 45, .ns = 7},
+ {.bs = 122, .dw = 98, .ns = 5},
+ {.bs = 45, .dw = 15, .ns = 3},
+ {.bs = 43, .dw = 19, .ns = 15}
+ }
+ },
+ { /* Version 17 */
+ .data_bytes = 815,
+ .ecc = {
+ {.bs = 74, .dw = 46, .ns = 10},
+ {.bs = 135, .dw = 107, .ns = 1},
+ {.bs = 42, .dw = 14, .ns = 2},
+ {.bs = 50, .dw = 22, .ns = 1}
+ }
+ },
+ { /* Version 18 */
+ .data_bytes = 901,
+ .ecc = {
+ {.bs = 69, .dw = 43, .ns = 9},
+ {.bs = 150, .dw = 120, .ns = 5},
+ {.bs = 42, .dw = 14, .ns = 2},
+ {.bs = 50, .dw = 22, .ns = 17}
+ }
+ },
+ { /* Version 19 */
+ .data_bytes = 991,
+ .ecc = {
+ {.bs = 70, .dw = 44, .ns = 3},
+ {.bs = 141, .dw = 113, .ns = 3},
+ {.bs = 39, .dw = 13, .ns = 9},
+ {.bs = 47, .dw = 21, .ns = 17}
+ }
+ },
+ { /* Version 20 */
+ .data_bytes = 1085,
+ .ecc = {
+ {.bs = 67, .dw = 41, .ns = 3},
+ {.bs = 135, .dw = 107, .ns = 3},
+ {.bs = 43, .dw = 15, .ns = 15},
+ {.bs = 54, .dw = 24, .ns = 15}
+ }
+ },
+ { /* Versions 21-40: using simplified single-block approximation for now */
+ .data_bytes = 1156, .ecc = {{.bs = 68, .dw = 42, .ns = 17}, {.bs = 144, .dw = 116, .ns = 4}, {.bs = 46, .dw = 16, .ns = 19}, {.bs = 50, .dw = 22, .ns = 17}}
+ },
+ { .data_bytes = 1258, .ecc = {{.bs = 74, .dw = 46, .ns = 17}, {.bs = 139, .dw = 111, .ns = 2}, {.bs = 37, .dw = 13, .ns = 34}, {.bs = 54, .dw = 24, .ns = 7}} },
+ { .data_bytes = 1364, .ecc = {{.bs = 75, .dw = 47, .ns = 4}, {.bs = 151, .dw = 121, .ns = 4}, {.bs = 45, .dw = 15, .ns = 16}, {.bs = 54, .dw = 24, .ns = 11}} },
+ { .data_bytes = 1474, .ecc = {{.bs = 73, .dw = 45, .ns = 6}, {.bs = 147, .dw = 117, .ns = 6}, {.bs = 46, .dw = 16, .ns = 30}, {.bs = 54, .dw = 24, .ns = 11}} },
+ { .data_bytes = 1588, .ecc = {{.bs = 75, .dw = 47, .ns = 8}, {.bs = 132, .dw = 106, .ns = 8}, {.bs = 45, .dw = 15, .ns = 22}, {.bs = 54, .dw = 24, .ns = 7}} },
+ { .data_bytes = 1706, .ecc = {{.bs = 74, .dw = 46, .ns = 19}, {.bs = 142, .dw = 114, .ns = 10}, {.bs = 46, .dw = 16, .ns = 33}, {.bs = 50, .dw = 22, .ns = 28}} },
+ { .data_bytes = 1828, .ecc = {{.bs = 73, .dw = 45, .ns = 22}, {.bs = 152, .dw = 122, .ns = 8}, {.bs = 45, .dw = 15, .ns = 12}, {.bs = 53, .dw = 23, .ns = 8}} },
+ { .data_bytes = 1921, .ecc = {{.bs = 73, .dw = 45, .ns = 3}, {.bs = 147, .dw = 117, .ns = 3}, {.bs = 45, .dw = 15, .ns = 11}, {.bs = 54, .dw = 24, .ns = 4}} },
+ { .data_bytes = 2051, .ecc = {{.bs = 73, .dw = 45, .ns = 21}, {.bs = 146, .dw = 116, .ns = 7}, {.bs = 45, .dw = 15, .ns = 19}, {.bs = 53, .dw = 23, .ns = 1}} },
+ { .data_bytes = 2185, .ecc = {{.bs = 75, .dw = 47, .ns = 19}, {.bs = 145, .dw = 115, .ns = 5}, {.bs = 45, .dw = 15, .ns = 23}, {.bs = 54, .dw = 24, .ns = 15}} },
+ { .data_bytes = 2323, .ecc = {{.bs = 74, .dw = 46, .ns = 2}, {.bs = 145, .dw = 115, .ns = 13}, {.bs = 45, .dw = 15, .ns = 23}, {.bs = 54, .dw = 24, .ns = 42}} },
+ { .data_bytes = 2465, .ecc = {{.bs = 74, .dw = 46, .ns = 10}, {.bs = 145, .dw = 115, .ns = 17}, {.bs = 45, .dw = 15, .ns = 19}, {.bs = 54, .dw = 24, .ns = 10}} },
+ { .data_bytes = 2611, .ecc = {{.bs = 74, .dw = 46, .ns = 14}, {.bs = 145, .dw = 115, .ns = 17}, {.bs = 45, .dw = 15, .ns = 11}, {.bs = 54, .dw = 24, .ns = 29}} },
+ { .data_bytes = 2761, .ecc = {{.bs = 74, .dw = 46, .ns = 14}, {.bs = 145, .dw = 115, .ns = 13}, {.bs = 46, .dw = 16, .ns = 59}, {.bs = 54, .dw = 24, .ns = 44}} },
+ { .data_bytes = 2876, .ecc = {{.bs = 75, .dw = 47, .ns = 12}, {.bs = 151, .dw = 121, .ns = 12}, {.bs = 45, .dw = 15, .ns = 22}, {.bs = 54, .dw = 24, .ns = 39}} },
+ { .data_bytes = 3034, .ecc = {{.bs = 75, .dw = 47, .ns = 6}, {.bs = 151, .dw = 121, .ns = 6}, {.bs = 45, .dw = 15, .ns = 2}, {.bs = 54, .dw = 24, .ns = 46}} },
+ { .data_bytes = 3196, .ecc = {{.bs = 74, .dw = 46, .ns = 29}, {.bs = 152, .dw = 122, .ns = 17}, {.bs = 45, .dw = 15, .ns = 24}, {.bs = 54, .dw = 24, .ns = 49}} },
+ { .data_bytes = 3362, .ecc = {{.bs = 74, .dw = 46, .ns = 13}, {.bs = 152, .dw = 122, .ns = 4}, {.bs = 45, .dw = 15, .ns = 42}, {.bs = 54, .dw = 24, .ns = 48}} },
+ { .data_bytes = 3532, .ecc = {{.bs = 75, .dw = 47, .ns = 40}, {.bs = 147, .dw = 117, .ns = 20}, {.bs = 45, .dw = 15, .ns = 10}, {.bs = 54, .dw = 24, .ns = 43}} },
+ { .data_bytes = 3706, .ecc = {{.bs = 75, .dw = 47, .ns = 18}, {.bs = 148, .dw = 118, .ns = 19}, {.bs = 45, .dw = 15, .ns = 20}, {.bs = 54, .dw = 24, .ns = 34}} },
+};
+
+/* Deinterleave a single block's codewords from interleaved raw data
+ * block_idx: which block (0 to bc-1)
+ * is_long: whether this is a long block (has dw+1 data codewords)
+ * Returns total block size (data + ecc)
+ */
+static int deinterleave_block(uint8_t *raw, int total_bytes, int bc, int dw, int bs,
+ int ns, int block_idx, uint8_t *block_out) {
+ int is_long = (block_idx >= ns);
+ int block_dw = is_long ? dw + 1 : dw;
+ int block_ecc = bs - dw;
+ int block_bs = is_long ? bs + 1 : bs;
+ int pos = 0;
+
+ /* Extract data codewords */
+ for (int j = 0; j < block_dw; j++) {
+ int src_idx;
+ if (j < dw) {
+ /* Regular interleaved position */
+ src_idx = j * bc + block_idx;
+ } else {
+ /* Extra codeword from long block */
+ src_idx = dw * bc + (block_idx - ns);
+ }
+ if (src_idx < total_bytes) {
+ block_out[pos++] = raw[src_idx];
+ }
+ }
+
+ /* Extract ECC codewords - they follow data in the interleaved stream */
+ int data_total = dw * bc + (bc - ns); /* Total data codewords */
+ for (int j = 0; j < block_ecc; j++) {
+ int src_idx = data_total + j * bc + block_idx;
+ if (src_idx < total_bytes) {
+ block_out[pos++] = raw[src_idx];
+ }
+ }
+
+ return block_bs;
+}
+
+/* Deinterleave codewords with RS error correction
+ * QR codes interleave data and ECC codewords across multiple blocks.
+ * This function:
+ * 1. Deinterleaves each block separately
+ * 2. Applies RS error correction to each block
+ * 3. Extracts just the corrected data codewords
+ *
+ * Returns number of data bytes extracted, or -1 if uncorrectable errors.
+ */
+static int deinterleave_and_correct(uint8_t *raw, int total_bytes, int version, int ecc_level,
+ uint8_t *data_out, int max_out, int *errors_corrected,
+ int *uncorrectable_out) {
+ if (version < 1 || version > 40) return 0;
+
+ const RSBlockParams *sb = &version_info[version].ecc[ecc_level];
+ int ns = sb->ns; /* Number of short blocks */
+ int dw = sb->dw; /* Data codewords per short block */
+ int bs = sb->bs; /* Total codewords per short block */
+
+ /* Calculate number of long blocks */
+ int short_total = ns * bs;
+ int remaining = total_bytes - short_total;
+ int nl = (remaining > 0) ? remaining / (bs + 1) : 0;
+ int bc = ns + nl; /* Total block count */
+ int block_ecc = bs - dw; /* ECC codewords per block */
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " RS correct: version=%d ecc=%d ns=%d nl=%d dw=%d bs=%d ecc_per_block=%d\n", // DEBUG
+ version, ecc_level, ns, nl, dw, bs, block_ecc); // DEBUG
+ } // DEBUG
+
+ int data_pos = 0;
+ int total_errors = 0;
+ int uncorrectable_blocks = 0;
+
+ /* Process each block */
+ for (int i = 0; i < bc && data_pos < max_out; i++) {
+ uint8_t block[256];
+ (void)deinterleave_block(raw, total_bytes, bc, dw, bs, ns, i, block);
+
+ int is_long = (i >= ns);
+ int block_dw = is_long ? dw + 1 : dw;
+
+ /* Apply RS error correction */
+ int corrected = rs_correct_errors(block, block_dw, block_ecc);
+
+ if (corrected < 0) {
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " Block %d: uncorrectable errors\n", i); // DEBUG
+ } // DEBUG
+ uncorrectable_blocks++;
+ /* Still copy the data but note the failure */
+ } else if (corrected > 0) {
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " Block %d: corrected %d errors\n", i, corrected); // DEBUG
+ } // DEBUG
+ total_errors += corrected;
+ }
+
+ /* Copy corrected data codewords to output */
+ for (int j = 0; j < block_dw && data_pos < max_out; j++) {
+ data_out[data_pos++] = block[j];
+ }
+ }
+
+ if (errors_corrected) *errors_corrected = total_errors;
+ if (uncorrectable_out) *uncorrectable_out = uncorrectable_blocks;
+
+ /* If ALL blocks had uncorrectable errors, the data is garbage - reject it */
+ if (uncorrectable_blocks == bc) {
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " RS: all %d blocks uncorrectable, rejecting\n", bc); // DEBUG
+ } // DEBUG
+ return -1;
+ }
+
+ return data_pos;
+}
+
+/* Extract data from QR code modules (in bit order) */
+static int extract_qr_data(QRCode *qr, uint8_t *data, int max_len) {
+ int size = qr->size;
+ int byte_index = 0;
+ int bit_index = 7;
+ int going_up = 1;
+ int total_bits = 0;
+
+ memset(data, 0, max_len);
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " extract_qr_data: first 32 bits from positions:\n "); // DEBUG
+ } // DEBUG
+
+ /* Read data in the zigzag pattern */
+ for (int col = size - 1; col >= 0; col -= 2) {
+ if (col == 6) col--; /* Skip timing pattern column */
+ if (col < 0) break;
+
+ int rows = going_up ? size - 1 : 0;
+ int end = going_up ? -1 : size;
+ int step = going_up ? -1 : 1;
+
+ for (int row = rows; row != end; row += step) {
+ for (int c = 0; c < 2; c++) {
+ int x = col - c;
+ if (x < 0) continue;
+
+ if (!is_function_module(qr->version, x, row)) {
+ if (byte_index < max_len) {
+ int bit = qr->modules[row][x];
+ if (debug_finder && total_bits < 32) { // DEBUG
+ fprintf(stderr, "(%d,%d)=%d ", x, row, bit);
+ if (total_bits % 8 == 7) fprintf(stderr, "\n ");
+ }
+ (void)0; /* Placeholder - debug removed */
+ if (bit) {
+ data[byte_index] |= (1 << bit_index);
+ }
+ bit_index--;
+ if (bit_index < 0) {
+ bit_index = 7;
+ byte_index++;
+ }
+ total_bits++;
+ }
+ }
+ }
+ }
+ going_up = !going_up;
+ }
+
+ if (debug_finder) fprintf(stderr, "\n"); // DEBUG
+
+ return byte_index + (bit_index < 7 ? 1 : 0);
+}
+
+/* Decode QR data content */
+static int decode_qr_content(uint8_t *data, int data_len, int version, char *output, int max_output) {
+ int bit_pos = 0;
+ int out_pos = 0;
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " decode_qr_content: data_len=%d version=%d first bytes: %02x %02x %02x %02x\n", // DEBUG
+ data_len, version, data[0], data[1], data[2], data[3]); // DEBUG
+ } // DEBUG
+
+ /* Read bits helper */
+ #define READ_BITS(n) ({ \
+ int val = 0; \
+ for (int i = 0; i < (n); i++) { \
+ int byte_idx = bit_pos / 8; \
+ int bit_idx = 7 - (bit_pos % 8); \
+ if (byte_idx < data_len) { \
+ val = (val << 1) | ((data[byte_idx] >> bit_idx) & 1); \
+ } \
+ bit_pos++; \
+ } \
+ val; \
+ })
+
+ while (bit_pos < data_len * 8 && out_pos < max_output - 1) {
+ int mode = READ_BITS(4);
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " mode=%d at bit_pos=%d\n", mode, bit_pos - 4); // DEBUG
+ } // DEBUG
+ if (mode == 0) break; /* Terminator */
+
+ /* Determine character count length based on version and mode */
+ int count_bits;
+
+ if (mode == 1) { /* Numeric */
+ count_bits = version <= 9 ? 10 : (version <= 26 ? 12 : 14);
+ int count = READ_BITS(count_bits);
+
+ while (count >= 3 && out_pos < max_output - 3) {
+ int val = READ_BITS(10);
+ output[out_pos++] = '0' + val / 100;
+ output[out_pos++] = '0' + (val / 10) % 10;
+ output[out_pos++] = '0' + val % 10;
+ count -= 3;
+ }
+ if (count == 2 && out_pos < max_output - 2) {
+ int val = READ_BITS(7);
+ output[out_pos++] = '0' + val / 10;
+ output[out_pos++] = '0' + val % 10;
+ } else if (count == 1 && out_pos < max_output - 1) {
+ int val = READ_BITS(4);
+ output[out_pos++] = '0' + val;
+ }
+ } else if (mode == 2) { /* Alphanumeric */
+ static const char alphanumeric[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
+ count_bits = version <= 9 ? 9 : (version <= 26 ? 11 : 13);
+ int count = READ_BITS(count_bits);
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " alphanumeric mode: count_bits=%d count=%d\n", count_bits, count); // DEBUG
+ } // DEBUG
+
+ int pair_idx = 0;
+ while (count >= 2 && out_pos < max_output - 2) {
+ int val = READ_BITS(11);
+ if (debug_finder && pair_idx < 5) { // DEBUG
+ fprintf(stderr, " alpha pair[%d] val=%d -> '%c%c'\n", pair_idx, val,
+ alphanumeric[val / 45], alphanumeric[val % 45]);
+ pair_idx++;
+ }
+ output[out_pos++] = alphanumeric[val / 45];
+ output[out_pos++] = alphanumeric[val % 45];
+ count -= 2;
+ }
+ if (count == 1 && out_pos < max_output - 1) {
+ int val = READ_BITS(6);
+ output[out_pos++] = alphanumeric[val];
+ }
+ } else if (mode == 4) { /* Byte mode */
+ count_bits = version <= 9 ? 8 : 16;
+ int count = READ_BITS(count_bits);
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " byte mode: count_bits=%d count=%d\n", count_bits, count); // DEBUG
+ } // DEBUG
+
+ for (int i = 0; i < count && out_pos < max_output - 1; i++) {
+ int start_bit = bit_pos;
+ int c = READ_BITS(8);
+ if (debug_finder && i >= count - 3) { // DEBUG
+ /* Show last 3 bytes in detail with actual bit pattern */
+ int byte1 = start_bit / 8;
+ int byte2 = (start_bit + 7) / 8;
+ fprintf(stderr, " byte[%d] = 0x%02x '%c' (bits %d-%d from bytes %d-%d: 0x%02x 0x%02x)\n",
+ i, c, (c >= 32 && c < 127) ? c : '?',
+ start_bit, start_bit + 7, byte1, byte2,
+ byte1 < data_len ? data[byte1] : 0,
+ byte2 < data_len ? data[byte2] : 0);
+ }
+ output[out_pos++] = c;
+ }
+ } else {
+ break; /* Unsupported mode */
+ }
+ }
+
+ output[out_pos] = '\0';
+ return out_pos;
+
+ #undef READ_BITS
+}
+
+/* Calculate distance squared between two points */
+static int distance_sq(int x1, int y1, int x2, int y2) {
+ int dx = x2 - x1;
+ int dy = y2 - y1;
+ return dx * dx + dy * dy;
+}
+
+/* Calculate cross product (determines orientation) */
+static int cross_product(int x1, int y1, int x2, int y2, int x3, int y3) {
+ return (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1);
+}
+
+/* Identify the three finder patterns as top-left, top-right, bottom-left */
+STATIC int identify_finder_roles(FinderPattern *fp, int *tl, int *tr, int *bl) {
+ /* The top-left finder is opposite the longest side (hypotenuse) */
+ int d01 = distance_sq(fp[0].x, fp[0].y, fp[1].x, fp[1].y);
+ int d02 = distance_sq(fp[0].x, fp[0].y, fp[2].x, fp[2].y);
+ int d12 = distance_sq(fp[1].x, fp[1].y, fp[2].x, fp[2].y);
+
+ int corner; /* The corner opposite the longest side (top-left) */
+ int p1, p2; /* The other two corners */
+
+ if (d01 >= d02 && d01 >= d12) {
+ corner = 2; p1 = 0; p2 = 1;
+ } else if (d02 >= d01 && d02 >= d12) {
+ corner = 1; p1 = 0; p2 = 2;
+ } else {
+ corner = 0; p1 = 1; p2 = 2;
+ }
+
+ *tl = corner;
+
+ /* Use cross product to determine which is top-right vs bottom-left */
+ int cp = cross_product(fp[corner].x, fp[corner].y,
+ fp[p1].x, fp[p1].y,
+ fp[p2].x, fp[p2].y);
+
+ if (cp > 0) {
+ *tr = p1;
+ *bl = p2;
+ } else {
+ *tr = p2;
+ *bl = p1;
+ }
+
+ return 1;
+}
+
+/* Gaussian elimination with partial pivoting */
+static int gaussian_eliminate(float *A, float *b, float *x, int n) {
+ /* Combine A|b into augmented matrix */
+ float *aug = malloc((n * (n + 1)) * sizeof(float));
+ if (!aug) return 0;
+
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < n; j++) {
+ aug[i * (n + 1) + j] = A[i * n + j];
+ }
+ aug[i * (n + 1) + n] = b[i];
+ }
+
+ /* Forward elimination with partial pivoting */
+ for (int k = 0; k < n - 1; k++) {
+ /* Find pivot */
+ int max_row = k;
+ float max_val = fabs(aug[k * (n + 1) + k]);
+ for (int i = k + 1; i < n; i++) {
+ float val = fabs(aug[i * (n + 1) + k]);
+ if (val > max_val) {
+ max_val = val;
+ max_row = i;
+ }
+ }
+
+ /* Swap rows if needed */
+ if (max_row != k) {
+ for (int j = k; j <= n; j++) {
+ float temp = aug[k * (n + 1) + j];
+ aug[k * (n + 1) + j] = aug[max_row * (n + 1) + j];
+ aug[max_row * (n + 1) + j] = temp;
+ }
+ }
+
+ /* Check for singularity */
+ if (fabs(aug[k * (n + 1) + k]) < 1e-10) {
+ free(aug);
+ return 0;
+ }
+
+ /* Eliminate below */
+ for (int i = k + 1; i < n; i++) {
+ float factor = aug[i * (n + 1) + k] / aug[k * (n + 1) + k];
+ aug[i * (n + 1) + k] = 0.0;
+ for (int j = k + 1; j <= n; j++) {
+ aug[i * (n + 1) + j] -= factor * aug[k * (n + 1) + j];
+ }
+ }
+ }
+
+ /* Back substitution */
+ for (int i = n - 1; i >= 0; i--) {
+ float sum = aug[i * (n + 1) + n];
+ for (int j = i + 1; j < n; j++) {
+ sum -= aug[i * (n + 1) + j] * x[j];
+ }
+ if (fabs(aug[i * (n + 1) + i]) < 1e-10) {
+ free(aug);
+ return 0;
+ }
+ x[i] = sum / aug[i * (n + 1) + i];
+ }
+
+ free(aug);
+ return 1;
+}
+
+/* Calculate homography matrix from 4 point correspondences */
+static int calculate_homography(Point2D src[4], Point2D dst[4], HomographyMatrix *H) {
+ /* For 4 point pairs, we need to solve an 8x8 system */
+ float A[64]; /* 8x8 matrix */
+ float b[8]; /* Right hand side */
+ float x[8]; /* Solution */
+
+ /* Fill matrices */
+ for (int i = 0; i < 4; i++) {
+ /* Each point gives two rows */
+ int row1 = i * 2;
+ int row2 = i * 2 + 1;
+
+ /* First row: x equation */
+ A[row1 * 8 + 0] = src[i].x;
+ A[row1 * 8 + 1] = src[i].y;
+ A[row1 * 8 + 2] = 1;
+ A[row1 * 8 + 3] = 0;
+ A[row1 * 8 + 4] = 0;
+ A[row1 * 8 + 5] = 0;
+ A[row1 * 8 + 6] = -src[i].x * dst[i].x;
+ A[row1 * 8 + 7] = -src[i].y * dst[i].x;
+ b[row1] = dst[i].x;
+
+ /* Second row: y equation */
+ A[row2 * 8 + 0] = 0;
+ A[row2 * 8 + 1] = 0;
+ A[row2 * 8 + 2] = 0;
+ A[row2 * 8 + 3] = src[i].x;
+ A[row2 * 8 + 4] = src[i].y;
+ A[row2 * 8 + 5] = 1;
+ A[row2 * 8 + 6] = -src[i].x * dst[i].y;
+ A[row2 * 8 + 7] = -src[i].y * dst[i].y;
+ b[row2] = dst[i].y;
+ }
+
+ /* Solve the system */
+ if (!gaussian_eliminate(A, b, x, 8)) {
+ return 0;
+ }
+
+ /* Fill homography matrix */
+ H->h[0][0] = x[0];
+ H->h[0][1] = x[1];
+ H->h[0][2] = x[2];
+ H->h[1][0] = x[3];
+ H->h[1][1] = x[4];
+ H->h[1][2] = x[5];
+ H->h[2][0] = x[6];
+ H->h[2][1] = x[7];
+ H->h[2][2] = 1.0f;
+
+ return 1;
+}
+
+/* Apply homography transform to a point */
+static Point2D apply_homography(const HomographyMatrix *H, float x, float y) {
+ Point2D result;
+ float w = H->h[2][0] * x + H->h[2][1] * y + H->h[2][2];
+
+ /* Avoid division by zero */
+ if (fabs(w) < 1e-10) w = 1e-10;
+
+ result.x = (H->h[0][0] * x + H->h[0][1] * y + H->h[0][2]) / w;
+ result.y = (H->h[1][0] * x + H->h[1][1] * y + H->h[1][2]) / w;
+
+ return result;
+}
+
+/* Refine finder center by searching for the center of the inner black region.
+ * The finder pattern has a 3x3 black center that we can use to find the true center.
+ * This is important for rotated codes where the initial line-scan estimate may be biased. */
+static void refine_finder_center(Image *img, int *px, int *py, float module_size) {
+ int x = *px, y = *py;
+ int search_radius = (int)(module_size * 2 + 0.5f);
+ if (search_radius < 5) search_radius = 5;
+ if (search_radius > 20) search_radius = 20;
+
+ /* Find the center of the black region near the initial estimate */
+ int best_x = x, best_y = y;
+ int max_black_count = 0;
+
+ for (int dy = -search_radius; dy <= search_radius; dy++) {
+ for (int dx = -search_radius; dx <= search_radius; dx++) {
+ int cx = x + dx, cy = y + dy;
+
+ /* Count black pixels in a small window around this candidate center */
+ int black_count = 0;
+ int window = (int)(module_size * 1.5f);
+ if (window < 3) window = 3;
+
+ for (int wy = -window; wy <= window; wy++) {
+ for (int wx = -window; wx <= window; wx++) {
+ if (image_get(img, cx + wx, cy + wy)) {
+ black_count++;
+ }
+ }
+ }
+
+ /* Prefer the candidate that's closest to our initial estimate when black counts are similar */
+ int dist_sq = dx*dx + dy*dy;
+ int score = black_count * 100 - dist_sq;
+
+ if (black_count > max_black_count ||
+ (black_count == max_black_count && dist_sq < (best_x - x)*(best_x - x) + (best_y - y)*(best_y - y))) {
+ max_black_count = black_count;
+ best_x = cx;
+ best_y = cy;
+ }
+ }
+ }
+
+ *px = best_x;
+ *py = best_y;
+}
+
+/* Calculate QR transform from finder patterns.
+ * If version_override > 0, use that version instead of computing from geometry. */
+static QRTransform calculate_qr_transform(FinderPattern *fp, int tl, int tr, int bl, int version_override) {
+ QRTransform transform;
+ memset(&transform, 0, sizeof(transform));
+
+ /* Store finder pattern positions (may be refined below) */
+ transform.tl_x = fp[tl].x;
+ transform.tl_y = fp[tl].y;
+ transform.tr_x = fp[tr].x;
+ transform.tr_y = fp[tr].y;
+ transform.bl_x = fp[bl].x;
+ transform.bl_y = fp[bl].y;
+
+ /* Calculate distances between finders */
+ float dx_tr = fp[tr].x - fp[tl].x;
+ float dy_tr = fp[tr].y - fp[tl].y;
+ float dx_bl = fp[bl].x - fp[tl].x;
+ float dy_bl = fp[bl].y - fp[tl].y;
+
+ float dist_tr = fsqrt(dx_tr*dx_tr + dy_tr*dy_tr);
+ float dist_bl = fsqrt(dx_bl*dx_bl + dy_bl*dy_bl);
+
+ /* Calculate module sizes - geometric mean of 3 values for overall scaling */
+ float geo_module = cbrtf(fp[tl].module_size * fp[tr].module_size * fp[bl].module_size);
+ if (!geo_module) geo_module = 1.0f; /* Avoid zero division */
+
+ /* Use TL finder module size for corner calculation since TL finder is the reference point */
+ float tl_module = fp[tl].module_size;
+
+ /* Calculate rotation angle from top-left to top-right */
+ transform.angle = atan2(dy_tr, dx_tr);
+
+ /* Calculate QR size from finder distances.
+ * The distance between finder centers is (size - 7) modules.
+ *
+ * For skewed/stretched images, we need to use directional module sizes.
+ * dist_tr (TL->TR) is primarily horizontal, so use module_size_x
+ * dist_bl (TL->BL) is primarily vertical, so use module_size_y
+ *
+ * For rotated images, we also correct for the angle-based stretch. */
+
+ /* Get average directional module sizes */
+ float avg_module_x = (fp[tl].module_size_x + fp[tr].module_size_x + fp[bl].module_size_x) / 3.0f;
+ float avg_module_y = (fp[tl].module_size_y + fp[tr].module_size_y + fp[bl].module_size_y) / 3.0f;
+ if (avg_module_x < 1.0f) avg_module_x = geo_module;
+ if (avg_module_y < 1.0f) avg_module_y = geo_module;
+
+ /* Calculate the angle-based stretch correction.
+ * For rotated patterns, the scanned module size is inflated because
+ * horizontal/vertical scanlines cut across modules at an angle. */
+ float angle_abs = fabsf(transform.angle);
+ /* Normalize to 0-45° range to find minimum stretch factor */
+ while (angle_abs > 0.7854f) angle_abs -= 1.5708f; /* M_PI_4, M_PI_2 */
+ angle_abs = fabsf(angle_abs);
+ float stretch_factor = 1.0f / cosf(angle_abs);
+ if (stretch_factor < 1.0f) stretch_factor = 1.0f; /* Safety clamp */
+
+ /* Correct the directional module sizes for rotation */
+ float corrected_module_x = avg_module_x / stretch_factor;
+ float corrected_module_y = avg_module_y / stretch_factor;
+
+ /* Calculate module count using appropriate directional modules.
+ * dist_tr is mostly horizontal -> use module_x
+ * dist_bl is mostly vertical -> use module_y */
+ float modules_tr = dist_tr / corrected_module_x;
+ float modules_bl = dist_bl / corrected_module_y;
+ float modules_est = (modules_tr + modules_bl) / 2.0f;
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " version calc: dist_tr=%.1f dist_bl=%.1f mod_x=%.1f mod_y=%.1f angle=%.1f° stretch=%.2f modules_tr=%.1f modules_bl=%.1f modules_est=%.1f\n", // DEBUG
+ dist_tr, dist_bl, avg_module_x, avg_module_y, transform.angle * 57.2958f, stretch_factor, modules_tr, modules_bl, modules_est); // DEBUG
+ } // DEBUG
+
+ /* Add 7 modules for finder patterns and round to valid QR size */
+ int qr_modules = (int)(modules_est + 7.5f);
+
+ /* Round to nearest valid QR size: size = 17 + 4*version */
+ int version;
+ if (version_override > 0) {
+ /* Use the override version for brute-force search */
+ version = version_override;
+ } else {
+ version = (qr_modules - 17 + 2) / 4;
+ if (version < 1) version = 1;
+ if (version > 40) version = 40;
+ }
+
+ int size = 17 + 4 * version;
+
+ /* Store QR code properties */
+ transform.version = version;
+ transform.size = size;
+ transform.module_size = geo_module;
+
+ /* Calculate TRUE module size from finder-to-finder geometry using geometric mean of distances */
+ float avg_dist = sqrtf(dist_tr * dist_bl);
+ float true_module = avg_dist / (size - 7);
+
+ /* Calculate scaling factors */
+ transform.scale_x = dist_tr / ((size - 7) * true_module);
+ transform.scale_y = dist_bl / ((size - 7) * true_module);
+
+ /* Calculate QR code corners using all three finder positions for better accuracy.
+ *
+ * In module coordinates:
+ * - TL finder is at (3.5, 3.5)
+ * - TR finder is at (size-3.5, 3.5)
+ * - BL finder is at (3.5, size-3.5)
+ *
+ * We use the finder-to-finder vectors to determine the QR orientation and scale,
+ * then compute corners from the centroid of all three finders for robustness. */
+
+ /* Finder positions in module coordinates */
+ float tl_mx = 3.5f, tl_my = 3.5f;
+ float tr_mx = size - 3.5f, tr_my = 3.5f;
+ float bl_mx = 3.5f, bl_my = size - 3.5f;
+
+ /* Compute QR corners using affine transformation derived from the three finder positions.
+ *
+ * We have three known correspondences:
+ * module (tl_mx, tl_my) -> image (fp[tl].x, fp[tl].y)
+ * module (tr_mx, tr_my) -> image (fp[tr].x, fp[tr].y)
+ * module (bl_mx, bl_my) -> image (fp[bl].x, fp[bl].y)
+ *
+ * An affine transform is: [x] [a b tx] [mx]
+ * [y] = [c d ty] [my]
+ * [1 ]
+ *
+ * We solve for a, b, c, d, tx, ty using the three points.
+ *
+ * From the module-to-image mapping:
+ * fp[tl].x = a * tl_mx + b * tl_my + tx
+ * fp[tl].y = c * tl_mx + d * tl_my + ty
+ * fp[tr].x = a * tr_mx + b * tr_my + tx
+ * fp[tr].y = c * tr_mx + d * tr_my + ty
+ * fp[bl].x = a * bl_mx + b * bl_my + tx
+ * fp[bl].y = c * bl_mx + d * bl_my + ty
+ */
+
+ /* Use vector differences to eliminate tx, ty:
+ * (fp[tr] - fp[tl]) = a * (tr_mx - tl_mx) + b * (tr_my - tl_my) [for x]
+ * (fp[bl] - fp[tl]) = a * (bl_mx - tl_mx) + b * (bl_my - tl_my) [for x]
+ *
+ * Since tr_my = tl_my (both at y=3.5 in module space) and bl_mx = tl_mx:
+ * dx_tr = a * (tr_mx - tl_mx) => a = dx_tr / (tr_mx - tl_mx)
+ * dx_bl = b * (bl_my - tl_my) => b = dx_bl / (bl_my - tl_my)
+ * Similarly for c, d.
+ */
+
+ float module_span_x = tr_mx - tl_mx; /* size - 7 */
+ float module_span_y = bl_my - tl_my; /* size - 7 */
+
+ /* Affine transform coefficients */
+ float a = dx_tr / module_span_x;
+ float b = dx_bl / module_span_y;
+ float c = dy_tr / module_span_x;
+ float d = dy_bl / module_span_y;
+ float tx = fp[tl].x - a * tl_mx - b * tl_my;
+ float ty = fp[tl].y - c * tl_mx - d * tl_my;
+
+ /* Now compute QR corners using the affine transform */
+ /* QR corner at module (0, 0) */
+ transform.qr_tl_x = a * 0 + b * 0 + tx;
+ transform.qr_tl_y = c * 0 + d * 0 + ty;
+
+ /* QR corner at module (size, 0) */
+ transform.qr_tr_x = a * size + b * 0 + tx;
+ transform.qr_tr_y = c * size + d * 0 + ty;
+
+ /* QR corner at module (0, size) */
+ transform.qr_bl_x = a * 0 + b * size + tx;
+ transform.qr_bl_y = c * 0 + d * size + ty;
+
+ /* QR corner at module (size, size) */
+ transform.qr_br_x = a * size + b * size + tx;
+ transform.qr_br_y = c * size + d * size + ty;
+
+ /* Calculate homography matrix */
+ /* Source points in QR module space (normalized 0-1) */
+ Point2D src[4] = {
+ {0, 0}, /* Top-left */
+ {1, 0}, /* Top-right */
+ {0, 1}, /* Bottom-left */
+ {1, 1} /* Bottom-right */
+ };
+
+ /* Destination points in image space */
+ Point2D dst[4] = {
+ {transform.qr_tl_x, transform.qr_tl_y}, /* Top-left */
+ {transform.qr_tr_x, transform.qr_tr_y}, /* Top-right */
+ {transform.qr_bl_x, transform.qr_bl_y}, /* Bottom-left */
+ {transform.qr_br_x, transform.qr_br_y} /* Bottom-right */
+ };
+
+ /* Calculate the homography transform */
+ transform.use_homography = calculate_homography(src, dst, &transform.homography);
+
+ return transform;
+}
+
+/* Apply transform to sample module at position (mx, my)
+ * If use_antialiasing is true, uses weighted 3x3 majority vote for robustness.
+ * If false, samples only the center pixel (better for format info). */
+static int sample_module_transform_ex(Image *img, const QRTransform *transform, int mx, int my, int use_antialiasing) {
+ /* Calculate normalized coordinates in the QR code (0.0 to 1.0) */
+ float u = (mx + 0.5f) / transform->size;
+ float v = (my + 0.5f) / transform->size;
+
+ float px, py;
+
+ if (transform->use_homography) {
+ /* Use homography transform for more accurate mapping */
+ Point2D p = apply_homography(&transform->homography, u, v);
+ px = p.x;
+ py = p.y;
+ } else {
+ /* Fall back to bilinear interpolation between the four corners */
+ px = (1-u) * (1-v) * transform->qr_tl_x +
+ u * (1-v) * transform->qr_tr_x +
+ (1-u) * v * transform->qr_bl_x +
+ u * v * transform->qr_br_x;
+
+ py = (1-u) * (1-v) * transform->qr_tl_y +
+ u * (1-v) * transform->qr_tr_y +
+ (1-u) * v * transform->qr_bl_y +
+ u * v * transform->qr_br_y;
+ }
+
+ if (!use_antialiasing) {
+ /* Center-only sampling - more accurate for format info */
+ int ix = (int)(px + 0.5f);
+ int iy = (int)(py + 0.5f);
+ return image_get(img, ix, iy) ? 1 : 0;
+ }
+
+ /* Anti-aliasing sampling - check surrounding pixels and use majority vote
+ * This helps with rotated codes where the modules may not align with pixels */
+ int sample_radius = 1; /* Sample in a 3x3 area */
+ int black_count = 0;
+ int total_samples = 0;
+
+ for (int dy = -sample_radius; dy <= sample_radius; dy++) {
+ for (int dx = -sample_radius; dx <= sample_radius; dx++) {
+ /* Use weighted samples - center pixel counts more */
+ float weight = (dx == 0 && dy == 0) ? 2.0f : 1.0f;
+
+ int ix = (int)(px + dx + 0.5f);
+ int iy = (int)(py + dy + 0.5f);
+
+ if (image_get(img, ix, iy)) {
+ black_count += weight;
+ }
+ total_samples += weight;
+ }
+ }
+
+ /* Return 1 (black) if majority of weighted samples are black */
+ return (black_count * 2 > total_samples) ? 1 : 0;
+}
+
+/* Apply transform to sample module at position (mx, my) with anti-aliasing */
+static int sample_module_transform(Image *img, const QRTransform *transform, int mx, int my) {
+ return sample_module_transform_ex(img, transform, mx, my, 1);
+}
+
+/* Sample module using perspective-corrected coordinates with anti-aliasing
+ * Legacy function for compatibility - forwards to the new transform-based implementation */
+static int sample_module_perspective(Image *img,
+ float tl_x, float tl_y,
+ float tr_x, float tr_y,
+ float bl_x, float bl_y,
+ int size, int mx, int my) {
+ /* Create a temporary transform */
+ QRTransform temp;
+
+ /* Calculate bottom-right corner */
+ float br_x = tr_x + bl_x - tl_x;
+ float br_y = tr_y + bl_y - tl_y;
+
+ /* Fill in transform corners */
+ temp.qr_tl_x = tl_x;
+ temp.qr_tl_y = tl_y;
+ temp.qr_tr_x = tr_x;
+ temp.qr_tr_y = tr_y;
+ temp.qr_bl_x = bl_x;
+ temp.qr_bl_y = bl_y;
+ temp.qr_br_x = br_x;
+ temp.qr_br_y = br_y;
+ temp.size = size;
+
+ /* Use the centralized transform function */
+ return sample_module_transform(img, &temp, mx, my);
+}
+
+/* Detect and decode a single QR code from finder patterns */
+static int decode_qr_from_finders(Image *img, FinderPattern *fp, int nfp, char *output, int max_output) {
+ if (nfp < 3) {
+ if (debug_finder) fprintf(stderr, " decode_qr: nfp < 3\n"); // DEBUG
+ return 0;
+ }
+
+ /* Identify which finder is top-left, top-right, bottom-left */
+ int tl, tr, bl;
+ if (!identify_finder_roles(fp, &tl, &tr, &bl)) {
+ if (debug_finder) fprintf(stderr, " decode_qr: identify_finder_roles failed\n"); // DEBUG
+ return 0;
+ }
+ if (debug_finder) fprintf(stderr, " decode_qr: trying fp[%d,%d,%d] -> tl=%d tr=%d bl=%d\n", // DEBUG
+ fp[0].x, fp[0].y, fp[1].x, tl, tr, bl); // DEBUG
+
+ /* First, get the geometrically estimated version */
+ QRTransform initial_transform = calculate_qr_transform(fp, tl, tr, bl, 0);
+ int estimated_version = initial_transform.version;
+
+ /* Build a list of versions to try.
+ * For large QR codes (v25+), geometric estimation can be off by 1-2 due to
+ * sub-pixel module size measurement errors. We try nearby versions (±2) for these.
+ * Also for small module sizes (< 8 pixels), estimation is less accurate.
+ * For other codes, the geometric estimate is accurate enough - only use it. */
+ int versions_to_try[10]; /* At most: estimated + 4 nearby + potential fallback */
+ int num_versions = 0;
+
+ /* Always try the estimated version first */
+ versions_to_try[num_versions++] = estimated_version;
+
+ /* For large versions (v25+) or small module sizes (< 8px), also try nearby versions */
+ float module_size = initial_transform.module_size;
+ if (estimated_version >= 25 || module_size < 8.0f) {
+ for (int delta = -2; delta <= 2; delta++) {
+ int v = estimated_version + delta;
+ if (v >= 1 && v <= 40 && v != estimated_version) {
+ versions_to_try[num_versions++] = v;
+ }
+ }
+ }
+
+ if (debug_finder && num_versions > 1) { // DEBUG
+ fprintf(stderr, " Versions to try (%d): ", num_versions);
+ for (int i = 0; i < num_versions; i++) {
+ fprintf(stderr, "%d ", versions_to_try[i]);
+ }
+ fprintf(stderr, "\n");
+ }
+
+ /* Try each version until one succeeds */
+ for (int vi = 0; vi < num_versions; vi++) {
+ int try_version = versions_to_try[vi];
+
+ /* Calculate transform from finder patterns */
+ QRTransform transform = calculate_qr_transform(fp, tl, tr, bl, try_version);
+
+ /* Debugging output */
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " avg_modules=%.2f -> qr_modules=%d version=%d size=%d\n", // DEBUG
+ transform.size - 7.0f, transform.size, transform.version, transform.size); // DEBUG
+ fprintf(stderr, " derived: module_size=%.2f (x=%.2f y=%.2f)\n", // DEBUG
+ transform.module_size, transform.scale_x * transform.module_size, // DEBUG
+ transform.scale_y * transform.module_size); // DEBUG
+ fprintf(stderr, " finders: tl=(%d,%d) tr=(%d,%d) bl=(%d,%d)\n", // DEBUG
+ fp[tl].x, fp[tl].y, fp[tr].x, fp[tr].y, fp[bl].x, fp[bl].y); // DEBUG
+ fprintf(stderr, " corners: tl=(%.1f,%.1f) tr=(%.1f,%.1f) bl=(%.1f,%.1f)\n", // DEBUG
+ transform.qr_tl_x, transform.qr_tl_y, // DEBUG
+ transform.qr_tr_x, transform.qr_tr_y, // DEBUG
+ transform.qr_bl_x, transform.qr_bl_y); // DEBUG
+ // DEBUG
+ if (transform.use_homography) { // DEBUG
+ fprintf(stderr, " homography: enabled (using full projective transform)\n"); // DEBUG
+ fprintf(stderr, " homography matrix: [%.3f %.3f %.3f; %.3f %.3f %.3f; %.3f %.3f 1.000]\n", // DEBUG
+ transform.homography.h[0][0], transform.homography.h[0][1], transform.homography.h[0][2], // DEBUG
+ transform.homography.h[1][0], transform.homography.h[1][1], transform.homography.h[1][2], // DEBUG
+ transform.homography.h[2][0], transform.homography.h[2][1]); // DEBUG
+ } else { // DEBUG
+ fprintf(stderr, " homography: disabled (falling back to bilinear interpolation)\n"); // DEBUG
+ } // DEBUG
+ } // DEBUG
+
+ /* Create QR code structure */
+ QRCode qr;
+ qr.version = transform.version;
+ qr.size = transform.size;
+
+ /* Sanity check: all corners should be within image bounds (with margin) */
+ float margin = -5.0f; /* Allow slightly outside */
+ if (transform.qr_tl_x < margin || transform.qr_tl_y < margin ||
+ transform.qr_tr_x < margin || transform.qr_tr_y < margin ||
+ transform.qr_bl_x < margin || transform.qr_bl_y < margin ||
+ transform.qr_br_x < margin || transform.qr_br_y < margin ||
+ transform.qr_tl_x > img->width + 5 || transform.qr_tl_y > img->height + 5 ||
+ transform.qr_tr_x > img->width + 5 || transform.qr_tr_y > img->height + 5 ||
+ transform.qr_bl_x > img->width + 5 || transform.qr_bl_y > img->height + 5 ||
+ transform.qr_br_x > img->width + 5 || transform.qr_br_y > img->height + 5) {
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " Rejecting v%d: corners outside image bounds\n", try_version); // DEBUG
+ } // DEBUG
+ continue; /* Try next version */
+ }
+
+ /* Sample all modules using the transform */
+ for (int my = 0; my < transform.size; my++) {
+ for (int mx = 0; mx < transform.size; mx++) {
+ qr.modules[my][mx] = sample_module_transform(img, &transform, mx, my);
+ }
+ }
+
+ /* Debug: print sampled QR code */
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " Sampled QR (%dx%d):\n", transform.size, transform.size); // DEBUG
+ for (int my = 0; my < transform.size; my++) { // DEBUG
+ fprintf(stderr, " "); // DEBUG
+ for (int mx = 0; mx < transform.size; mx++) { // DEBUG
+ fprintf(stderr, "%c", qr.modules[my][mx] ? '#' : '.'); // DEBUG
+ } // DEBUG
+ fprintf(stderr, "\n"); // DEBUG
+ } // DEBUG
+ } // DEBUG
+
+ /* Debug dump: sampled grid */
+ debug_dump_grid(qr.modules, transform.size, transform.version, "sampled"); // DEBUG
+
+ /* Read format info directly from image without anti-aliasing.
+ * Format bits can fall on module boundaries where anti-aliasing causes errors. */
+ int format_info = read_format_info_direct(img, &transform);
+
+ /* Try to correct format info using BCH error correction */
+ int corrected_format = validate_format_info_with_correction(format_info);
+
+ /* If direct read fails (even with correction), try reading from pre-sampled grid */
+ if (corrected_format < 0) {
+ format_info = read_format_info(&qr);
+ corrected_format = validate_format_info_with_correction(format_info);
+ }
+
+ /* Reject if still invalid after correction attempts */
+ if (corrected_format < 0) {
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " v%d: Format info 0x%04x invalid (BCH check failed), trying next version\n", // DEBUG
+ try_version, format_info); // DEBUG
+ } // DEBUG
+ continue; /* Try next version */
+ }
+
+ /* Use the corrected format info */
+ format_info = corrected_format;
+
+ /* After XOR with 0x5412, format info (15 bits) is:
+ * Bits 0-9: BCH error correction
+ * Bits 10-12: mask pattern (0-7)
+ * Bits 13-14: error correction level (0=M, 1=L, 2=H, 3=Q)
+ */
+ int mask_pattern = (format_info >> 10) & 0x07;
+ int ecc_level = (format_info >> 13) & 0x03;
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " QR version=%d size=%d format=0x%04x ecc=%d mask=%d\n", // DEBUG
+ transform.version, transform.size, format_info, ecc_level, mask_pattern); // DEBUG
+ } // DEBUG
+
+ unmask_qr(&qr, mask_pattern);
+
+ /* Debug: print unmasked QR code */
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " Unmasked QR:\n"); // DEBUG
+ for (int my = 0; my < transform.size; my++) { // DEBUG
+ fprintf(stderr, " "); // DEBUG
+ for (int mx = 0; mx < transform.size; mx++) { // DEBUG
+ fprintf(stderr, "%c", qr.modules[my][mx] ? '#' : '.'); // DEBUG
+ } // DEBUG
+ fprintf(stderr, "\n"); // DEBUG
+ } // DEBUG
+ /* Verify specific modules near data start - only do this for large codes */ // DEBUG
+ if (transform.size >= 53) { // DEBUG
+ fprintf(stderr, " DEBUG: qr.modules[51][52] = %d\n", qr.modules[51][52]); // DEBUG
+ fprintf(stderr, " DEBUG: qr.modules[52][52] = %d\n", qr.modules[52][52]); // DEBUG
+ fprintf(stderr, " DEBUG: Row 51 cols 50-52: "); // DEBUG
+ for (int i = 50; i <= 52; i++) { // DEBUG
+ fprintf(stderr, "[%d]=%d ", i, qr.modules[51][i]); // DEBUG
+ } // DEBUG
+ fprintf(stderr, "\n"); // DEBUG
+ fprintf(stderr, " DEBUG: Row 52 cols 50-52: "); // DEBUG
+ for (int i = 50; i <= 52; i++) { // DEBUG
+ fprintf(stderr, "[%d]=%d ", i, qr.modules[52][i]); // DEBUG
+ } // DEBUG
+ fprintf(stderr, "\n"); // DEBUG
+ } // DEBUG
+ } // DEBUG
+
+ /* Debug dump: unmasked grid */
+ debug_dump_grid(qr.modules, transform.size, transform.version, "unmasked"); // DEBUG
+
+ /* Extract raw codewords (still interleaved) */
+ uint8_t raw_codewords[4096];
+ int raw_len = extract_qr_data(&qr, raw_codewords, sizeof(raw_codewords));
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " raw bytes (first 32): "); // DEBUG
+ for (int i = 0; i < 32 && i < raw_len; i++) { // DEBUG
+ fprintf(stderr, "%02x ", raw_codewords[i]); // DEBUG
+ } // DEBUG
+ fprintf(stderr, "\n"); // DEBUG
+ fprintf(stderr, " raw bytes around 215-220: "); // DEBUG
+ for (int i = 213; i < 222 && i < raw_len; i++) { // DEBUG
+ fprintf(stderr, "[%d]=%02x ", i, raw_codewords[i]); // DEBUG
+ } // DEBUG
+ fprintf(stderr, "\n"); // DEBUG
+ } // DEBUG
+
+ /* Debug dump: raw bits */
+ debug_dump_bits(raw_codewords, raw_len, transform.version, ecc_level); // DEBUG
+
+ /* Deinterleave codewords and apply RS error correction */
+ uint8_t codewords[4096];
+ int rs_errors = 0;
+ int rs_uncorrectable = 0;
+ int data_len = deinterleave_and_correct(raw_codewords, raw_len, transform.version, ecc_level,
+ codewords, sizeof(codewords), &rs_errors, &rs_uncorrectable);
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " raw_len=%d deinterleaved data_len=%d expected=%d rs_errors=%d\n", // DEBUG
+ raw_len, data_len, data_codewords[transform.version][ecc_level], rs_errors); // DEBUG
+ fprintf(stderr, " deinterleaved bytes (first 32): "); // DEBUG
+ for (int i = 0; i < 32 && i < data_len; i++) { // DEBUG
+ fprintf(stderr, "%02x ", codewords[i]); // DEBUG
+ } // DEBUG
+ fprintf(stderr, "\n"); // DEBUG
+ fprintf(stderr, " deinterleaved bytes (last 10): "); // DEBUG
+ for (int i = data_len - 10; i < data_len; i++) { // DEBUG
+ if (i >= 0) fprintf(stderr, "[%d]=%02x ", i, codewords[i]); // DEBUG
+ } // DEBUG
+ fprintf(stderr, "\n"); // DEBUG
+ } // DEBUG
+
+ /* Debug dump: codewords */
+ debug_dump_codewords(raw_codewords, raw_len, codewords, data_len, transform.version, ecc_level); // DEBUG
+
+ /* Decode content */
+ int result = decode_qr_content(codewords, data_len, transform.version, output, max_output);
+
+ /* If decode succeeded, check if we should accept it */
+ if (result > 0) {
+ /* When doing version brute-force (trying multiple versions), reject decodes
+ * with uncorrectable RS errors - this usually indicates wrong version guess.
+ * Only apply this check when we have alternatives to try (num_versions > 1). */
+ if (num_versions > 1 && rs_uncorrectable > 0) {
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " v%d decode rejected: %d uncorrectable RS blocks (trying alternatives)\n", // DEBUG
+ try_version, rs_uncorrectable); // DEBUG
+ } // DEBUG
+ continue; /* Try next version */
+ }
+
+ /* Increment debug counter for next QR */
+ if (debug_mode) { // DEBUG
+ debug_qr_counter += 10; /* BASIC style: 10, 20, 30, ... */ // DEBUG
+ } // DEBUG
+
+ if (vi > 0 && debug_finder) { // DEBUG
+ fprintf(stderr, " Version brute-force: estimated v%d, decoded with v%d\n",
+ estimated_version, try_version);
+ }
+
+ return result;
+ }
+
+ /* Decode failed, try next version */
+ } /* end version loop */
+
+ /* All versions failed */
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " decode_qr: all %d versions failed\n", num_versions); // DEBUG
+ } // DEBUG
+ return 0;
+}
+
+/* Find and decode all QR codes in an image */
+static int scan_image_for_qr(Image *img, ChunkList *chunks);
+
+/* ============================================================================
+ * PHASE 3: CHUNK ORGANIZATION
+ * ============================================================================ */
+
+STATIC void chunk_list_init(ChunkList *cl) {
+ cl->chunks = NULL;
+ cl->count = 0;
+ cl->capacity = 0;
+}
+
+STATIC void chunk_list_free(ChunkList *cl) {
+ for (int i = 0; i < cl->count; i++) {
+ free(cl->chunks[i].data);
+ }
+ free(cl->chunks);
+ cl->chunks = NULL;
+ cl->count = 0;
+ cl->capacity = 0;
+}
+
+STATIC int chunk_list_add(ChunkList *cl, char type, int index, int total,
+ const uint8_t *data, int data_len) {
+ if (cl->count >= cl->capacity) {
+ int new_cap = cl->capacity ? cl->capacity * 2 : 64;
+ Chunk *new_chunks = realloc(cl->chunks, new_cap * sizeof(Chunk));
+ if (!new_chunks) return 0;
+ cl->chunks = new_chunks;
+ cl->capacity = new_cap;
+ }
+
+ Chunk *c = &cl->chunks[cl->count];
+ c->type = type;
+ c->index = index;
+ c->total = total;
+ c->data = malloc(data_len);
+ if (!c->data) return 0;
+ memcpy(c->data, data, data_len);
+ c->data_len = data_len;
+ cl->count++;
+ return 1;
+}
+
+/* Parse QR content to extract chunk info: "N01/50:base64data..." */
+STATIC int parse_chunk_label(const char *content, char *type, int *index, int *total,
+ const char **data_start) {
+ if (content[0] != 'N' && content[0] != 'P') return 0;
+ *type = content[0];
+
+ /* Parse index */
+ const char *p = content + 1;
+ *index = 0;
+ while (*p >= '0' && *p <= '9') {
+ *index = *index * 10 + (*p - '0');
+ p++;
+ }
+
+ if (*p != '/') return 0;
+ p++;
+
+ /* Parse total */
+ *total = 0;
+ while (*p >= '0' && *p <= '9') {
+ *total = *total * 10 + (*p - '0');
+ p++;
+ }
+
+ /* Skip colon or other separator */
+ while (*p && (*p == ':' || *p == ' ')) p++;
+
+ *data_start = p;
+ return 1;
+}
+
+/* Sort chunks by type then index */
+static int chunk_compare(const void *a, const void *b) {
+ const Chunk *ca = a;
+ const Chunk *cb = b;
+
+ if (ca->type != cb->type) return ca->type - cb->type;
+ return ca->index - cb->index;
+}
+
+STATIC void chunk_list_sort(ChunkList *cl) {
+ if (cl->count > 1) {
+ qsort(cl->chunks, cl->count, sizeof(Chunk), chunk_compare);
+ }
+}
+
+/* Remove duplicate chunks (same type and index) */
+STATIC void chunk_list_dedupe(ChunkList *cl) {
+ if (cl->count < 2) return;
+
+ int write = 1;
+ for (int read = 1; read < cl->count; read++) {
+ if (cl->chunks[read].type != cl->chunks[write-1].type ||
+ cl->chunks[read].index != cl->chunks[write-1].index) {
+ if (write != read) {
+ cl->chunks[write] = cl->chunks[read];
+ }
+ write++;
+ } else {
+ free(cl->chunks[read].data);
+ }
+ }
+ cl->count = write;
+}
+
+/* ============================================================================
+ * PHASE 4: BASE64 DECODING
+ * ============================================================================ */
+
+static const int8_t b64_decode_table[256] = {
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
+ 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
+ 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
+ -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
+ 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+ -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+};
+
+STATIC int base64_decode(const char *input, int input_len, uint8_t *output, int max_output) {
+ int out_len = 0;
+ uint32_t accum = 0;
+ int bits = 0;
+
+ for (int i = 0; i < input_len; i++) {
+ int c = (unsigned char)input[i];
+ if (c == '=' || c == '\n' || c == '\r' || c == ' ') continue;
+
+ int val = b64_decode_table[c];
+ if (val < 0) continue; /* Skip invalid characters */
+
+ accum = (accum << 6) | val;
+ bits += 6;
+
+ if (bits >= 8) {
+ bits -= 8;
+ if (out_len < max_output) {
+ output[out_len++] = (accum >> bits) & 0xFF;
+ }
+ }
+ }
+
+ return out_len;
+}
+
+/* ============================================================================
+ * PHASE 5: REED-SOLOMON ERASURE CODING RECOVERY
+ * ============================================================================ */
+
+/* GF(2^8) for erasure coding - different from QR's GF */
+STATIC uint8_t ec_gf_exp[512];
+STATIC uint8_t ec_gf_log[256];
+static int ec_gf_initialized = 0;
+
+STATIC void ec_gf_init(void) {
+ if (ec_gf_initialized) return;
+
+ /* Using primitive polynomial x^8 + x^4 + x^3 + x^2 + 1 (0x11d) */
+ int x = 1;
+ for (int i = 0; i < 255; i++) {
+ ec_gf_exp[i] = x;
+ ec_gf_log[x] = i;
+ x <<= 1;
+ if (x & 0x100) x ^= 0x11d;
+ }
+ for (int i = 255; i < 512; i++) {
+ ec_gf_exp[i] = ec_gf_exp[i - 255];
+ }
+ ec_gf_initialized = 1;
+}
+
+STATIC uint8_t ec_gf_mul(uint8_t a, uint8_t b) {
+ if (a == 0 || b == 0) return 0;
+ return ec_gf_exp[ec_gf_log[a] + ec_gf_log[b]];
+}
+
+STATIC uint8_t ec_gf_inv(uint8_t a) {
+ if (a == 0) return 0;
+ return ec_gf_exp[255 - ec_gf_log[a]];
+}
+
+/* Recover missing data chunks using parity chunks
+ * This implements Reed-Solomon erasure recovery in GF(2^8)
+ *
+ * qr-backup uses a systematic RS code where:
+ * - N chunks are the original data
+ * - P chunks are parity computed as P[j] = sum(N[i] * alpha^(i*j)) for all i
+ *
+ * For recovery, we solve a system of equations in GF(2^8) using
+ * Gaussian elimination.
+ */
+STATIC int erasure_recover(ChunkList *cl, int total_normal, int total_parity) {
+ ec_gf_init();
+
+ /* Count available normal and parity chunks */
+ int have_normal = 0;
+ int have_parity = 0;
+
+ for (int i = 0; i < cl->count; i++) {
+ if (cl->chunks[i].type == 'N') have_normal++;
+ else if (cl->chunks[i].type == 'P') have_parity++;
+ }
+
+ int missing = total_normal - have_normal;
+ if (missing == 0) return 1; /* All data present */
+ if (missing > have_parity || missing > total_parity) {
+ fprintf(stderr, "Warning: %d chunks missing, only %d parity available\n",
+ missing, have_parity);
+ return 0; /* Cannot recover */
+ }
+
+ /* Find which chunks are missing and which are present */
+ int *n_present = calloc(total_normal + 1, sizeof(int));
+ int *p_present = calloc(total_parity + 1, sizeof(int));
+ int *missing_indices = calloc(missing, sizeof(int));
+ if (!n_present || !p_present || !missing_indices) {
+ free(n_present); free(p_present); free(missing_indices);
+ return 0;
+ }
+
+ for (int i = 0; i < cl->count; i++) {
+ if (cl->chunks[i].type == 'N' && cl->chunks[i].index <= total_normal) {
+ n_present[cl->chunks[i].index] = 1;
+ } else if (cl->chunks[i].type == 'P' && cl->chunks[i].index <= total_parity) {
+ p_present[cl->chunks[i].index] = 1;
+ }
+ }
+
+ int missing_count = 0;
+ for (int i = 1; i <= total_normal && missing_count < missing; i++) {
+ if (!n_present[i]) {
+ missing_indices[missing_count++] = i;
+ }
+ }
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " Erasure recovery: %d missing chunks [", missing); // DEBUG
+ for (int i = 0; i < missing; i++) { // DEBUG
+ fprintf(stderr, "%s%d", i > 0 ? "," : "", missing_indices[i]); // DEBUG
+ } // DEBUG
+ fprintf(stderr, "], %d parity available\n", have_parity); // DEBUG
+ } // DEBUG
+
+ /* Debug dump: erasure recovery initial state (result filled in on success paths) */
+ debug_dump_erasure(total_normal, total_parity, have_normal, have_parity, // DEBUG
+ missing_indices, missing, 0); // DEBUG
+
+ /* Simple XOR parity recovery for single erasure */
+ if (missing == 1 && have_parity >= 1) {
+ int missing_idx = missing_indices[0];
+
+ /* Find first parity chunk (P1 is simple XOR of all N chunks) */
+ Chunk *parity = NULL;
+ for (int i = 0; i < cl->count; i++) {
+ if (cl->chunks[i].type == 'P' && cl->chunks[i].index == 1) {
+ parity = &cl->chunks[i];
+ break;
+ }
+ }
+
+ /* If no P1, try any parity chunk */
+ if (!parity) {
+ for (int i = 0; i < cl->count; i++) {
+ if (cl->chunks[i].type == 'P') {
+ parity = &cl->chunks[i];
+ break;
+ }
+ }
+ }
+
+ if (parity && parity->data_len > 0) {
+ /* Determine chunk size from parity or other chunks */
+ int chunk_size = parity->data_len;
+ uint8_t *recovered = malloc(chunk_size);
+
+ if (recovered) {
+ memcpy(recovered, parity->data, chunk_size);
+
+ /* XOR with all present N chunks */
+ for (int i = 0; i < cl->count; i++) {
+ if (cl->chunks[i].type == 'N') {
+ int len = cl->chunks[i].data_len;
+ if (len > chunk_size) len = chunk_size;
+ for (int j = 0; j < len; j++) {
+ recovered[j] ^= cl->chunks[i].data[j];
+ }
+ }
+ }
+
+ /* Add recovered chunk */
+ chunk_list_add(cl, 'N', missing_idx, total_normal, recovered, chunk_size);
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " Recovered N%d using XOR parity\n", missing_idx); // DEBUG
+ } // DEBUG
+
+ /* Update debug file with success and recovered data */
+ uint8_t *rec_ptrs[1] = { recovered };
+ int rec_lens[1] = { chunk_size };
+ debug_dump_erasure_ex(total_normal, total_parity, have_normal, have_parity, // DEBUG
+ &missing_idx, 1, 1, rec_ptrs, rec_lens, "XOR parity"); // DEBUG
+
+ free(recovered);
+ free(n_present); free(p_present); free(missing_indices);
+ return 1;
+ }
+ }
+ }
+
+ /* Multi-erasure recovery using Cauchy matrix in GF(2^8)
+ *
+ * The generator matrix is [I | P] where P is a Cauchy matrix:
+ * P[i][j] = 1 / (x[i] + y[j]) where x and y are distinct field elements
+ *
+ * For qr-backup, we use x[i] = i (data indices) and y[j] = 255-j (parity indices)
+ * This ensures the Vandermonde-like matrix is invertible.
+ */
+ if (missing > 1 && have_parity >= missing) {
+ /* Find chunk size from any available chunk */
+ int chunk_size = 0;
+ for (int i = 0; i < cl->count && chunk_size == 0; i++) {
+ chunk_size = cl->chunks[i].data_len;
+ }
+
+ if (chunk_size == 0) {
+ free(n_present); free(p_present); free(missing_indices);
+ return 0;
+ }
+
+ /* Build the recovery matrix
+ * We need to select 'missing' parity chunks and solve for missing data
+ *
+ * The system is: P_selected = M * D_missing
+ * Where M is derived from the Cauchy encoding matrix
+ *
+ * For simplicity, use a Vandermonde-like matrix:
+ * M[i][j] = alpha^((missing_idx[j]-1) * parity_idx[i])
+ */
+
+ /* Select which parity chunks to use */
+ int *parity_used = calloc(missing, sizeof(int));
+ int parity_count = 0;
+ for (int p = 1; p <= total_parity && parity_count < missing; p++) {
+ if (p_present[p]) {
+ parity_used[parity_count++] = p;
+ }
+ }
+
+ if (parity_count < missing) {
+ free(n_present); free(p_present); free(missing_indices); free(parity_used);
+ return 0;
+ }
+
+ /* Allocate matrix for Gaussian elimination (missing x missing) */
+ uint8_t **matrix = malloc(missing * sizeof(uint8_t *));
+ uint8_t **augmented = malloc(missing * sizeof(uint8_t *));
+ for (int i = 0; i < missing; i++) {
+ matrix[i] = malloc(missing);
+ augmented[i] = malloc(chunk_size);
+ }
+
+ /* Build the encoding matrix
+ * For parity chunk p, its contribution from data chunk d is:
+ * coef = alpha^((d-1) * (p-1))
+ *
+ * We only care about the missing data chunks' contribution.
+ * The parity - sum(known_data * coef) = sum(missing_data * coef)
+ */
+ for (int i = 0; i < missing; i++) {
+ int p_idx = parity_used[i];
+
+ /* Find this parity chunk */
+ Chunk *parity = NULL;
+ for (int c = 0; c < cl->count; c++) {
+ if (cl->chunks[c].type == 'P' && cl->chunks[c].index == p_idx) {
+ parity = &cl->chunks[c];
+ break;
+ }
+ }
+
+ if (!parity) continue;
+
+ /* Start with parity data */
+ memcpy(augmented[i], parity->data, chunk_size);
+
+ /* Subtract contribution from known data chunks */
+ for (int c = 0; c < cl->count; c++) {
+ if (cl->chunks[c].type == 'N') {
+ int d_idx = cl->chunks[c].index;
+ uint8_t coef = ec_gf_exp[((d_idx - 1) * (p_idx - 1)) % 255];
+
+ for (int b = 0; b < chunk_size && b < cl->chunks[c].data_len; b++) {
+ augmented[i][b] ^= ec_gf_mul(coef, cl->chunks[c].data[b]);
+ }
+ }
+ }
+
+ /* Fill in the matrix coefficients for missing chunks */
+ for (int j = 0; j < missing; j++) {
+ int d_idx = missing_indices[j];
+ matrix[i][j] = ec_gf_exp[((d_idx - 1) * (p_idx - 1)) % 255];
+ }
+ }
+
+ /* Gaussian elimination with partial pivoting in GF(2^8) */
+ int success = 1;
+ for (int col = 0; col < missing && success; col++) {
+ /* Find pivot */
+ int pivot = -1;
+ for (int row = col; row < missing; row++) {
+ if (matrix[row][col] != 0) {
+ pivot = row;
+ break;
+ }
+ }
+
+ if (pivot < 0) {
+ success = 0;
+ break;
+ }
+
+ /* Swap rows if needed */
+ if (pivot != col) {
+ uint8_t *tmp = matrix[col];
+ matrix[col] = matrix[pivot];
+ matrix[pivot] = tmp;
+
+ tmp = augmented[col];
+ augmented[col] = augmented[pivot];
+ augmented[pivot] = tmp;
+ }
+
+ /* Scale pivot row */
+ uint8_t pivot_inv = ec_gf_inv(matrix[col][col]);
+ for (int j = col; j < missing; j++) {
+ matrix[col][j] = ec_gf_mul(matrix[col][j], pivot_inv);
+ }
+ for (int b = 0; b < chunk_size; b++) {
+ augmented[col][b] = ec_gf_mul(augmented[col][b], pivot_inv);
+ }
+
+ /* Eliminate column */
+ for (int row = 0; row < missing; row++) {
+ if (row != col && matrix[row][col] != 0) {
+ uint8_t factor = matrix[row][col];
+ for (int j = col; j < missing; j++) {
+ matrix[row][j] ^= ec_gf_mul(factor, matrix[col][j]);
+ }
+ for (int b = 0; b < chunk_size; b++) {
+ augmented[row][b] ^= ec_gf_mul(factor, augmented[col][b]);
+ }
+ }
+ }
+ }
+
+ if (success) {
+ /* Add recovered chunks */
+ for (int i = 0; i < missing; i++) {
+ chunk_list_add(cl, 'N', missing_indices[i], total_normal,
+ augmented[i], chunk_size);
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " Recovered N%d using multi-erasure recovery\n", // DEBUG
+ missing_indices[i]); // DEBUG
+ } // DEBUG
+ }
+
+ /* Update debug file with success and recovered data */
+ int *rec_lens = malloc(missing * sizeof(int));
+ for (int i = 0; i < missing; i++) rec_lens[i] = chunk_size;
+ debug_dump_erasure_ex(total_normal, total_parity, have_normal, have_parity, // DEBUG
+ missing_indices, missing, 1, augmented, rec_lens, // DEBUG
+ "Gaussian elimination in GF(2^8)"); // DEBUG
+ free(rec_lens);
+ }
+
+ /* Cleanup */
+ for (int i = 0; i < missing; i++) {
+ free(matrix[i]);
+ free(augmented[i]);
+ }
+ free(matrix);
+ free(augmented);
+ free(parity_used);
+
+ free(n_present); free(p_present); free(missing_indices);
+ return success;
+ }
+
+ free(n_present); free(p_present); free(missing_indices);
+
+ if (missing > 0) {
+ fprintf(stderr, "Warning: %d chunks missing, recovery failed\n", missing);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ============================================================================
+ * PHASE 6: DATA ASSEMBLY
+ * ============================================================================ */
+
+STATIC uint8_t *assemble_data(ChunkList *cl, int *out_len) {
+ /* Find total size needed */
+ int total_size = 0;
+ for (int i = 0; i < cl->count; i++) {
+ if (cl->chunks[i].type == 'N') {
+ total_size += cl->chunks[i].data_len;
+ }
+ }
+
+ if (total_size == 0) {
+ *out_len = 0;
+ return NULL;
+ }
+
+ uint8_t *data = malloc(total_size);
+ if (!data) {
+ *out_len = 0;
+ return NULL;
+ }
+
+ /* Concatenate normal chunks in order */
+ int pos = 0;
+ for (int i = 0; i < cl->count; i++) {
+ if (cl->chunks[i].type == 'N') {
+ memcpy(data + pos, cl->chunks[i].data, cl->chunks[i].data_len);
+ pos += cl->chunks[i].data_len;
+ }
+ }
+
+ /* Parse length prefix - qr-backup uses ASCII decimal followed by space
+ * e.g., "336 " for 336 bytes of data */
+ int len_end = 0;
+ uint32_t real_len = 0;
+
+ /* Parse ASCII decimal length */
+ while (len_end < pos && data[len_end] >= '0' && data[len_end] <= '9') {
+ real_len = real_len * 10 + (data[len_end] - '0');
+ len_end++;
+ }
+
+ /* Skip space separator */
+ if (len_end < pos && data[len_end] == ' ') {
+ len_end++;
+ }
+
+ if (len_end == 0) {
+ /* No valid length prefix found */
+ *out_len = pos;
+ return data;
+ }
+
+ /* The length prefix indicates the COMPRESSED size (gzip data length).
+ * This tells us how much of the remaining data is actual content
+ * vs padding added to fill the last QR chunk. */
+ int remaining = pos - len_end;
+
+ /* Use the smaller of: stated length or remaining data */
+ int actual_len = (int)real_len;
+ if (actual_len > remaining) {
+ actual_len = remaining;
+ }
+
+ /* Remove length prefix and truncate to actual length */
+ memmove(data, data + len_end, actual_len);
+ *out_len = actual_len;
+
+ return data;
+}
+
+/* ============================================================================
+ * PHASE 7: AES DECRYPTION (GPG Symmetric Mode)
+ * ============================================================================ */
+
+/* AES S-box */
+static const uint8_t aes_sbox[256] = {
+ 0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
+ 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
+ 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
+ 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
+ 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
+ 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
+ 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
+ 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
+ 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
+ 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
+ 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
+ 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
+ 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
+ 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
+ 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
+ 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
+};
+
+/* Inverse AES S-box */
+static const uint8_t aes_inv_sbox[256] = {
+ 0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
+ 0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
+ 0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
+ 0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
+ 0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
+ 0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
+ 0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
+ 0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
+ 0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
+ 0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
+ 0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
+ 0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
+ 0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
+ 0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
+ 0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
+ 0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d
+};
+
+/* AES round constants */
+static const uint8_t aes_rcon[11] = {
+ 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36
+};
+
+/* xtime - multiply by x in GF(2^8) */
+static uint8_t xtime(uint8_t x) {
+ return (x << 1) ^ ((x >> 7) * 0x1b);
+}
+
+/* AES key expansion */
+static void aes_key_expand(const uint8_t *key, uint8_t *round_keys, int key_len) {
+ int nk = key_len / 4; /* Number of 32-bit words in key */
+ int nr = nk + 6; /* Number of rounds */
+ int nb = 4; /* Block size in words */
+
+ /* Copy original key */
+ memcpy(round_keys, key, key_len);
+
+ uint8_t temp[4];
+ int i = nk;
+
+ while (i < nb * (nr + 1)) {
+ memcpy(temp, round_keys + (i - 1) * 4, 4);
+
+ if (i % nk == 0) {
+ /* RotWord */
+ uint8_t t = temp[0];
+ temp[0] = temp[1]; temp[1] = temp[2]; temp[2] = temp[3]; temp[3] = t;
+ /* SubWord */
+ for (int j = 0; j < 4; j++) temp[j] = aes_sbox[temp[j]];
+ /* XOR with Rcon */
+ temp[0] ^= aes_rcon[i / nk];
+ } else if (nk > 6 && i % nk == 4) {
+ for (int j = 0; j < 4; j++) temp[j] = aes_sbox[temp[j]];
+ }
+
+ for (int j = 0; j < 4; j++) {
+ round_keys[i * 4 + j] = round_keys[(i - nk) * 4 + j] ^ temp[j];
+ }
+ i++;
+ }
+}
+
+/* AES inverse cipher (decryption) */
+static void aes_decrypt_block(const uint8_t *in, uint8_t *out, const uint8_t *round_keys, int nr) {
+ uint8_t state[16];
+ memcpy(state, in, 16);
+
+ /* Add round key */
+ for (int i = 0; i < 16; i++) state[i] ^= round_keys[nr * 16 + i];
+
+ for (int round = nr - 1; round >= 0; round--) {
+ /* Inverse ShiftRows */
+ uint8_t temp;
+ temp = state[13]; state[13] = state[9]; state[9] = state[5]; state[5] = state[1]; state[1] = temp;
+ temp = state[2]; state[2] = state[10]; state[10] = temp;
+ temp = state[6]; state[6] = state[14]; state[14] = temp;
+ temp = state[3]; state[3] = state[7]; state[7] = state[11]; state[11] = state[15]; state[15] = temp;
+
+ /* Inverse SubBytes */
+ for (int i = 0; i < 16; i++) state[i] = aes_inv_sbox[state[i]];
+
+ /* Add round key */
+ for (int i = 0; i < 16; i++) state[i] ^= round_keys[round * 16 + i];
+
+ /* Inverse MixColumns (skip for round 0) */
+ if (round > 0) {
+ for (int c = 0; c < 4; c++) {
+ uint8_t a[4];
+ for (int i = 0; i < 4; i++) a[i] = state[c * 4 + i];
+
+ state[c * 4 + 0] = gf_mul(0x0e, a[0]) ^ gf_mul(0x0b, a[1]) ^ gf_mul(0x0d, a[2]) ^ gf_mul(0x09, a[3]);
+ state[c * 4 + 1] = gf_mul(0x09, a[0]) ^ gf_mul(0x0e, a[1]) ^ gf_mul(0x0b, a[2]) ^ gf_mul(0x0d, a[3]);
+ state[c * 4 + 2] = gf_mul(0x0d, a[0]) ^ gf_mul(0x09, a[1]) ^ gf_mul(0x0e, a[2]) ^ gf_mul(0x0b, a[3]);
+ state[c * 4 + 3] = gf_mul(0x0b, a[0]) ^ gf_mul(0x0d, a[1]) ^ gf_mul(0x09, a[2]) ^ gf_mul(0x0e, a[3]);
+ }
+ }
+ }
+
+ memcpy(out, state, 16);
+}
+
+/* Forward declarations */
+static void sha256(const uint8_t *data, size_t len, uint8_t *hash);
+STATIC uint8_t *gzip_decompress(const uint8_t *data, int data_len, int *out_len);
+
+/* ============================================================================
+ * SHA-1 Implementation (for S2K and MDC)
+ * ============================================================================ */
+
+static void sha1_transform(uint32_t *state, const uint8_t *data) {
+ uint32_t a, b, c, d, e, t, w[80];
+
+ /* Prepare message schedule */
+ for (int i = 0; i < 16; i++) {
+ w[i] = ((uint32_t)data[i*4] << 24) | ((uint32_t)data[i*4+1] << 16) |
+ ((uint32_t)data[i*4+2] << 8) | data[i*4+3];
+ }
+ for (int i = 16; i < 80; i++) {
+ t = w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16];
+ w[i] = (t << 1) | (t >> 31);
+ }
+
+ a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4];
+
+ #define SHA1_ROL(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+ for (int i = 0; i < 80; i++) {
+ uint32_t f, k;
+ if (i < 20) {
+ f = (b & c) | ((~b) & d);
+ k = 0x5A827999;
+ } else if (i < 40) {
+ f = b ^ c ^ d;
+ k = 0x6ED9EBA1;
+ } else if (i < 60) {
+ f = (b & c) | (b & d) | (c & d);
+ k = 0x8F1BBCDC;
+ } else {
+ f = b ^ c ^ d;
+ k = 0xCA62C1D6;
+ }
+
+ t = SHA1_ROL(a, 5) + f + e + k + w[i];
+ e = d; d = c; c = SHA1_ROL(b, 30); b = a; a = t;
+ }
+
+ #undef SHA1_ROL
+
+ state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e;
+}
+
+static void sha1(const uint8_t *data, size_t len, uint8_t *hash) {
+ uint32_t state[5] = {0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0};
+ uint8_t buffer[64];
+ size_t pos = 0;
+
+ /* Process complete blocks */
+ while (pos + 64 <= len) {
+ sha1_transform(state, data + pos);
+ pos += 64;
+ }
+
+ /* Final block with padding */
+ size_t remaining = len - pos;
+ memcpy(buffer, data + pos, remaining);
+ buffer[remaining++] = 0x80;
+
+ if (remaining > 56) {
+ memset(buffer + remaining, 0, 64 - remaining);
+ sha1_transform(state, buffer);
+ memset(buffer, 0, 56);
+ } else {
+ memset(buffer + remaining, 0, 56 - remaining);
+ }
+
+ /* Append length in bits */
+ uint64_t bits = len * 8;
+ buffer[56] = bits >> 56; buffer[57] = bits >> 48;
+ buffer[58] = bits >> 40; buffer[59] = bits >> 32;
+ buffer[60] = bits >> 24; buffer[61] = bits >> 16;
+ buffer[62] = bits >> 8; buffer[63] = bits;
+ sha1_transform(state, buffer);
+
+ /* Output hash */
+ for (int i = 0; i < 5; i++) {
+ hash[i*4] = state[i] >> 24; hash[i*4+1] = state[i] >> 16;
+ hash[i*4+2] = state[i] >> 8; hash[i*4+3] = state[i];
+ }
+}
+
+/* ============================================================================
+ * OpenPGP Packet Parsing and GPG Decryption
+ * ============================================================================ */
+
+/* S2K (String-to-Key) types */
+#define S2K_SIMPLE 0
+#define S2K_SALTED 1
+#define S2K_ITERATED 3
+
+/* Cipher algorithms */
+#define CIPHER_AES128 7
+#define CIPHER_AES192 8
+#define CIPHER_AES256 9
+
+/* Hash algorithms */
+#define HASH_SHA1 2
+#define HASH_SHA256 8
+
+/* S2K count decode: count = (16 + (c & 15)) << ((c >> 4) + 6) */
+static uint32_t s2k_decode_count(uint8_t c) {
+ return (16 + (c & 15)) << ((c >> 4) + 6);
+}
+
+/* Iterated and Salted S2K key derivation */
+static void s2k_derive_key(const char *password, int pass_len,
+ const uint8_t *salt, int s2k_type, uint8_t hash_algo,
+ uint32_t count, uint8_t *key, int key_len) {
+ int hash_len = (hash_algo == HASH_SHA256) ? 32 : 20;
+ int pos = 0;
+ int preload = 0; /* Number of zero bytes to prepend for key extension */
+
+ while (pos < key_len) {
+ /* Build data to hash */
+ uint8_t *hash_input = NULL;
+ int input_len = 0;
+
+ if (s2k_type == S2K_SIMPLE) {
+ /* Just hash the password */
+ input_len = preload + pass_len;
+ hash_input = malloc(input_len);
+ memset(hash_input, 0, preload);
+ memcpy(hash_input + preload, password, pass_len);
+ } else if (s2k_type == S2K_SALTED) {
+ /* Hash salt + password */
+ input_len = preload + 8 + pass_len;
+ hash_input = malloc(input_len);
+ memset(hash_input, 0, preload);
+ memcpy(hash_input + preload, salt, 8);
+ memcpy(hash_input + preload + 8, password, pass_len);
+ } else { /* S2K_ITERATED */
+ /* Hash salt + password repeatedly until count bytes processed */
+ int sp_len = 8 + pass_len; /* salt + password length */
+ uint32_t actual_count = count;
+ if (actual_count < (uint32_t)sp_len) actual_count = sp_len;
+
+ input_len = preload + actual_count;
+ hash_input = malloc(input_len);
+ memset(hash_input, 0, preload);
+
+ /* Fill with repeated salt+password */
+ int fill_pos = preload;
+ while (fill_pos < input_len) {
+ int chunk = sp_len;
+ if (fill_pos + chunk > input_len) chunk = input_len - fill_pos;
+
+ if (chunk <= 8) {
+ memcpy(hash_input + fill_pos, salt, chunk);
+ } else {
+ memcpy(hash_input + fill_pos, salt, 8);
+ int pass_chunk = chunk - 8;
+ if (pass_chunk > pass_len) pass_chunk = pass_len;
+ memcpy(hash_input + fill_pos + 8, password, pass_chunk);
+ }
+ fill_pos += sp_len;
+ }
+ }
+
+ /* Hash and extract key bytes */
+ uint8_t hash[32];
+ if (hash_algo == HASH_SHA256) {
+ sha256(hash_input, input_len, hash);
+ } else {
+ sha1(hash_input, input_len, hash);
+ }
+
+ int copy_len = hash_len;
+ if (pos + copy_len > key_len) copy_len = key_len - pos;
+ memcpy(key + pos, hash, copy_len);
+ pos += copy_len;
+
+ free(hash_input);
+ preload++; /* Add another zero byte for next round if needed */
+ }
+}
+
+/* AES encryption (for CFB mode) */
+static void aes_encrypt_block(const uint8_t *in, uint8_t *out, const uint8_t *round_keys, int nr) {
+ uint8_t state[16];
+ memcpy(state, in, 16);
+
+ /* Initial round key addition */
+ for (int i = 0; i < 16; i++) state[i] ^= round_keys[i];
+
+ for (int round = 1; round <= nr; round++) {
+ /* SubBytes */
+ for (int i = 0; i < 16; i++) state[i] = aes_sbox[state[i]];
+
+ /* ShiftRows */
+ uint8_t temp;
+ temp = state[1]; state[1] = state[5]; state[5] = state[9]; state[9] = state[13]; state[13] = temp;
+ temp = state[2]; state[2] = state[10]; state[10] = temp;
+ temp = state[6]; state[6] = state[14]; state[14] = temp;
+ temp = state[15]; state[15] = state[11]; state[11] = state[7]; state[7] = state[3]; state[3] = temp;
+
+ /* MixColumns (skip for final round) */
+ if (round < nr) {
+ for (int c = 0; c < 4; c++) {
+ uint8_t a[4];
+ for (int i = 0; i < 4; i++) a[i] = state[c * 4 + i];
+
+ state[c * 4 + 0] = xtime(a[0]) ^ xtime(a[1]) ^ a[1] ^ a[2] ^ a[3];
+ state[c * 4 + 1] = a[0] ^ xtime(a[1]) ^ xtime(a[2]) ^ a[2] ^ a[3];
+ state[c * 4 + 2] = a[0] ^ a[1] ^ xtime(a[2]) ^ xtime(a[3]) ^ a[3];
+ state[c * 4 + 3] = xtime(a[0]) ^ a[0] ^ a[1] ^ a[2] ^ xtime(a[3]);
+ }
+ }
+
+ /* AddRoundKey */
+ for (int i = 0; i < 16; i++) state[i] ^= round_keys[round * 16 + i];
+ }
+
+ memcpy(out, state, 16);
+}
+
+/* AES-CFB decryption (OpenPGP variant with resync) */
+static void aes_cfb_decrypt(const uint8_t *data, int data_len,
+ const uint8_t *key, int key_len,
+ uint8_t *output) {
+ int nr = (key_len == 16) ? 10 : (key_len == 24) ? 12 : 14;
+ uint8_t round_keys[240];
+ aes_key_expand(key, round_keys, key_len);
+
+ uint8_t fr[16] = {0}; /* Feedback register - starts as zeros (IV) */
+ uint8_t fre[16]; /* Encrypted feedback register */
+ int pos = 0;
+
+ while (pos < data_len) {
+ /* Encrypt feedback register */
+ aes_encrypt_block(fr, fre, round_keys, nr);
+
+ /* XOR with ciphertext to get plaintext */
+ int block_len = 16;
+ if (pos + block_len > data_len) block_len = data_len - pos;
+
+ for (int i = 0; i < block_len; i++) {
+ output[pos + i] = data[pos + i] ^ fre[i];
+ }
+
+ /* Update feedback register with ciphertext */
+ if (block_len == 16) {
+ memcpy(fr, data + pos, 16);
+ } else {
+ /* Partial block: shift and fill */
+ memmove(fr, fr + block_len, 16 - block_len);
+ memcpy(fr + 16 - block_len, data + pos, block_len);
+ }
+
+ pos += block_len;
+ }
+}
+
+/* Parse OpenPGP packet header
+ * Returns packet type and sets *body_len and *header_len
+ * Returns -1 on error */
+static int pgp_parse_header(const uint8_t *data, int len, int *body_len, int *header_len) {
+ if (len < 2) return -1;
+
+ uint8_t tag_byte = data[0];
+ if ((tag_byte & 0x80) == 0) return -1; /* Not a PGP packet */
+
+ int packet_tag;
+ int pos = 1;
+
+ if (tag_byte & 0x40) {
+ /* New format packet */
+ packet_tag = tag_byte & 0x3f;
+
+ if (data[pos] < 192) {
+ *body_len = data[pos];
+ pos++;
+ } else if (data[pos] < 224) {
+ if (len < pos + 2) return -1;
+ *body_len = ((data[pos] - 192) << 8) + data[pos+1] + 192;
+ pos += 2;
+ } else if (data[pos] == 255) {
+ if (len < pos + 5) return -1;
+ *body_len = ((uint32_t)data[pos+1] << 24) | ((uint32_t)data[pos+2] << 16) |
+ ((uint32_t)data[pos+3] << 8) | data[pos+4];
+ pos += 5;
+ } else {
+ /* Partial body length - not fully supported */
+ *body_len = 1 << (data[pos] & 0x1f);
+ pos++;
+ }
+ } else {
+ /* Old format packet */
+ packet_tag = (tag_byte >> 2) & 0x0f;
+ int length_type = tag_byte & 0x03;
+
+ if (length_type == 0) {
+ *body_len = data[pos];
+ pos++;
+ } else if (length_type == 1) {
+ if (len < pos + 2) return -1;
+ *body_len = (data[pos] << 8) | data[pos+1];
+ pos += 2;
+ } else if (length_type == 2) {
+ if (len < pos + 4) return -1;
+ *body_len = ((uint32_t)data[pos] << 24) | ((uint32_t)data[pos+1] << 16) |
+ ((uint32_t)data[pos+2] << 8) | data[pos+3];
+ pos += 4;
+ } else {
+ /* Indeterminate length */
+ *body_len = len - pos;
+ }
+ }
+
+ *header_len = pos;
+ return packet_tag;
+}
+
+/* Check if data looks like GPG encrypted data */
+STATIC int is_gpg_encrypted(const uint8_t *data, int len) {
+ if (len < 2) return 0;
+
+ /* Check for PGP packet tag */
+ if ((data[0] & 0x80) == 0) return 0;
+
+ /* Get packet type */
+ int packet_tag;
+ if (data[0] & 0x40) {
+ packet_tag = data[0] & 0x3f;
+ } else {
+ packet_tag = (data[0] >> 2) & 0x0f;
+ }
+
+ /* Symmetric-Key Encrypted Session Key (tag 3) or
+ * Symmetrically Encrypted Data (tag 9) or
+ * Symmetrically Encrypted and Integrity Protected (tag 18) */
+ return (packet_tag == 3 || packet_tag == 9 || packet_tag == 18);
+}
+
+/* Full GPG symmetric decryption
+ * Returns decrypted data or NULL on error */
+static uint8_t *gpg_decrypt(const uint8_t *data, int data_len,
+ const char *password, int *out_len) {
+ *out_len = 0;
+
+ if (!is_gpg_encrypted(data, data_len)) {
+ return NULL;
+ }
+
+ int pos = 0;
+ uint8_t session_key[32];
+ int session_key_len = 0;
+ int cipher_algo = CIPHER_AES128;
+ const uint8_t *encrypted_data = NULL;
+ int encrypted_len = 0;
+ int has_mdc = 0;
+
+ /* Parse packets */
+ while (pos < data_len) {
+ int body_len, header_len;
+ int packet_tag = pgp_parse_header(data + pos, data_len - pos, &body_len, &header_len);
+
+ if (packet_tag < 0) break;
+
+ const uint8_t *body = data + pos + header_len;
+
+ if (packet_tag == 3) {
+ /* Symmetric-Key Encrypted Session Key packet */
+ if (body_len < 4) return NULL;
+
+ int version = body[0];
+ if (version != 4) {
+ fprintf(stderr, "GPG: Unsupported SKESK version %d\n", version);
+ return NULL;
+ }
+
+ cipher_algo = body[1];
+ int s2k_type = body[2];
+ int s2k_pos = 3;
+
+ uint8_t hash_algo = HASH_SHA1;
+ uint8_t salt[8] = {0};
+ uint32_t count = 65536;
+
+ if (s2k_type == S2K_SIMPLE) {
+ hash_algo = body[s2k_pos++];
+ } else if (s2k_type == S2K_SALTED) {
+ hash_algo = body[s2k_pos++];
+ memcpy(salt, body + s2k_pos, 8);
+ s2k_pos += 8;
+ } else if (s2k_type == S2K_ITERATED) {
+ hash_algo = body[s2k_pos++];
+ memcpy(salt, body + s2k_pos, 8);
+ s2k_pos += 8;
+ count = s2k_decode_count(body[s2k_pos++]);
+ } else {
+ fprintf(stderr, "GPG: Unsupported S2K type %d\n", s2k_type);
+ return NULL;
+ }
+
+ /* Determine key length from cipher */
+ switch (cipher_algo) {
+ case CIPHER_AES128: session_key_len = 16; break;
+ case CIPHER_AES192: session_key_len = 24; break;
+ case CIPHER_AES256: session_key_len = 32; break;
+ default:
+ fprintf(stderr, "GPG: Unsupported cipher algorithm %d\n", cipher_algo);
+ return NULL;
+ }
+
+ /* Derive session key from password */
+ s2k_derive_key(password, strlen(password), salt, s2k_type,
+ hash_algo, count, session_key, session_key_len);
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, "GPG: S2K type=%d hash=%d count=%u cipher=%d keylen=%d\n", // DEBUG
+ s2k_type, hash_algo, count, cipher_algo, session_key_len); // DEBUG
+ } // DEBUG
+
+ } else if (packet_tag == 9) {
+ /* Symmetrically Encrypted Data packet */
+ encrypted_data = body;
+ encrypted_len = body_len;
+ has_mdc = 0;
+
+ } else if (packet_tag == 18) {
+ /* Symmetrically Encrypted Integrity Protected Data packet */
+ if (body_len < 1 || body[0] != 1) {
+ fprintf(stderr, "GPG: Unsupported SEIPD version\n");
+ return NULL;
+ }
+ encrypted_data = body + 1;
+ encrypted_len = body_len - 1;
+ has_mdc = 1;
+ }
+
+ pos += header_len + body_len;
+ }
+
+ if (!encrypted_data || session_key_len == 0) {
+ fprintf(stderr, "GPG: Missing encrypted data or session key\n");
+ return NULL;
+ }
+
+ /* Decrypt the data using AES-CFB */
+ uint8_t *decrypted = malloc(encrypted_len);
+ if (!decrypted) return NULL;
+
+ aes_cfb_decrypt(encrypted_data, encrypted_len, session_key, session_key_len, decrypted);
+
+ /* OpenPGP CFB resync: first block_size+2 bytes are prefix
+ * prefix[block_size-2:block_size] should equal prefix[block_size:block_size+2] */
+ int block_size = 16; /* AES block size */
+ if (encrypted_len < block_size + 2) {
+ free(decrypted);
+ return NULL;
+ }
+
+ /* Verify prefix (quick check) */
+ if (decrypted[block_size - 2] != decrypted[block_size] ||
+ decrypted[block_size - 1] != decrypted[block_size + 1]) {
+ fprintf(stderr, "GPG: Decryption prefix check failed (wrong password?)\n");
+ free(decrypted);
+ return NULL;
+ }
+
+ /* Skip prefix */
+ int payload_start = block_size + 2;
+ int payload_len = encrypted_len - payload_start;
+
+ /* Handle MDC if present */
+ if (has_mdc) {
+ /* Last 22 bytes should be MDC packet: 0xD3 0x14 + 20-byte SHA1 */
+ if (payload_len < 22) {
+ free(decrypted);
+ return NULL;
+ }
+
+ int mdc_pos = payload_start + payload_len - 22;
+ if (decrypted[mdc_pos] != 0xD3 || decrypted[mdc_pos + 1] != 0x14) {
+ fprintf(stderr, "GPG: MDC packet not found\n");
+ free(decrypted);
+ return NULL;
+ }
+
+ /* Verify MDC: SHA1 of prefix + plaintext + 0xD3 0x14 */
+ uint8_t *mdc_input = malloc(payload_start + payload_len - 20);
+ memcpy(mdc_input, decrypted, payload_start + payload_len - 20);
+
+ uint8_t computed_hash[20];
+ sha1(mdc_input, payload_start + payload_len - 20, computed_hash);
+ free(mdc_input);
+
+ if (memcmp(computed_hash, decrypted + mdc_pos + 2, 20) != 0) {
+ fprintf(stderr, "GPG: MDC verification failed\n");
+ free(decrypted);
+ return NULL;
+ }
+
+ payload_len -= 22; /* Remove MDC from output */
+ }
+
+ /* The payload should now be a Literal Data or Compressed Data packet */
+ int literal_body_len, literal_header_len;
+ int literal_tag = pgp_parse_header(decrypted + payload_start, payload_len,
+ &literal_body_len, &literal_header_len);
+
+ uint8_t *result = NULL;
+
+ if (literal_tag == 11) {
+ /* Literal Data packet */
+ const uint8_t *lit_body = decrypted + payload_start + literal_header_len;
+
+ /* Format: mode(1) + filename_len(1) + filename + date(4) + data */
+ if (literal_body_len < 6) {
+ free(decrypted);
+ return NULL;
+ }
+
+ int filename_len = lit_body[1];
+ int data_offset = 2 + filename_len + 4;
+ int data_len_final = literal_body_len - data_offset;
+
+ result = malloc(data_len_final);
+ if (result) {
+ memcpy(result, lit_body + data_offset, data_len_final);
+ *out_len = data_len_final;
+ }
+
+ } else if (literal_tag == 8) {
+ /* Compressed Data packet - need to decompress */
+ const uint8_t *comp_body = decrypted + payload_start + literal_header_len;
+ int comp_algo = comp_body[0];
+
+ if (comp_algo == 0) {
+ /* Uncompressed */
+ result = malloc(literal_body_len - 1);
+ if (result) {
+ memcpy(result, comp_body + 1, literal_body_len - 1);
+ *out_len = literal_body_len - 1;
+ }
+ } else if (comp_algo == 1 || comp_algo == 2) {
+ /* ZIP or ZLIB - use gzip_decompress (handles zlib format too) */
+ /* For raw deflate (ZIP), we need to wrap it */
+ if (comp_algo == 1) {
+ /* Raw deflate - construct minimal gzip wrapper */
+ int comp_len = literal_body_len - 1;
+ uint8_t *wrapped = malloc(comp_len + 18); /* gzip header + trailer */
+ /* Minimal gzip header */
+ wrapped[0] = 0x1f; wrapped[1] = 0x8b; wrapped[2] = 8; wrapped[3] = 0;
+ wrapped[4] = wrapped[5] = wrapped[6] = wrapped[7] = 0; /* mtime */
+ wrapped[8] = 0; wrapped[9] = 0xff; /* xfl, os */
+ memcpy(wrapped + 10, comp_body + 1, comp_len);
+ /* Add dummy trailer (CRC + size will be wrong but ignored) */
+ memset(wrapped + 10 + comp_len, 0, 8);
+
+ result = gzip_decompress(wrapped, comp_len + 18, out_len);
+ free(wrapped);
+ } else {
+ /* ZLIB format - skip 2-byte header and 4-byte trailer */
+ int comp_len = literal_body_len - 1;
+ if (comp_len > 6) {
+ uint8_t *wrapped = malloc(comp_len + 12);
+ wrapped[0] = 0x1f; wrapped[1] = 0x8b; wrapped[2] = 8; wrapped[3] = 0;
+ wrapped[4] = wrapped[5] = wrapped[6] = wrapped[7] = 0;
+ wrapped[8] = 0; wrapped[9] = 0xff;
+ memcpy(wrapped + 10, comp_body + 3, comp_len - 6); /* Skip zlib header/trailer */
+ memset(wrapped + 10 + comp_len - 6, 0, 8);
+
+ result = gzip_decompress(wrapped, comp_len + 6, out_len);
+ free(wrapped);
+ }
+ }
+ } else {
+ fprintf(stderr, "GPG: Unsupported compression algorithm %d\n", comp_algo);
+ }
+ } else {
+ fprintf(stderr, "GPG: Unexpected packet type %d after decryption\n", literal_tag);
+ }
+
+ free(decrypted);
+ return result;
+}
+
+/* Legacy functions for API compatibility */
+static uint8_t *aes_cbc_decrypt(const uint8_t *data, int data_len,
+ const uint8_t *key, int key_len,
+ const uint8_t *iv, int *out_len) {
+ if (data_len % 16 != 0 || data_len == 0) {
+ *out_len = 0;
+ return NULL;
+ }
+
+ uint8_t *output = malloc(data_len);
+ if (!output) {
+ *out_len = 0;
+ return NULL;
+ }
+
+ /* Expand key */
+ int nr = (key_len == 16) ? 10 : (key_len == 24) ? 12 : 14;
+ uint8_t round_keys[240];
+ aes_key_expand(key, round_keys, key_len);
+
+ /* Decrypt blocks */
+ uint8_t prev_block[16];
+ memcpy(prev_block, iv, 16);
+
+ for (int i = 0; i < data_len; i += 16) {
+ uint8_t decrypted[16];
+ aes_decrypt_block(data + i, decrypted, round_keys, nr);
+
+ for (int j = 0; j < 16; j++) {
+ output[i + j] = decrypted[j] ^ prev_block[j];
+ }
+ memcpy(prev_block, data + i, 16);
+ }
+
+ /* Remove PKCS7 padding */
+ int pad_len = output[data_len - 1];
+ if (pad_len > 0 && pad_len <= 16) {
+ *out_len = data_len - pad_len;
+ } else {
+ *out_len = data_len;
+ }
+
+ return output;
+}
+
+/* ============================================================================
+ * PHASE 8: GZIP/ZLIB DECOMPRESSION
+ * ============================================================================ */
+
+/* Huffman code decoding table */
+typedef struct {
+ uint16_t sym; /* Symbol or subtable offset */
+ uint8_t bits; /* Number of bits */
+} HuffEntry;
+
+/* Inflate state */
+typedef struct {
+ const uint8_t *in;
+ int in_len;
+ int in_pos;
+ uint32_t bit_buf;
+ int bit_cnt;
+
+ uint8_t *out;
+ int out_len;
+ int out_pos;
+ int out_cap;
+} InflateState;
+
+/* Read bits from input stream */
+static uint32_t inf_read_bits(InflateState *s, int n) {
+ while (s->bit_cnt < n) {
+ if (s->in_pos >= s->in_len) return 0;
+ s->bit_buf |= (uint32_t)s->in[s->in_pos++] << s->bit_cnt;
+ s->bit_cnt += 8;
+ }
+ uint32_t val = s->bit_buf & ((1 << n) - 1);
+ s->bit_buf >>= n;
+ s->bit_cnt -= n;
+ return val;
+}
+
+/* Output a byte */
+static int inf_output(InflateState *s, uint8_t b) {
+ if (s->out_pos >= s->out_cap) {
+ int new_cap = s->out_cap ? s->out_cap * 2 : 4096;
+ uint8_t *new_out = realloc(s->out, new_cap);
+ if (!new_out) return 0;
+ s->out = new_out;
+ s->out_cap = new_cap;
+ }
+ s->out[s->out_pos++] = b;
+ return 1;
+}
+
+/* Fixed Huffman code lengths */
+static const uint8_t fixed_lit_lengths[288] = {
+ 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+ 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+ 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+ 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+ 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+ 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+ 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+ 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8
+};
+
+/* Build Huffman decode table */
+static int huff_build(const uint8_t *lengths, int count, HuffEntry *table, int *table_bits) {
+ int bl_count[16] = {0};
+ int max_len = 0;
+
+ for (int i = 0; i < count; i++) {
+ if (lengths[i]) {
+ bl_count[lengths[i]]++;
+ if (lengths[i] > max_len) max_len = lengths[i];
+ }
+ }
+
+ *table_bits = max_len > 9 ? 9 : max_len;
+
+ int code = 0;
+ int next_code[16];
+ next_code[0] = 0;
+ for (int bits = 1; bits <= max_len; bits++) {
+ code = (code + bl_count[bits - 1]) << 1;
+ next_code[bits] = code;
+ }
+
+ for (int i = 0; i < count; i++) {
+ int len = lengths[i];
+ if (len) {
+ int c = next_code[len]++;
+ /* Reverse bits */
+ int rev = 0;
+ for (int j = 0; j < len; j++) {
+ rev = (rev << 1) | (c & 1);
+ c >>= 1;
+ }
+
+ if (len <= *table_bits) {
+ /* Fill all entries for this code */
+ int fill = 1 << (*table_bits - len);
+ for (int j = 0; j < fill; j++) {
+ int idx = rev | (j << len);
+ table[idx].sym = i;
+ table[idx].bits = len;
+ }
+ }
+ }
+ }
+
+ return 1;
+}
+
+/* Decode a Huffman symbol */
+static int huff_decode(InflateState *s, HuffEntry *table, int table_bits) {
+ while (s->bit_cnt < table_bits) {
+ if (s->in_pos >= s->in_len) return -1;
+ s->bit_buf |= (uint32_t)s->in[s->in_pos++] << s->bit_cnt;
+ s->bit_cnt += 8;
+ }
+
+ int idx = s->bit_buf & ((1 << table_bits) - 1);
+ int bits = table[idx].bits;
+ int sym = table[idx].sym;
+
+ s->bit_buf >>= bits;
+ s->bit_cnt -= bits;
+
+ return sym;
+}
+
+/* Length and distance extra bits tables */
+static const int len_base[] = {3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258};
+static const int len_extra[] = {0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0};
+static const int dist_base[] = {1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577};
+static const int dist_extra[] = {0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};
+
+/* Code length code order for dynamic Huffman */
+static const int codelen_order[19] = {
+ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+};
+
+/* Inflate a deflate stream with given Huffman tables */
+static int inflate_with_tables(InflateState *s, HuffEntry *lit_table, int lit_bits,
+ HuffEntry *dist_table, int dist_bits) {
+ while (1) {
+ int sym = huff_decode(s, lit_table, lit_bits);
+ if (sym < 0) return 0;
+
+ if (sym < 256) {
+ /* Literal */
+ if (!inf_output(s, sym)) return 0;
+ } else if (sym == 256) {
+ /* End of block */
+ return 1;
+ } else {
+ /* Length/distance pair */
+ sym -= 257;
+ if (sym >= 29) return 0;
+ int len = len_base[sym] + inf_read_bits(s, len_extra[sym]);
+
+ int dist_sym = huff_decode(s, dist_table, dist_bits);
+ if (dist_sym < 0 || dist_sym >= 30) return 0;
+ int dist = dist_base[dist_sym] + inf_read_bits(s, dist_extra[dist_sym]);
+
+ /* Copy from history */
+ for (int i = 0; i < len; i++) {
+ int src = s->out_pos - dist;
+ if (src < 0) return 0;
+ if (!inf_output(s, s->out[src])) return 0;
+ }
+ }
+ }
+}
+
+/* Inflate a block with fixed Huffman codes */
+static int inflate_block_fixed(InflateState *s) {
+ HuffEntry lit_table[512];
+ HuffEntry dist_table[32];
+ int lit_bits, dist_bits;
+
+ /* Build fixed tables */
+ huff_build(fixed_lit_lengths, 288, lit_table, &lit_bits);
+
+ uint8_t fixed_dist[32];
+ for (int i = 0; i < 32; i++) fixed_dist[i] = 5;
+ huff_build(fixed_dist, 32, dist_table, &dist_bits);
+
+ return inflate_with_tables(s, lit_table, lit_bits, dist_table, dist_bits);
+}
+
+/* Inflate a block with dynamic Huffman codes */
+static int inflate_block_dynamic(InflateState *s) {
+ /* Read header */
+ int hlit = inf_read_bits(s, 5) + 257; /* Literal/length codes */
+ int hdist = inf_read_bits(s, 5) + 1; /* Distance codes */
+ int hclen = inf_read_bits(s, 4) + 4; /* Code length codes */
+
+ if (hlit > 286 || hdist > 30) return 0;
+
+ /* Read code length code lengths */
+ uint8_t codelen_lengths[19] = {0};
+ for (int i = 0; i < hclen; i++) {
+ codelen_lengths[codelen_order[i]] = inf_read_bits(s, 3);
+ }
+
+ /* Build code length Huffman table */
+ HuffEntry codelen_table[128];
+ int codelen_bits;
+ if (!huff_build(codelen_lengths, 19, codelen_table, &codelen_bits)) return 0;
+
+ /* Read literal/length and distance code lengths */
+ uint8_t lengths[286 + 30];
+ int total_codes = hlit + hdist;
+ int i = 0;
+
+ while (i < total_codes) {
+ int sym = huff_decode(s, codelen_table, codelen_bits);
+ if (sym < 0) return 0;
+
+ if (sym < 16) {
+ /* Literal code length */
+ lengths[i++] = sym;
+ } else if (sym == 16) {
+ /* Repeat previous length 3-6 times */
+ if (i == 0) return 0;
+ int repeat = 3 + inf_read_bits(s, 2);
+ uint8_t prev = lengths[i - 1];
+ while (repeat-- && i < total_codes) lengths[i++] = prev;
+ } else if (sym == 17) {
+ /* Repeat zero 3-10 times */
+ int repeat = 3 + inf_read_bits(s, 3);
+ while (repeat-- && i < total_codes) lengths[i++] = 0;
+ } else if (sym == 18) {
+ /* Repeat zero 11-138 times */
+ int repeat = 11 + inf_read_bits(s, 7);
+ while (repeat-- && i < total_codes) lengths[i++] = 0;
+ } else {
+ return 0;
+ }
+ }
+
+ /* Build literal/length and distance tables */
+ HuffEntry lit_table[32768]; /* Need larger table for dynamic codes */
+ HuffEntry dist_table[32768];
+ int lit_bits, dist_bits;
+
+ if (!huff_build(lengths, hlit, lit_table, &lit_bits)) return 0;
+ if (!huff_build(lengths + hlit, hdist, dist_table, &dist_bits)) return 0;
+
+ return inflate_with_tables(s, lit_table, lit_bits, dist_table, dist_bits);
+}
+
+/* Decompress gzip data */
+STATIC uint8_t *gzip_decompress(const uint8_t *data, int data_len, int *out_len) {
+ if (data_len < 10) {
+ *out_len = 0;
+ return NULL;
+ }
+
+ /* Check gzip magic */
+ if (data[0] != 0x1f || data[1] != 0x8b) {
+ *out_len = 0;
+ return NULL;
+ }
+
+ /* Check compression method (8 = deflate) */
+ if (data[2] != 8) {
+ *out_len = 0;
+ return NULL;
+ }
+
+ int flags = data[3];
+ int pos = 10;
+
+ /* Skip extra field */
+ if (flags & 0x04) {
+ if (pos + 2 > data_len) { *out_len = 0; return NULL; }
+ int xlen = data[pos] | (data[pos+1] << 8);
+ pos += 2 + xlen;
+ }
+
+ /* Skip filename */
+ if (flags & 0x08) {
+ while (pos < data_len && data[pos]) pos++;
+ pos++;
+ }
+
+ /* Skip comment */
+ if (flags & 0x10) {
+ while (pos < data_len && data[pos]) pos++;
+ pos++;
+ }
+
+ /* Skip header CRC */
+ if (flags & 0x02) pos += 2;
+
+ if (pos >= data_len) {
+ *out_len = 0;
+ return NULL;
+ }
+
+ /* Initialize inflate state */
+ InflateState s = {0};
+ s.in = data + pos;
+ s.in_len = data_len - pos - 8; /* Exclude trailer */
+ s.in_pos = 0;
+ s.out = NULL;
+ s.out_pos = 0;
+ s.out_cap = 0;
+
+ /* Process blocks */
+ int bfinal;
+ do {
+ bfinal = inf_read_bits(&s, 1);
+ int btype = inf_read_bits(&s, 2);
+
+ if (btype == 0) {
+ /* Stored block */
+ s.bit_buf = 0;
+ s.bit_cnt = 0;
+
+ if (s.in_pos + 4 > s.in_len) break;
+ int len = s.in[s.in_pos] | (s.in[s.in_pos+1] << 8);
+ s.in_pos += 4;
+
+ for (int i = 0; i < len && s.in_pos < s.in_len; i++) {
+ if (!inf_output(&s, s.in[s.in_pos++])) break;
+ }
+ } else if (btype == 1) {
+ /* Fixed Huffman */
+ if (!inflate_block_fixed(&s)) break;
+ } else if (btype == 2) {
+ /* Dynamic Huffman */
+ if (!inflate_block_dynamic(&s)) break;
+ } else {
+ break; /* Invalid block type */
+ }
+ } while (!bfinal);
+
+ *out_len = s.out_pos;
+ return s.out;
+}
+
+/* ============================================================================
+ * PHASE 9: SHA256 CHECKSUM
+ * ============================================================================ */
+
+/* SHA256 constants */
+static const uint32_t sha256_k[64] = {
+ 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
+ 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
+ 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
+ 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
+ 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
+ 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
+ 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
+ 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
+};
+
+#define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
+#define CH(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
+#define MAJ(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+#define EP0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
+#define EP1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
+#define SIG0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ ((x) >> 3))
+#define SIG1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ ((x) >> 10))
+
+static void sha256_transform(uint32_t *state, const uint8_t *block) {
+ uint32_t w[64];
+
+ for (int i = 0; i < 16; i++) {
+ w[i] = ((uint32_t)block[i*4] << 24) |
+ ((uint32_t)block[i*4+1] << 16) |
+ ((uint32_t)block[i*4+2] << 8) |
+ (uint32_t)block[i*4+3];
+ }
+
+ for (int i = 16; i < 64; i++) {
+ w[i] = SIG1(w[i-2]) + w[i-7] + SIG0(w[i-15]) + w[i-16];
+ }
+
+ uint32_t a = state[0], b = state[1], c = state[2], d = state[3];
+ uint32_t e = state[4], f = state[5], g = state[6], h = state[7];
+
+ for (int i = 0; i < 64; i++) {
+ uint32_t t1 = h + EP1(e) + CH(e, f, g) + sha256_k[i] + w[i];
+ uint32_t t2 = EP0(a) + MAJ(a, b, c);
+ h = g; g = f; f = e; e = d + t1;
+ d = c; c = b; b = a; a = t1 + t2;
+ }
+
+ state[0] += a; state[1] += b; state[2] += c; state[3] += d;
+ state[4] += e; state[5] += f; state[6] += g; state[7] += h;
+}
+
+static void sha256(const uint8_t *data, size_t len, uint8_t *hash) {
+ uint32_t state[8] = {
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
+ };
+
+ /* Process full blocks */
+ size_t i;
+ for (i = 0; i + 64 <= len; i += 64) {
+ sha256_transform(state, data + i);
+ }
+
+ /* Pad and process final block(s) */
+ uint8_t block[64];
+ size_t remaining = len - i;
+ memcpy(block, data + i, remaining);
+ block[remaining] = 0x80;
+
+ if (remaining >= 56) {
+ memset(block + remaining + 1, 0, 63 - remaining);
+ sha256_transform(state, block);
+ memset(block, 0, 56);
+ } else {
+ memset(block + remaining + 1, 0, 55 - remaining);
+ }
+
+ /* Append length in bits */
+ uint64_t bit_len = len * 8;
+ for (int j = 0; j < 8; j++) {
+ block[63 - j] = bit_len & 0xff;
+ bit_len >>= 8;
+ }
+ sha256_transform(state, block);
+
+ /* Output hash */
+ for (int j = 0; j < 8; j++) {
+ hash[j*4] = (state[j] >> 24) & 0xff;
+ hash[j*4+1] = (state[j] >> 16) & 0xff;
+ hash[j*4+2] = (state[j] >> 8) & 0xff;
+ hash[j*4+3] = state[j] & 0xff;
+ }
+}
+
+static void print_sha256(const uint8_t *hash) {
+ for (int i = 0; i < 32; i++) {
+ printf("%02x", hash[i]);
+ }
+ printf("\n");
+}
+
+/* ============================================================================
+ * SCAN IMAGE FOR QR CODES (Implementation)
+ * ============================================================================ */
+
+/* Check if three finder patterns could form a valid QR code */
+static int valid_qr_triple(FinderPattern *p0, FinderPattern *p1, FinderPattern *p2) {
+ /* Module sizes should be similar, but can vary significantly with distortion */
+ float ms0 = p0->module_size;
+ float ms1 = p1->module_size;
+ float ms2 = p2->module_size;
+ float avg_ms = (ms0 + ms1 + ms2) / 3.0f;
+
+ /* Allow 25% variance on module sizes (tightened from 40%) */
+ if (ms0 < avg_ms * 0.75f || ms0 > avg_ms * 1.25f) return 0;
+ if (ms1 < avg_ms * 0.75f || ms1 > avg_ms * 1.25f) return 0;
+ if (ms2 < avg_ms * 0.75f || ms2 > avg_ms * 1.25f) return 0;
+
+ /* Check that distances are consistent with a QR code */
+ int d01 = distance_sq(p0->x, p0->y, p1->x, p1->y);
+ int d02 = distance_sq(p0->x, p0->y, p2->x, p2->y);
+ int d12 = distance_sq(p1->x, p1->y, p2->x, p2->y);
+
+ /* Sort distances to identify min, mid, max */
+ int min_d, mid_d, max_d;
+ if (d01 <= d02 && d01 <= d12) {
+ min_d = d01;
+ mid_d = (d02 < d12) ? d02 : d12;
+ max_d = (d02 < d12) ? d12 : d02;
+ } else if (d02 <= d01 && d02 <= d12) {
+ min_d = d02;
+ mid_d = (d01 < d12) ? d01 : d12;
+ max_d = (d01 < d12) ? d12 : d01;
+ } else {
+ min_d = d12;
+ mid_d = (d01 < d02) ? d01 : d02;
+ max_d = (d01 < d02) ? d02 : d01;
+ }
+
+ /* Allow up to 2x difference between the two shorter sides (tightened from 5x)
+ * This handles moderate aspect ratio distortion */
+ if (mid_d > min_d * 2.0f) return 0;
+
+ /* Hypotenuse check: for a right angle, max_d = min_d + mid_d (Pythagorean theorem)
+ * For distorted codes, this may not be exactly true. Allow 30% tolerance (up from 20%) */
+ int expected_hyp = min_d + mid_d;
+ float hyp_ratio = (float)max_d / expected_hyp;
+
+ /* Scale tolerance with the size of the code - allow more distortion for larger codes
+ * This is because even a small rotation can cause significant distortion at the edges
+ * of large codes */
+ float hyp_tolerance = 0.20f; /* Base tolerance of 20% (tightened from 30%) */
+ if (avg_ms > 3.0f) {
+ /* Increase tolerance for larger modules, capped at 25% */
+ hyp_tolerance += (avg_ms - 3.0f) * 0.01f;
+ if (hyp_tolerance > 0.25f) hyp_tolerance = 0.25f;
+ }
+
+ if (hyp_ratio < 1.0f - hyp_tolerance || hyp_ratio > 1.0f + hyp_tolerance) return 0;
+
+ /* Version consistency check with expanded tolerance */
+ float side1 = fsqrt((float)min_d);
+ float side2 = fsqrt((float)mid_d);
+ float version1 = (side1 / avg_ms - 10.0f) / 4.0f;
+ float version2 = (side2 / avg_ms - 10.0f) / 4.0f;
+
+ /* Allow versions to differ by at most 3 (tightened from 10) - QR codes should have
+ * consistent dimensions even with some distortion */
+ float version_diff = (version1 > version2) ? version1 - version2 : version2 - version1;
+ if (version_diff > 3.0f) return 0;
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " valid_triple: (%d,%d), (%d,%d), (%d,%d) ms=[%.1f,%.1f,%.1f] d=[%d,%d,%d] v=[%.1f,%.1f] hyp_ratio=%.2f -> PASS\n", // DEBUG
+ p0->x, p0->y, p1->x, p1->y, p2->x, p2->y, // DEBUG
+ ms0, ms1, ms2, min_d, mid_d, max_d, version1, version2, hyp_ratio); // DEBUG
+ } // DEBUG
+
+ /* For rotated codes, the module size from line scanning can be inflated by up to sqrt(2),
+ * which throws off the version calculation. Rather than rejecting based on computed version,
+ * rely on the geometric checks (right triangle, consistent distances) and let the actual
+ * decode validate if it's a real QR code.
+ *
+ * Only reject if the version estimates are wildly inconsistent (differ by more than 5)
+ * or absurdly high (over 50), which indicates mismatched finder patterns. */
+ if (version1 > 50 || version2 > 50) return 0;
+
+ return 1;
+}
+
+static int scan_image_for_qr(Image *img, ChunkList *chunks) {
+ FinderPattern patterns[500];
+ int npatterns = find_finder_patterns(img, patterns, 500);
+
+ /* Dump all finder patterns found in this image */
+ if (debug_mode && npatterns > 0) { // DEBUG
+ debug_dump_finders(patterns, npatterns); // DEBUG
+ }
+
+ if (npatterns < 3) {
+ return 0; /* Need at least 3 finder patterns for a QR code */
+ }
+
+ /* Track which patterns have been used */
+ int *used = calloc(npatterns, sizeof(int));
+ if (!used) return 0;
+
+ int decoded_count = 0;
+
+ /* Try all combinations of 3 patterns to find valid QR codes */
+ for (int i = 0; i < npatterns - 2 && decoded_count < MAX_CHUNKS; i++) {
+ if (used[i]) continue;
+
+ for (int j = i + 1; j < npatterns - 1; j++) {
+ if (used[j]) continue;
+
+ for (int k = j + 1; k < npatterns; k++) {
+ if (used[k]) continue;
+
+ /* Check if these three patterns could form a valid QR code */
+ if (!valid_qr_triple(&patterns[i], &patterns[j], &patterns[k])) {
+ continue;
+ }
+
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " Attempting decode with patterns %d,%d,%d\n", i, j, k); // DEBUG
+ } // DEBUG
+
+ /* Try to decode this QR code */
+ FinderPattern triple[3] = {patterns[i], patterns[j], patterns[k]};
+ char qr_content[4096];
+ int len = decode_qr_from_finders(img, triple, 3, qr_content, sizeof(qr_content));
+
+ if (len > 0) {
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " QR decoded (%d chars): '%s'\n", len, qr_content); // DEBUG
+ } // DEBUG
+
+ /* Parse the chunk label */
+ char type;
+ int index, total;
+ const char *data_start;
+
+ if (parse_chunk_label(qr_content, &type, &index, &total, &data_start)) {
+ /* Debug dump: decoded content */
+ debug_dump_decoded(qr_content, len, type, index, total); // DEBUG
+
+ /* Base64 decode the data portion */
+ uint8_t decoded[MAX_CHUNK_SIZE];
+ int decoded_len = base64_decode(data_start, strlen(data_start),
+ decoded, sizeof(decoded));
+
+ if (decoded_len > 0) {
+ if (debug_finder) { // DEBUG
+ fprintf(stderr, " -> chunk %c%02d/%02d, %d bytes, base64: '%.10s...%s'\n", // DEBUG
+ type, index, total, decoded_len, data_start, data_start + strlen(data_start) - 10); // DEBUG
+ } // DEBUG
+ chunk_list_add(chunks, type, index, total, decoded, decoded_len);
+ decoded_count++;
+
+ /* Mark these patterns as used */
+ used[i] = used[j] = used[k] = 1;
+ goto next_i; /* Move to next starting pattern */
+ }
+ }
+ }
+ }
+ }
+ next_i:;
+ }
+
+ free(used);
+ return decoded_count;
+}
+
+/* ============================================================================
+ * MAIN PROGRAM
+ * ============================================================================ */
+
+static void print_usage(const char *prog) {
+ fprintf(stderr, "Usage: %s [options] <image.pbm> [image2.pbm ...]\n", prog);
+ fprintf(stderr, "\nRestores qr-backup paper backups from PBM images.\n");
+ fprintf(stderr, "Supports GPG symmetric encryption (AES-128/192/256) and gzip compression.\n");
+ fprintf(stderr, "\nOptions:\n");
+ fprintf(stderr, " -p <password> Decrypt GPG-encrypted backup\n");
+ fprintf(stderr, " -o <file> Output to file (default: stdout)\n");
+ fprintf(stderr, " -v Verbose output (-vv for more detail)\n");
+ fprintf(stderr, " --debug Write intermediate files (debug_*.txt/bin)\n");
+ fprintf(stderr, " -h Show this help\n");
+ fprintf(stderr, "\nExamples:\n");
+ fprintf(stderr, " %s backup.pbm # Basic restore\n", prog);
+ fprintf(stderr, " %s -p secret backup.pbm # Decrypt and restore\n", prog);
+ fprintf(stderr, " %s -v page1.pbm page2.pbm # Multi-page restore\n", prog);
+ fprintf(stderr, " %s --debug backup.pbm # Debug output\n", prog);
+}
+
+#ifndef UNIT_TEST_BUILD
+int main(int argc, char **argv) {
+ const char *password = NULL;
+ const char *output_file = NULL;
+ int verbose = 0;
+ int first_file = 1;
+
+ /* Parse arguments */
+ for (int i = 1; i < argc; i++) {
+ if (argv[i][0] == '-') {
+ if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
+ password = argv[++i];
+ } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
+ output_file = argv[++i];
+ } else if (strcmp(argv[i], "-v") == 0) {
+ verbose++;
+ } else if (strcmp(argv[i], "-vv") == 0) {
+ verbose = 2;
+ } else if (strcmp(argv[i], "--debug") == 0) {
+ debug_mode = 1; // DEBUG
+ verbose = 2; /* Debug mode implies verbose */
+ } else if (strcmp(argv[i], "-h") == 0) {
+ print_usage(argv[0]);
+ return 0;
+ } else {
+ fprintf(stderr, "Unknown option: %s\n", argv[i]);
+ return 1;
+ }
+ } else {
+ if (first_file) first_file = i;
+ }
+ }
+
+ if (first_file == 0 || first_file >= argc) {
+ print_usage(argv[0]);
+ return 1;
+ }
+
+ /* Enable debug output for high verbosity */
+ if (verbose >= 2) { // VERBOSE
+ debug_finder = 1; // DEBUG
+ } // VERBOSE
+
+ /* Initialize chunk list */
+ ChunkList chunks;
+ chunk_list_init(&chunks);
+
+ /* Process each input image */
+ for (int i = first_file; i < argc; i++) {
+ if (argv[i][0] == '-') continue;
+
+ if (verbose) { // VERBOSE
+ fprintf(stderr, "Processing: %s\n", argv[i]); // VERBOSE
+ } // VERBOSE
+
+ Image *img = image_read_pbm(argv[i]);
+ if (!img) {
+ fprintf(stderr, "Failed to read image: %s\n", argv[i]);
+ continue;
+ }
+
+ int found = scan_image_for_qr(img, &chunks);
+ if (verbose) { // VERBOSE
+ fprintf(stderr, " Found %d QR codes\n", found); // VERBOSE
+ } // VERBOSE
+
+ image_free(img);
+ }
+
+ if (chunks.count == 0) {
+ fprintf(stderr, "No QR codes found in input images\n");
+ chunk_list_free(&chunks);
+ return 1;
+ }
+
+ if (verbose) { // VERBOSE
+ fprintf(stderr, "Total chunks collected: %d\n", chunks.count); // VERBOSE
+ } // VERBOSE
+
+ /* Sort and deduplicate chunks */
+ chunk_list_sort(&chunks);
+ debug_dump_chunks(&chunks, "collected"); // DEBUG
+ chunk_list_dedupe(&chunks);
+ debug_dump_chunks(&chunks, "deduped"); // DEBUG
+
+ if (verbose) { // VERBOSE
+ fprintf(stderr, "After deduplication: %d chunks\n", chunks.count); // VERBOSE
+ } // VERBOSE
+
+ /* Count normal and parity chunks */
+ int total_normal = 0, total_parity = 0;
+ for (int i = 0; i < chunks.count; i++) {
+ if (chunks.chunks[i].type == 'N') {
+ if (chunks.chunks[i].total > total_normal) {
+ total_normal = chunks.chunks[i].total;
+ }
+ } else if (chunks.chunks[i].type == 'P') {
+ if (chunks.chunks[i].total > total_parity) {
+ total_parity = chunks.chunks[i].total;
+ }
+ }
+ }
+
+ /* Attempt erasure recovery if needed */
+ if (!erasure_recover(&chunks, total_normal, total_parity)) {
+ fprintf(stderr, "Warning: Could not recover all missing chunks\n");
+ }
+
+ /* Assemble data */
+ int data_len;
+ uint8_t *data = assemble_data(&chunks, &data_len);
+ chunk_list_free(&chunks);
+
+ if (!data || data_len == 0) {
+ fprintf(stderr, "Failed to assemble data\n");
+ return 1;
+ }
+
+ if (verbose) { // VERBOSE
+ fprintf(stderr, "Assembled data: %d bytes\n", data_len); // VERBOSE
+ fprintf(stderr, "First 16 bytes: "); // VERBOSE
+ for (int i = 0; i < 16 && i < data_len; i++) { // VERBOSE
+ fprintf(stderr, "%02x ", data[i]); // VERBOSE
+ } // VERBOSE
+ fprintf(stderr, "\n"); // VERBOSE
+ } // VERBOSE
+
+ debug_dump_assembled(data, data_len); // DEBUG
+
+ /* Decrypt if GPG encrypted and password provided */
+ if (is_gpg_encrypted(data, data_len)) {
+ if (!password) {
+ fprintf(stderr, "Error: Data is GPG encrypted but no password provided (-p)\n");
+ free(data);
+ return 1;
+ }
+
+ if (verbose) { // VERBOSE
+ fprintf(stderr, "Decrypting GPG data...\n"); // VERBOSE
+ } // VERBOSE
+
+ int decrypted_len;
+ uint8_t *decrypted = gpg_decrypt(data, data_len, password, &decrypted_len);
+
+ if (decrypted) {
+ free(data);
+ data = decrypted;
+ data_len = decrypted_len;
+
+ if (verbose) { // VERBOSE
+ fprintf(stderr, "Decrypted %d bytes\n", data_len); // VERBOSE
+ } // VERBOSE
+ } else {
+ fprintf(stderr, "GPG decryption failed\n");
+ free(data);
+ return 1;
+ }
+ }
+
+ /* Mark legacy functions as used for API compatibility */
+ (void)aes_cbc_decrypt;
+ (void)xtime;
+ (void)rs_check_syndromes;
+
+ /* Decompress if gzip */
+ uint8_t *final_data = data;
+ int final_len = data_len;
+
+ if (data_len >= 2 && data[0] == 0x1f && data[1] == 0x8b) {
+ if (verbose) { // VERBOSE
+ fprintf(stderr, "Decompressing gzip data...\n"); // VERBOSE
+ } // VERBOSE
+ int decompressed_len;
+ uint8_t *decompressed = gzip_decompress(data, data_len, &decompressed_len);
+ if (decompressed) {
+ free(data);
+ final_data = decompressed;
+ final_len = decompressed_len;
+ debug_dump_decompressed(final_data, final_len); // DEBUG
+ } else {
+ fprintf(stderr, "Warning: Decompression failed, outputting raw data\n");
+ }
+ } else {
+ /* Not gzip compressed, still dump as "decompressed" */
+ debug_dump_decompressed(final_data, final_len); // DEBUG
+ }
+
+ /* Compute and display SHA256 */
+ uint8_t hash[32];
+ sha256(final_data, final_len, hash);
+ fprintf(stderr, "SHA256: ");
+ print_sha256(hash);
+
+ /* Output data */
+ FILE *out = stdout;
+ if (output_file) {
+ out = fopen(output_file, "wb");
+ if (!out) {
+ fprintf(stderr, "Failed to open output file: %s\n", output_file);
+ free(final_data);
+ return 1;
+ }
+ }
+
+ fwrite(final_data, 1, final_len, out);
+
+ if (output_file) {
+ fclose(out);
+ }
+
+ free(final_data);
+
+ if (verbose) { // VERBOSE
+ fprintf(stderr, "Restored %d bytes\n", final_len); // VERBOSE
+ } // VERBOSE
+
+ return 0;
+}
+#endif /* UNIT_TEST_BUILD */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <math.h>
+typedef struct {
+ float x, y;
+} Point2D;
+typedef struct {
+ float h[3][3]; /* h[2][2] is typically normalized to 1 */
+} HomographyMatrix;
+typedef struct {
+ int version, size;
+ float module_size;
+ HomographyMatrix homography;
+} QRTransform;
+#define MAX_QR_VERSION 40
+#define MAX_QR_MODULES (17 + 4 * MAX_QR_VERSION) /* 177 for version 40 */
+#define MAX_CHUNKS 1024
+#define MAX_CHUNK_SIZE 4096
+#define MAX_DATA_SIZE (MAX_CHUNKS * MAX_CHUNK_SIZE)
+typedef struct {
+ int width;
+ int height;
+ uint8_t *data; /* 1 = black, 0 = white */
+} Image;
+typedef struct {
+ char type; /* 'N' for normal, 'P' for parity */
+ int index; /* chunk number (1-based) */
+ int total; /* total chunks of this type */
+ uint8_t *data; /* decoded data */
+ int data_len;
+} Chunk;
+typedef struct {
+ Chunk *chunks;
+ int count;
+ int capacity;
+} ChunkList;
+static int skip_ws(FILE *f) {
+ int c;
+ while ((c = fgetc(f)) != EOF) {
+ if (c == '#') while ((c = fgetc(f)) != EOF && c != '\n');
+ else if (c > ' ') { ungetc(c, f); return 1; }
+ }
+ return 0;
+}
+static void adaptive_threshold(uint8_t *gray, uint8_t *binary,
+ int width, int height) {
+ int bs = 32; /* Block size */
+ for (int by = 0; by < height; by += bs) {
+ for (int bx = 0; bx < width; bx += bs) {
+ /* Compute mean over 2x block for smoothing */
+ int bw = (bx + bs * 2 < width) ? bs * 2 : width - bx;
+ int bh = (by + bs * 2 < height) ? bs * 2 : height - by;
+ int sum = 0;
+ for (int y = by; y < by + bh; y++)
+ for (int x = bx; x < bx + bw; x++) sum += gray[y * width + x];
+ int thresh = sum / (bw * bh) - 10;
+ if (thresh < 20) thresh = 20;
+ /* Apply to 1x block */
+ int ew = (bx + bs < width) ? bs : width - bx;
+ int eh = (by + bs < height) ? bs : height - by;
+ for (int y = by; y < by + eh; y++)
+ for (int x = bx; x < bx + ew; x++) {
+ int idx = y * width + x;
+ binary[idx] = (gray[idx] < thresh) ? 1 : 0;
+ }
+ }
+ }
+}
+static Image *image_read_pbm(const char *filename) {
+ FILE *f = fopen(filename, "rb");
+ if (!f) return NULL;
+ Image *img = calloc(1, sizeof(Image));
+ uint8_t *gray = NULL;
+ char magic[3] = {0};
+ if (fread(magic, 1, 2, f) != 2) goto fail;
+ int fmt = 0;
+ if (magic[1] == '1') fmt = 1; else if (magic[1] == '4') fmt = 4;
+ else if (magic[1] == '2') fmt = 2; else if (magic[1] == '5') fmt = 5;
+ if (magic[0] != 'P' || !fmt) goto fail;
+ if (!skip_ws(f) || fscanf(f, "%d", &img->width) != 1) goto fail;
+ if (!skip_ws(f) || fscanf(f, "%d", &img->height) != 1) goto fail;
+ int maxval = 1;
+ if (fmt == 2 || fmt == 5) {
+ if (!skip_ws(f) || fscanf(f, "%d", &maxval) != 1) goto fail;
+ if (maxval <= 0) maxval = 255;
+ }
+ fgetc(f);
+ int n = img->width * img->height;
+ img->data = malloc(n);
+ if (fmt == 4) {
+ int rb = (img->width + 7) / 8;
+ uint8_t *row = malloc(rb);
+ for (int y = 0; y < img->height; y++) {
+ if (fread(row, 1, rb, f) != (size_t)rb) { free(row); goto fail; }
+ for (int x = 0; x < img->width; x++)
+ img->data[y * img->width + x] = (row[x / 8] >> (7 - x % 8)) & 1;
+ }
+ free(row);
+ } else if (fmt == 1) {
+ for (int i = 0; i < n; i++) {
+ int c; while ((c = fgetc(f)) != EOF && c <= ' ');
+ if (c == EOF) goto fail;
+ img->data[i] = (c == '1') ? 1 : 0;
+ }
+ } else { /* fmt == 2 or 5: grayscale */
+ gray = malloc(n);
+ if (fmt == 5) {
+ if (fread(gray, 1, n, f) != (size_t)n) goto fail;
+ } else {
+ for (int i = 0; i < n; i++) {
+ int val; if (fscanf(f, "%d", &val) != 1) goto fail;
+ gray[i] = val;
+ }
+ }
+ if (maxval != 255)
+ for (int i = 0; i < n; i++) gray[i] = gray[i] * 255 / maxval;
+ adaptive_threshold(gray, img->data, img->width, img->height);
+ free(gray); gray = NULL;
+ }
+ fclose(f);
+ return img;
+fail:
+ free(gray); if (img) free(img->data); free(img); fclose(f); return NULL;
+}
+static void image_free(Image *img) { if (img) { free(img->data); free(img); } }
+static int image_get(Image *img, int x, int y) {
+ if (x < 0 || x >= img->width || y < 0 || y >= img->height) return 0;
+ return img->data[y * img->width + x];
+}
+typedef struct {
+ int x, y; /* Center position */
+ float module_size; /* Estimated module size (geometric mean of x/y) */
+ float module_size_x; /* Horizontal module size (for non-uniform scaling) */
+ float module_size_y; /* Vertical module size (for non-uniform scaling) */
+} FinderPattern;
+typedef struct {
+ int pos; /* Position of scan line (y for horiz, x for vert) */
+ int bound_min; /* Start of pattern on scan line */
+ int bound_max; /* End of pattern on scan line */
+} FinderLine;
+typedef struct {
+ int pos_min, pos_max; /* Range of scan positions */
+ int center_min, center_max; /* Min/max of centers */
+ int center_sum; /* Sum of centers for averaging */
+ int count; /* Number of lines in this range */
+ float module_sum; /* Sum of module sizes for averaging */
+} FinderRange;
+#define MAX_FINDER_LINES 16000
+#define INITIAL_FINDER_RANGES 128
+static float check_finder_ratio_ex(int *counts, float tolerance) {
+ int total = counts[0] + counts[1] + counts[2] + counts[3] + counts[4];
+ if (total < 7) return 0;
+ if (!counts[0] || !counts[1] || !counts[2] || !counts[3] || !counts[4])
+ return 0;
+ float module = total / 7.0f, var = module * tolerance * 1.2f;
+ float center_ratio = (float)counts[2] / total;
+ if (center_ratio < 0.25f || center_ratio > 0.55f) return 0;
+ for (int i = 0; i < 5; i += (i == 1 ? 2 : 1)) /* Check 0,1,3,4 (sides) */
+ if (counts[i] < module - var || counts[i] > module + var) return 0;
+ if (counts[2] < 2.0f * module || counts[2] > 4.0f * module) return 0;
+ return module;
+}
+static int add_finder_line(FinderLine *lines, int count, int max,
+ int pos, int bmin, int bmax) {
+ if (count < max) {
+ lines[count].pos = pos;
+ lines[count].bound_min = bmin;
+ lines[count].bound_max = bmax;
+ }
+ return count < max ? count + 1 : count;
+}
+static int ranges_overlap(int a0, int a1, int b0, int b1) {
+ int ov = ((a1 < b1 ? a1 : b1) - (a0 > b0 ? a0 : b0));
+ int smaller = ((a1-a0) < (b1-b0)) ? (a1-a0) : (b1-b0);
+ return ov >= 0 && ov >= smaller / 2;
+}
+static int cluster_finder_lines(FinderLine *lines, int nlines,
+ FinderRange **ranges_ptr, int *capacity_ptr) {
+ if (nlines == 0) return 0;
+ FinderRange *ranges = *ranges_ptr;
+ int capacity = *capacity_ptr;
+ int nranges = 0;
+ typedef struct {
+ int range_idx; /* Index into ranges array */
+ int last_bound_min; /* Last line's bound_min */
+ int last_bound_max; /* Last line's bound_max */
+ int last_pos; /* Position where this cluster was last updated */
+ } ActiveCluster;
+ #define MAX_GAP 3
+ ActiveCluster active[128];
+ int nactive = 0;
+ int last_pos = -999; /* Position of last processed line */
+ for (int i = 0; i < nlines; i++) {
+ FinderLine *line = &lines[i];
+ if (line->pos != last_pos) {
+ int write_idx = 0;
+ for (int a = 0; a < nactive; a++) {
+ if (line->pos - active[a].last_pos <= MAX_GAP + 1) {
+ if (write_idx != a) {
+ active[write_idx] = active[a];
+ }
+ write_idx++;
+ }
+ }
+ nactive = write_idx;
+ last_pos = line->pos;
+ }
+ int matched = -1;
+ int line_center = (line->bound_min + line->bound_max) / 2;
+ for (int a = 0; a < nactive; a++) {
+ if (ranges_overlap(line->bound_min, line->bound_max,
+ active[a].last_bound_min, active[a].last_bound_max)) {
+ int gap = line->pos - active[a].last_pos;
+ if (gap > 1) {
+ FinderRange *r = &ranges[active[a].range_idx];
+ int cluster_center_min = r->center_min;
+ int cluster_center_max = r->center_max;
+ if (line_center < cluster_center_min - 10 ||
+ line_center > cluster_center_max + 10) {
+ continue; /* Line center too far from cluster */
+ }
+ }
+ matched = a;
+ break;
+ }
+ }
+ int ri, ai;
+ if (matched >= 0) {
+ ri = active[matched].range_idx; ai = matched;
+ } else if (nactive < 128) {
+ if (nranges >= capacity) {
+ capacity *= 2;
+ ranges = realloc(ranges, capacity * sizeof(FinderRange));
+ *ranges_ptr = ranges; *capacity_ptr = capacity;
+ }
+ ri = nranges++; ai = nactive++;
+ active[ai].range_idx = ri;
+ ranges[ri].pos_min = line->pos;
+ ranges[ri].center_min = line->bound_max;
+ ranges[ri].center_max = line->bound_min;
+ ranges[ri].center_sum = 0;
+ ranges[ri].module_sum = 0;
+ ranges[ri].count = 0;
+ } else continue;
+ FinderRange *r = &ranges[ri];
+ r->pos_max = line->pos;
+ if (line->bound_min < r->center_min) r->center_min = line->bound_min;
+ if (line->bound_max > r->center_max) r->center_max = line->bound_max;
+ r->center_sum += (line->bound_min + line->bound_max) / 2;
+ r->module_sum += (line->bound_max - line->bound_min) / 7.0f;
+ r->count++;
+ active[ai].last_bound_min = line->bound_min;
+ active[ai].last_bound_max = line->bound_max;
+ active[ai].last_pos = line->pos;
+ }
+ int valid = 0;
+ for (int i = 0; i < nranges; i++) {
+ FinderRange *r = &ranges[i];
+ int min_lines = (int)(r->module_sum / r->count * 1.4f);
+ if (min_lines < 3) min_lines = 3;
+ if (r->count >= min_lines && r->pos_max - r->pos_min >= 4)
+ ranges[valid++] = *r;
+ }
+ return valid;
+}
+static int compare_finder_lines_by_pos(const void *a, const void *b) {
+ const FinderLine *la = (const FinderLine *)a;
+ const FinderLine *lb = (const FinderLine *)b;
+ return la->pos - lb->pos;
+}
+static int scan_finder_lines(Image *img, FinderLine *lines, int vert) {
+ int outer = vert ? img->width : img->height;
+ int inner = vert ? img->height : img->width, nlines = 0;
+ for (int o = 0; o < outer; o++) {
+ int counts[5] = {0}, state = 0;
+ for (int i = 0; i < inner; i++) {
+ int pixel = vert ? image_get(img, o, i) : image_get(img, i, o);
+ int expected = (state & 1); /* 1,3,5=black, 0,2,4=white */
+ if (state == 0) { if (pixel) { state = 1; counts[0] = 1; } }
+ else if (pixel == expected) { counts[state - 1]++; }
+ else if (state < 5) { state++; counts[state - 1] = 1; }
+ else { /* state == 5, got white */
+ if (check_finder_ratio_ex(counts, 0.8f) > 0) {
+ int w = counts[0]+counts[1]+counts[2]+counts[3]+counts[4];
+ nlines = add_finder_line(lines, nlines,
+ MAX_FINDER_LINES, o, i - w, i - 1);
+ }
+ counts[0] = counts[2]; counts[1] = counts[3];
+ counts[2] = counts[4]; counts[3] = 1; counts[4] = 0;
+ state = 4;
+ }
+ }
+ }
+ return nlines;
+}
+static int find_finder_patterns(Image *img, FinderPattern *patterns,
+ int max_patterns) {
+ FinderLine *h_lines = malloc(MAX_FINDER_LINES * sizeof(FinderLine));
+ FinderLine *v_lines = malloc(MAX_FINDER_LINES * sizeof(FinderLine));
+ int hrc = INITIAL_FINDER_RANGES, vrc = INITIAL_FINDER_RANGES;
+ FinderRange *h_ranges = malloc(hrc * sizeof(FinderRange));
+ FinderRange *v_ranges = malloc(vrc * sizeof(FinderRange));
+ int nhl = scan_finder_lines(img, h_lines, 0);
+ int nvl = scan_finder_lines(img, v_lines, 1);
+ qsort(h_lines, nhl, sizeof(FinderLine), compare_finder_lines_by_pos);
+ qsort(v_lines, nvl, sizeof(FinderLine), compare_finder_lines_by_pos);
+ int nhr = cluster_finder_lines(h_lines, nhl, &h_ranges, &hrc);
+ int nvr = cluster_finder_lines(v_lines, nvl, &v_ranges, &vrc);
+ int count = 0;
+ for (int hi = 0; hi < nhr && count < max_patterns; hi++) {
+ FinderRange *hr = &h_ranges[hi];
+ int fx = hr->center_sum / hr->count;
+ float hm = hr->module_sum / hr->count;
+ for (int vi = 0; vi < nvr && count < max_patterns; vi++) {
+ FinderRange *vr = &v_ranges[vi];
+ int fy = vr->center_sum / vr->count;
+ float vm = vr->module_sum / vr->count;
+ if (fx < vr->pos_min - hm*1.5f || fx > vr->pos_max + hm*1.5f)
+ continue;
+ if (fy < hr->pos_min - vm*1.5f || fy > hr->pos_max + vm*1.5f)
+ continue;
+ /* Check module size ratio */
+ float ratio = (hm > vm) ? hm / vm : vm / hm;
+ if (ratio > ((hm > 3.0f || vm > 3.0f) ? 3.0f : 2.5f)) continue;
+ /* Check for duplicates */
+ float fm = sqrtf(hm * vm);
+ int is_dup = 0;
+ for (int i = 0; i < count && !is_dup; i++) {
+ int dx = patterns[i].x - fx, dy = patterns[i].y - fy;
+ is_dup = dx*dx + dy*dy < fm * fm * 16;
+ }
+ if (!is_dup) {
+ patterns[count].x = fx;
+ patterns[count].y = fy;
+ patterns[count].module_size = fm;
+ patterns[count].module_size_x = hm;
+ patterns[count].module_size_y = vm;
+ count++;
+ }
+ }
+ }
+ free(h_lines);
+ free(v_lines);
+ free(h_ranges);
+ free(v_ranges);
+ return count;
+}
+static int qr_version_to_size(int v) { return 17 + 4 * v; }
+typedef struct {
+ uint8_t modules[MAX_QR_MODULES][MAX_QR_MODULES];
+ int size;
+ int version;
+} QRCode;
+static void get_alignment_positions(int version, int pos[8]) {
+ if (version == 1) { pos[0] = 0; return; }
+ int size = 17 + version * 4, last = size - 7, num = version / 7 + 2;
+ int total = last - 6, iv = num - 1;
+ int step = ((total * 2 + iv) / (iv * 2) + 1) & ~1;
+ pos[0] = 6;
+ for (int i = num - 1; i >= 1; i--) pos[i] = last - (num - 1 - i) * step;
+ pos[num] = 0;
+}
+static int is_function_module(int version, int x, int y) {
+ int s = qr_version_to_size(version);
+ /* Finder patterns and format info */
+ if ((x < 9 && y < 9) || (x < 8 && y >= s-8) || (x >= s-8 && y < 9))
+ return 1;
+ if ((y == 8 && (x < 9 || x >= s-8)) || (x == 8 && (y < 9 || y >= s-8)))
+ return 1;
+ if (x == 6 || y == 6) return 1; /* Timing patterns */
+ /* Alignment patterns */
+ if (version >= 2) {
+ int pos[8]; get_alignment_positions(version, pos);
+ for (int i = 0; pos[i]; i++)
+ for (int j = 0; pos[j]; j++) {
+ int ax = pos[i], ay = pos[j];
+ if ((ax < 9 && ay < 9) || (ax < 9 && ay >= s-8) ||
+ (ax >= s-8 && ay < 9)) continue;
+ if (x >= ax-2 && x <= ax+2 && y >= ay-2 && y <= ay+2) return 1;
+ }
+ }
+ /* Version info */
+ if (version >= 7) {
+ if ((x < 6 && y >= s-11 && y < s-8) ||
+ (y < 6 && x >= s-11 && x < s-8)) return 1;
+ }
+ return 0;
+}
+static int sample_module_transform_ex(Image *img, const QRTransform *transform,
+ int mx, int my, int use_aa);
+static int read_format_info(QRCode *qr, Image *img,
+ const QRTransform *transform) {
+ static const int xs[15] = {8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0};
+ static const int ys[15] = {0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8};
+ uint32_t format1 = 0;
+ for (int i = 14; i >= 0; i--) {
+ int bit;
+ if (img) bit = sample_module_transform_ex(img, transform,
+ xs[i], ys[i], 0);
+ else bit = qr->modules[ys[i]][xs[i]] & 1;
+ format1 = (format1 << 1) | bit;
+ }
+ return format1 ^ 0x5412;
+}
+static const uint16_t valid_format_unmasked[32] = {
+ 0x0000, 0x0537, 0x0A6E, 0x0F59, 0x11EB, 0x14DC, 0x1B85, 0x1EB2,
+ 0x23D6, 0x26E1, 0x29B8, 0x2C8F, 0x323D, 0x370A, 0x3853, 0x3D64,
+ 0x429B, 0x47AC, 0x48F5, 0x4DC2, 0x5370, 0x5647, 0x591E, 0x5C29,
+ 0x614D, 0x647A, 0x6B23, 0x6E14, 0x70A6, 0x7591, 0x7AC8, 0x7FFF
+};
+static int validate_format_info_with_correction(int format_info) {
+ int remainder = format_info;
+ for (int i = 14; i >= 10; i--) {
+ if (remainder & (1 << i))
+ remainder ^= (0x537 << (i - 10));
+ }
+ if (remainder == 0) return format_info; /* Already valid */
+ int best_match = -1, best_distance = 5; /* Accept distance <= 4 */
+ for (int i = 0; i < 32; i++) {
+ int diff = format_info ^ valid_format_unmasked[i];
+ int distance = 0;
+ while (diff) {
+ distance += diff & 1;
+ diff >>= 1;
+ }
+ if (distance < best_distance) {
+ best_distance = distance;
+ best_match = valid_format_unmasked[i];
+ }
+ }
+ return best_match; /* Returns -1 if no match within distance 3 */
+}
+static uint8_t gf_exp[512], gf_log[256];
+static void gf_init(void) {
+ static int done = 0; if (done) return; done = 1;
+ int x = 1;
+ for (int i = 0; i < 255; i++) {
+ gf_exp[i] = x; gf_log[x] = i;
+ x = (x << 1) ^ ((x >> 7) * 0x11d);
+ }
+ for (int i = 255; i < 512; i++) gf_exp[i] = gf_exp[i - 255];
+}
+static uint8_t gf_mul(uint8_t a, uint8_t b) {
+ return (a && b) ? gf_exp[gf_log[a] + gf_log[b]] : 0;
+}
+static uint8_t gf_div(uint8_t a, uint8_t b) {
+ return (a && b) ? gf_exp[(gf_log[a] + 255 - gf_log[b]) % 255] : 0;
+}
+static uint8_t gf_inv(uint8_t a) {
+ return a ? gf_exp[255 - gf_log[a]] : 0;
+}
+static void rs_calc_syndromes(uint8_t *data, int total_len, int ecc_len,
+ uint8_t *syndromes) {
+ gf_init();
+ for (int i = 0; i < ecc_len; i++) {
+ uint8_t s = 0;
+ for (int j = 0; j < total_len; j++) {
+ s = gf_mul(s, gf_exp[i]) ^ data[j];
+ }
+ syndromes[i] = s;
+ }
+}
+static int rs_berlekamp_massey(uint8_t *syndromes, int ecc_len,
+ uint8_t *sigma) {
+ gf_init();
+ uint8_t C[256] = {0}; /* Connection polynomial */
+ uint8_t B[256] = {0}; /* Previous connection polynomial */
+ C[0] = 1;
+ B[0] = 1;
+ int L = 0, m = 1; /* L=LFSR len, m=iterations since L update */
+ uint8_t b = 1; /* Previous discrepancy */
+ for (int n = 0; n < ecc_len; n++) {
+ uint8_t d = syndromes[n];
+ for (int i = 1; i <= L; i++) {
+ d ^= gf_mul(C[i], syndromes[n - i]);
+ }
+ if (d == 0) {
+ m++;
+ } else if (2 * L <= n) {
+ uint8_t T[256];
+ memcpy(T, C, sizeof(T));
+ uint8_t coef = gf_div(d, b);
+ for (int i = 0; i < ecc_len - m; i++) {
+ C[i + m] ^= gf_mul(coef, B[i]);
+ }
+ memcpy(B, T, sizeof(B));
+ L = n + 1 - L;
+ b = d;
+ m = 1;
+ } else {
+ uint8_t coef = gf_div(d, b);
+ for (int i = 0; i < ecc_len - m; i++) {
+ C[i + m] ^= gf_mul(coef, B[i]);
+ }
+ m++;
+ }
+ }
+ memcpy(sigma, C, ecc_len + 1);
+ return L;
+}
+static int rs_chien_search(uint8_t *sigma, int num_errors, int n,
+ int *positions) {
+ gf_init();
+ int found = 0;
+ for (int i = 0; i < n; i++) {
+ uint8_t sum = sigma[0]; /* = 1 */
+ for (int j = 1; j <= num_errors; j++) {
+ uint8_t power = ((255 - i) * j) % 255;
+ sum ^= gf_mul(sigma[j], gf_exp[power]);
+ }
+ if (sum == 0)
+ positions[found++] = n - 1 - i;
+ }
+ return (found == num_errors) ? found : -1;
+}
+static void rs_forney(uint8_t *syndromes, uint8_t *sigma, int num_errors,
+ int *positions, int n, uint8_t *values) {
+ gf_init();
+ uint8_t omega[256] = {0};
+ for (int i = 0; i < num_errors; i++) {
+ uint8_t v = 0;
+ for (int j = 0; j <= i; j++) {
+ v ^= gf_mul(syndromes[i - j], sigma[j]);
+ }
+ omega[i] = v;
+ }
+ uint8_t sigma_prime[256] = {0};
+ for (int i = 1; i <= num_errors; i += 2) {
+ sigma_prime[i - 1] = sigma[i];
+ }
+ for (int i = 0; i < num_errors; i++) {
+ int pos = positions[i];
+ int Xi_inv_power = (n - 1 - pos) % 255; /* Power of alpha for X_i^-1 */
+ uint8_t omega_val = 0;
+ for (int j = 0; j < num_errors; j++) {
+ omega_val ^= gf_mul(omega[j], gf_exp[(Xi_inv_power * j) % 255]);
+ }
+ uint8_t sigma_prime_val = 0;
+ for (int j = 0; j < num_errors; j++) {
+ int p = (Xi_inv_power * j) % 255;
+ sigma_prime_val ^= gf_mul(sigma_prime[j], gf_exp[p]);
+ }
+ if (sigma_prime_val != 0) {
+ values[i] = gf_div(omega_val, sigma_prime_val);
+ } else {
+ values[i] = 0;
+ }
+ }
+}
+static int rs_correct_errors(uint8_t *data, int data_len, int ecc_len) {
+ gf_init();
+ int total_len = data_len + ecc_len, max_errors = ecc_len / 2;
+ uint8_t syndromes[256];
+ rs_calc_syndromes(data, total_len, ecc_len, syndromes);
+ int all_zero = 1;
+ for (int i = 0; i < ecc_len; i++)
+ if (syndromes[i] != 0) { all_zero = 0; break; }
+ if (all_zero) return 0; /* No errors */
+ uint8_t sigma[256] = {0};
+ int num_errors = rs_berlekamp_massey(syndromes, ecc_len, sigma);
+ if (num_errors > max_errors) return -1;
+ int positions[256];
+ int found = rs_chien_search(sigma, num_errors, total_len, positions);
+ if (found != num_errors) return -1;
+ uint8_t values[256];
+ rs_forney(syndromes, sigma, num_errors, positions, total_len, values);
+ for (int i = 0; i < num_errors; i++) {
+ if (positions[i] >= 0 && positions[i] < total_len)
+ data[positions[i]] ^= values[i];
+ }
+ rs_calc_syndromes(data, total_len, ecc_len, syndromes);
+ for (int i = 0; i < ecc_len; i++)
+ if (syndromes[i] != 0) return -1;
+ return num_errors;
+}
+static int mask_bit(int m, int x, int y) {
+ switch (m) {
+ case 0: return (y+x) % 2 == 0; case 1: return y % 2 == 0;
+ case 2: return x % 3 == 0; case 3: return (y+x) % 3 == 0;
+ case 4: return (y/2 + x/3) % 2 == 0;
+ case 5: return (y*x) % 2 + (y*x) % 3 == 0;
+ case 6: return ((y*x) % 2 + (y*x) % 3) % 2 == 0;
+ default: return ((y+x) % 2 + (y*x) % 3) % 2 == 0;
+ }
+}
+static void unmask_qr(QRCode *qr, int mask) {
+ for (int y = 0; y < qr->size; y++)
+ for (int x = 0; x < qr->size; x++)
+ if (!is_function_module(qr->version, x, y) && mask_bit(mask, x, y))
+ qr->modules[y][x] ^= 1;
+}
+typedef struct { int bs, dw, ns; } RSBlockParams;
+typedef struct { int data_bytes; RSBlockParams ecc[4]; } VersionInfo;
+static const VersionInfo version_info[41] = {{0},
+{26,{{26,16,1},{26,19,1},{26,9,1},{26,13,1}}},
+{44,{{44,28,1},{44,34,1},{44,16,1},{44,22,1}}},
+{70,{{70,44,1},{70,55,1},{35,13,2},{35,17,2}}},
+{100,{{50,32,2},{100,80,1},{25,9,4},{50,24,2}}},
+{134,{{67,43,2},{134,108,1},{33,11,2},{33,15,2}}},
+{172,{{43,27,4},{86,68,2},{43,15,4},{43,19,4}}},
+{196,{{49,31,4},{98,78,2},{39,13,4},{32,14,2}}},
+{242,{{60,38,2},{121,97,2},{40,14,4},{40,18,4}}},
+{292,{{58,36,3},{146,116,2},{36,12,4},{36,16,4}}},
+{346,{{69,43,4},{86,68,2},{43,15,6},{43,19,6}}},
+{404,{{80,50,1},{101,81,4},{36,12,3},{50,22,4}}},
+{466,{{58,36,6},{116,92,2},{42,14,7},{46,20,4}}},
+{532,{{59,37,8},{133,107,4},{33,11,12},{44,20,8}}},
+{581,{{64,40,4},{145,115,3},{36,12,11},{36,16,11}}},
+{655,{{65,41,5},{109,87,5},{36,12,11},{54,24,5}}},
+{733,{{73,45,7},{122,98,5},{45,15,3},{43,19,15}}},
+{815,{{74,46,10},{135,107,1},{42,14,2},{50,22,1}}},
+{901,{{69,43,9},{150,120,5},{42,14,2},{50,22,17}}},
+{991,{{70,44,3},{141,113,3},{39,13,9},{47,21,17}}},
+{1085,{{67,41,3},{135,107,3},{43,15,15},{54,24,15}}},
+{1156,{{68,42,17},{144,116,4},{46,16,19},{50,22,17}}},
+{1258,{{74,46,17},{139,111,2},{37,13,34},{54,24,7}}},
+{1364,{{75,47,4},{151,121,4},{45,15,16},{54,24,11}}},
+{1474,{{73,45,6},{147,117,6},{46,16,30},{54,24,11}}},
+{1588,{{75,47,8},{132,106,8},{45,15,22},{54,24,7}}},
+{1706,{{74,46,19},{142,114,10},{46,16,33},{50,22,28}}},
+{1828,{{73,45,22},{152,122,8},{45,15,12},{53,23,8}}},
+{1921,{{73,45,3},{147,117,3},{45,15,11},{54,24,4}}},
+{2051,{{73,45,21},{146,116,7},{45,15,19},{53,23,1}}},
+{2185,{{75,47,19},{145,115,5},{45,15,23},{54,24,15}}},
+{2323,{{74,46,2},{145,115,13},{45,15,23},{54,24,42}}},
+{2465,{{74,46,10},{145,115,17},{45,15,19},{54,24,10}}},
+{2611,{{74,46,14},{145,115,17},{45,15,11},{54,24,29}}},
+{2761,{{74,46,14},{145,115,13},{46,16,59},{54,24,44}}},
+{2876,{{75,47,12},{151,121,12},{45,15,22},{54,24,39}}},
+{3034,{{75,47,6},{151,121,6},{45,15,2},{54,24,46}}},
+{3196,{{74,46,29},{152,122,17},{45,15,24},{54,24,49}}},
+{3362,{{74,46,13},{152,122,4},{45,15,42},{54,24,48}}},
+{3532,{{75,47,40},{147,117,20},{45,15,10},{54,24,43}}},
+{3706,{{75,47,18},{148,118,19},{45,15,20},{54,24,34}}}};
+static int deinterleave_block(uint8_t *raw, int total_bytes, int bc,
+ int dw, int bs, int ns, int block_idx,
+ uint8_t *block_out) {
+ int is_long = (block_idx >= ns);
+ int block_dw = is_long ? dw + 1 : dw;
+ int block_ecc = bs - dw;
+ int block_bs = is_long ? bs + 1 : bs;
+ int pos = 0;
+ for (int j = 0; j < block_dw; j++) {
+ int src_idx;
+ if (j < dw) {
+ src_idx = j * bc + block_idx;
+ } else {
+ src_idx = dw * bc + (block_idx - ns);
+ }
+ if (src_idx < total_bytes) {
+ block_out[pos++] = raw[src_idx];
+ }
+ }
+ int data_total = dw * bc + (bc - ns); /* Total data codewords */
+ for (int j = 0; j < block_ecc; j++) {
+ int src_idx = data_total + j * bc + block_idx;
+ if (src_idx < total_bytes) {
+ block_out[pos++] = raw[src_idx];
+ }
+ }
+ return block_bs;
+}
+static int deinterleave_and_correct(uint8_t *raw, int total_bytes,
+ int version, int ecc_level, uint8_t *data_out, int max_out,
+ int *errors_corrected, int *uncorrectable_out) {
+ if (version < 1 || version > 40) return 0;
+ const RSBlockParams *sb = &version_info[version].ecc[ecc_level];
+ int ns = sb->ns; /* Number of short blocks */
+ int dw = sb->dw; /* Data codewords per short block */
+ int bs = sb->bs; /* Total codewords per short block */
+ int short_total = ns * bs;
+ int remaining = total_bytes - short_total;
+ int nl = (remaining > 0) ? remaining / (bs + 1) : 0;
+ int bc = ns + nl; /* Total block count */
+ int block_ecc = bs - dw; /* ECC codewords per block */
+ int data_pos = 0, total_errors = 0, uncorrectable_blocks = 0;
+ for (int i = 0; i < bc && data_pos < max_out; i++) {
+ uint8_t block[256];
+ (void)deinterleave_block(raw, total_bytes, bc, dw, bs, ns, i, block);
+ int is_long = (i >= ns);
+ int block_dw = is_long ? dw + 1 : dw;
+ int corrected = rs_correct_errors(block, block_dw, block_ecc);
+ if (corrected < 0) {
+ uncorrectable_blocks++;
+ } else if (corrected > 0) {
+ total_errors += corrected;
+ }
+ for (int j = 0; j < block_dw && data_pos < max_out; j++) {
+ data_out[data_pos++] = block[j];
+ }
+ }
+ if (errors_corrected) *errors_corrected = total_errors;
+ if (uncorrectable_out) *uncorrectable_out = uncorrectable_blocks;
+ if (uncorrectable_blocks == bc) {
+ return -1;
+ }
+ return data_pos;
+}
+static int extract_qr_data(QRCode *qr, uint8_t *data, int max_len) {
+ int size = qr->size, bi = 0, bit = 7, up = 1;
+ memset(data, 0, max_len);
+ for (int col = size - 1; col >= 0; col -= 2) {
+ if (col == 6) col--;
+ int rstart = up ? size-1 : 0;
+ int rend = up ? -1 : size;
+ int rstep = up ? -1 : 1;
+ for (int row = rstart; row != rend; row += rstep)
+ for (int c = 0; c < 2 && col-c >= 0; c++)
+ if (!is_function_module(qr->version, col-c, row) &&
+ bi < max_len) {
+ if (qr->modules[row][col-c]) data[bi] |= (1 << bit);
+ if (--bit < 0) { bit = 7; bi++; }
+ }
+ up = !up;
+ }
+ return bi + (bit < 7 ? 1 : 0);
+}
+static int decode_qr_content(uint8_t *data, int data_len, int version,
+ char *output, int max_output) {
+ int bit_pos = 0, out_pos = 0;
+ #define READ_BITS(n) ({ \
+ int val = 0; \
+ for (int i = 0; i < (n); i++) { \
+ int byte_idx = bit_pos / 8; \
+ int bit_idx = 7 - (bit_pos % 8); \
+ if (byte_idx < data_len) { \
+ val = (val << 1) | ((data[byte_idx] >> bit_idx) & 1); \
+ } \
+ bit_pos++; \
+ } \
+ val; \
+ })
+ while (bit_pos < data_len * 8 && out_pos < max_output - 1) {
+ int mode = READ_BITS(4);
+ if (mode == 0) break; /* Terminator */
+ if (mode == 4) { /* Byte mode - only mode qr-backup uses */
+ int count_bits = version <= 9 ? 8 : 16;
+ int count = READ_BITS(count_bits);
+ for (int i = 0; i < count && out_pos < max_output - 1; i++)
+ output[out_pos++] = READ_BITS(8);
+ } else break;
+ }
+ output[out_pos] = '\0';
+ return out_pos;
+ #undef READ_BITS
+}
+static int distance_sq(int x1, int y1, int x2, int y2) {
+ int dx = x2 - x1, dy = y2 - y1; return dx * dx + dy * dy;
+}
+static int cross_product(int x1, int y1, int x2, int y2, int x3, int y3) {
+ return (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1);
+}
+static int identify_finder_roles(FinderPattern *fp, int *tl, int *tr, int *bl) {
+ int d[3] = {distance_sq(fp[0].x, fp[0].y, fp[1].x, fp[1].y),
+ distance_sq(fp[0].x, fp[0].y, fp[2].x, fp[2].y),
+ distance_sq(fp[1].x, fp[1].y, fp[2].x, fp[2].y)};
+ /* Corner opposite longest side is top-left */
+ int corner = (d[0] >= d[1] && d[0] >= d[2]) ? 2 : (d[1] >= d[2]) ? 1 : 0;
+ int p1 = (corner == 0) ? 1 : 0, p2 = (corner == 2) ? 1 : 2;
+ *tl = corner;
+ int cp = cross_product(fp[corner].x, fp[corner].y,
+ fp[p1].x, fp[p1].y, fp[p2].x, fp[p2].y);
+ *tr = (cp > 0) ? p1 : p2;
+ *bl = (cp > 0) ? p2 : p1;
+ return 1;
+}
+/* Closed-form homography from unit square to quadrilateral dst[0..3] */
+static void calculate_homography(Point2D dst[4], HomographyMatrix *H) {
+ float x0 = dst[0].x, y0 = dst[0].y, x1 = dst[1].x, y1 = dst[1].y;
+ float x2 = dst[2].x, y2 = dst[2].y, x3 = dst[3].x, y3 = dst[3].y;
+ float dx1 = x1 - x3, dy1 = y1 - y3, dx2 = x2 - x3, dy2 = y2 - y3;
+ float sx = x0 - x1 + x3 - x2, sy = y0 - y1 + y3 - y2;
+ float det = dx1 * dy2 - dx2 * dy1;
+ if (fabsf(det) < 1e-10f) det = 1e-10f;
+ float g = (sx * dy2 - dx2 * sy) / det;
+ float h = (dx1 * sy - sx * dy1) / det;
+ H->h[0][0] = x1 - x0 + g * x1;
+ H->h[0][1] = x2 - x0 + h * x2;
+ H->h[0][2] = x0;
+ H->h[1][0] = y1 - y0 + g * y1;
+ H->h[1][1] = y2 - y0 + h * y2;
+ H->h[1][2] = y0;
+ H->h[2][0] = g; H->h[2][1] = h; H->h[2][2] = 1.0f;
+}
+static Point2D apply_homography(const HomographyMatrix *H, float x, float y) {
+ Point2D result;
+ float w = H->h[2][0] * x + H->h[2][1] * y + H->h[2][2];
+ if (fabs(w) < 1e-10) w = 1e-10;
+ result.x = (H->h[0][0] * x + H->h[0][1] * y + H->h[0][2]) / w;
+ result.y = (H->h[1][0] * x + H->h[1][1] * y + H->h[1][2]) / w;
+ return result;
+}
+static QRTransform calculate_qr_transform(FinderPattern *fp, int tl,
+ int tr, int bl, int version_override) {
+ QRTransform transform = {0};
+ float dx_tr = fp[tr].x - fp[tl].x, dy_tr = fp[tr].y - fp[tl].y;
+ float dx_bl = fp[bl].x - fp[tl].x, dy_bl = fp[bl].y - fp[tl].y;
+ float dist_tr = sqrtf(dx_tr*dx_tr + dy_tr*dy_tr);
+ float dist_bl = sqrtf(dx_bl*dx_bl + dy_bl*dy_bl);
+ float gm = cbrtf(fp[tl].module_size * fp[tr].module_size *
+ fp[bl].module_size);
+ if (gm < 1.0f) gm = 1.0f;
+ float modules_est = (dist_tr + dist_bl) / (2.0f * gm);
+ int version = version_override > 0 ? version_override :
+ ((int)(modules_est + 7.5f) - 17 + 2) / 4;
+ if (version < 1) version = 1;
+ if (version > 40) version = 40;
+ int size = 17 + 4 * version;
+ transform.version = version;
+ transform.size = size;
+ transform.module_size = gm;
+ float span = size - 7;
+ float a = dx_tr / span, b = dx_bl / span;
+ float c = dy_tr / span, d = dy_bl / span;
+ float tx = fp[tl].x - 3.5f * (a + b);
+ float ty = fp[tl].y - 3.5f * (c + d);
+ Point2D dst[4] = {{tx, ty}, {a*size+tx, c*size+ty},
+ {b*size+tx, d*size+ty},
+ {(a+b)*size+tx, (c+d)*size+ty}};
+ calculate_homography(dst, &transform.homography);
+ return transform;
+}
+static int sample_module_transform_ex(Image *img, const QRTransform *tf,
+ int mx, int my, int use_aa) {
+ float u = (mx + 0.5f) / tf->size, v = (my + 0.5f) / tf->size;
+ Point2D p = apply_homography(&tf->homography, u, v);
+ if (!use_aa)
+ return image_get(img, (int)(p.x + 0.5f), (int)(p.y + 0.5f)) ? 1 : 0;
+ int black = 0;
+ for (int dy = -1; dy <= 1; dy++)
+ for (int dx = -1; dx <= 1; dx++) {
+ int ix = (int)(p.x + dx + 0.5f), iy = (int)(p.y + dy + 0.5f);
+ black += image_get(img, ix, iy) * ((dx|dy) ? 1 : 2);
+ }
+ return black > 5 ? 1 : 0;
+}
+
+static int decode_qr_from_finders(Image *img, FinderPattern *fp, int nfp,
+ char *output, int max_output) {
+ if (nfp < 3) {
+ return 0;
+ }
+ int tl, tr, bl;
+ if (!identify_finder_roles(fp, &tl, &tr, &bl)) {
+ return 0;
+ }
+ QRTransform initial_transform = calculate_qr_transform(fp, tl, tr, bl, 0);
+ int estimated_version = initial_transform.version;
+ int versions_to_try[10]; /* estimated + nearby + fallback */
+ int num_versions = 0;
+ versions_to_try[num_versions++] = estimated_version;
+ float module_size = initial_transform.module_size;
+ if (estimated_version >= 25 || module_size < 8.0f) {
+ for (int delta = -2; delta <= 2; delta++) {
+ int v = estimated_version + delta;
+ if (v >= 1 && v <= 40 && v != estimated_version) {
+ versions_to_try[num_versions++] = v;
+ }
+ }
+ }
+ for (int vi = 0; vi < num_versions; vi++) {
+ int try_version = versions_to_try[vi];
+ QRTransform transform = calculate_qr_transform(fp, tl, tr, bl, try_version);
+ QRCode qr;
+ qr.version = transform.version;
+ qr.size = transform.size;
+ for (int my = 0; my < transform.size; my++) {
+ for (int mx = 0; mx < transform.size; mx++) {
+ qr.modules[my][mx] = sample_module_transform_ex(img,
+ &transform, mx, my, 1);
+ }
+ }
+ int format_info = read_format_info(NULL, img, &transform);
+ int corrected_format = validate_format_info_with_correction(format_info);
+ if (corrected_format < 0) {
+ format_info = read_format_info(&qr, NULL, NULL);
+ corrected_format = validate_format_info_with_correction(format_info);
+ }
+ if (corrected_format < 0) {
+ continue; /* Try next version */
+ }
+ format_info = corrected_format;
+ int mask_pattern = (format_info >> 10) & 0x07;
+ int ecc_level = (format_info >> 13) & 0x03;
+ unmask_qr(&qr, mask_pattern);
+ uint8_t raw_codewords[4096];
+ int raw_len = extract_qr_data(&qr, raw_codewords, sizeof(raw_codewords));
+ uint8_t codewords[4096];
+ int rs_errors = 0, rs_uncorrectable = 0;
+ int data_len = deinterleave_and_correct(raw_codewords, raw_len,
+ transform.version, ecc_level, codewords, sizeof(codewords),
+ &rs_errors, &rs_uncorrectable);
+ int result = decode_qr_content(codewords, data_len, transform.version,
+ output, max_output);
+ if (result > 0) {
+ if (num_versions > 1 && rs_uncorrectable > 0) {
+ continue; /* Try next version */
+ }
+ return result;
+ }
+ } /* end version loop */
+ return 0;
+}
+static int scan_image_for_qr(Image *img, ChunkList *chunks);
+static void chunk_list_init(ChunkList *cl) {
+ cl->chunks = NULL; cl->count = 0; cl->capacity = 0;
+}
+static void chunk_list_free(ChunkList *cl) {
+ for (int i = 0; i < cl->count; i++) free(cl->chunks[i].data);
+ free(cl->chunks);
+ cl->chunks = NULL; cl->count = 0; cl->capacity = 0;
+}
+static int chunk_list_add(ChunkList *cl, char type, int index, int total,
+ const uint8_t *data, int data_len) {
+ if (cl->count >= cl->capacity) {
+ int new_cap = cl->capacity ? cl->capacity * 2 : 64;
+ Chunk *new_chunks = realloc(cl->chunks, new_cap * sizeof(Chunk));
+ if (!new_chunks) return 0;
+ cl->chunks = new_chunks;
+ cl->capacity = new_cap;
+ }
+ Chunk *c = &cl->chunks[cl->count];
+ c->type = type;
+ c->index = index;
+ c->total = total;
+ c->data = malloc(data_len);
+ memcpy(c->data, data, data_len);
+ c->data_len = data_len;
+ cl->count++;
+ return 1;
+}
+static int parse_chunk_label(const char *content, char *type,
+ int *index, int *total,
+ const char **data_start) {
+ if (content[0] != 'N' && content[0] != 'P') return 0;
+ *type = content[0];
+ int n = 0;
+ if (sscanf(content + 1, "%d/%d%n", index, total, &n) != 2) return 0;
+ const char *p = content + 1 + n;
+ while (*p == ':' || *p == ' ') p++;
+ *data_start = p;
+ return 1;
+}
+static int chunk_compare(const void *a, const void *b) {
+ const Chunk *ca = a, *cb = b;
+ return (ca->type != cb->type) ? ca->type - cb->type : ca->index - cb->index;
+}
+static void chunk_list_sort_dedupe(ChunkList *cl) {
+ if (cl->count > 1)
+ qsort(cl->chunks, cl->count, sizeof(Chunk), chunk_compare);
+ if (cl->count < 2) return;
+ int w = 1;
+ for (int r = 1; r < cl->count; r++) {
+ if (cl->chunks[r].type != cl->chunks[w-1].type ||
+ cl->chunks[r].index != cl->chunks[w-1].index)
+ cl->chunks[w++] = cl->chunks[r];
+ else free(cl->chunks[r].data);
+ }
+ cl->count = w;
+}
+static int8_t b64_decode_table[256];
+static void b64_init(void) {
+ static int done = 0; if (done) return; done = 1;
+ memset(b64_decode_table, -1, 256);
+ for (int i = 0; i < 26; i++) {
+ b64_decode_table['A'+i] = i;
+ b64_decode_table['a'+i] = 26+i;
+ }
+ for (int i = 0; i < 10; i++) b64_decode_table['0'+i] = 52+i;
+ b64_decode_table['+'] = 62;
+ b64_decode_table['/'] = 63;
+}
+static int base64_decode(const char *input, int input_len,
+ uint8_t *output, int max_output) {
+ b64_init();
+ int out_len = 0, bits = 0; uint32_t accum = 0;
+ for (int i = 0; i < input_len; i++) {
+ int c = (unsigned char)input[i], val = b64_decode_table[c];
+ if (c == '=' || c == '\n' || c == '\r' || c == ' ' || val < 0)
+ continue;
+ accum = (accum << 6) | val;
+ bits += 6;
+ if (bits >= 8) {
+ bits -= 8;
+ if (out_len < max_output)
+ output[out_len++] = (accum >> bits) & 0xFF;
+ }
+ }
+ return out_len;
+}
+static int erasure_recover(ChunkList *cl, int total_normal, int total_parity) {
+ gf_init();
+ int result = 0, missing = 0, chunk_size = 0, parity_count = 0;
+ int *n_present = calloc(total_normal + 1, sizeof(int));
+ int *p_present = calloc(total_parity + 1, sizeof(int));
+ int *missing_indices = calloc(total_normal, sizeof(int));
+ int *parity_used = calloc(total_parity, sizeof(int));
+ uint8_t **matrix = NULL, **augmented = NULL;
+ /* Mark present chunks and find chunk_size */
+ for (int i = 0; i < cl->count; i++) {
+ Chunk *c = &cl->chunks[i];
+ if (c->type == 'N' && c->index <= total_normal)
+ n_present[c->index] = 1;
+ else if (c->type == 'P' && c->index <= total_parity)
+ p_present[c->index] = 1;
+ if (!chunk_size) chunk_size = c->data_len;
+ }
+ /* Find missing normal chunks */
+ for (int i = 1; i <= total_normal; i++)
+ if (!n_present[i]) missing_indices[missing++] = i;
+ if (missing == 0) { result = 1; goto cleanup; }
+ /* Select parity chunks to use */
+ for (int p = 1; p <= total_parity && parity_count < missing; p++)
+ if (p_present[p]) parity_used[parity_count++] = p;
+ if (parity_count < missing) {
+ fprintf(stderr,
+ "Warning: %d missing, only %d parity\n", missing, parity_count);
+ goto cleanup;
+ }
+ /* Allocate matrix: rows=parity+known, cols=normal+parity */
+ int n_known = total_normal - missing;
+ int n_rows = parity_count + n_known;
+ int n_cols = total_normal + parity_count;
+ matrix = malloc(n_rows * sizeof(uint8_t *));
+ augmented = malloc(n_rows * sizeof(uint8_t *));
+ for (int i = 0; i < n_rows; i++) {
+ matrix[i] = calloc(n_cols, 1);
+ augmented[i] = calloc(chunk_size, 1);
+ }
+ /* Parity equations: coef*N[1] + coef*N[2] + ... + P = 0 */
+ for (int i = 0; i < parity_count; i++) {
+ int p_idx = parity_used[i];
+ for (int d = 1; d <= total_normal; d++)
+ matrix[i][d - 1] = gf_exp[((d - 1) * (p_idx - 1)) % 255];
+ matrix[i][total_normal + i] = 1; /* coefficient for P itself */
+ }
+ /* Known chunk equations: N[d] = data, or P[p] = data */
+ int row = parity_count;
+ for (int i = 0; i < cl->count; i++) {
+ Chunk *c = &cl->chunks[i];
+ int col;
+ if (c->type == 'N' && c->index <= total_normal)
+ col = c->index - 1;
+ else if (c->type == 'P' && c->index <= total_parity) {
+ int pi = -1;
+ for (int j = 0; j < parity_count; j++)
+ if (parity_used[j] == c->index) { pi = j; break; }
+ if (pi < 0) continue;
+ col = total_normal + pi;
+ } else continue;
+ matrix[row][col] = 1;
+ memcpy(augmented[row], c->data, chunk_size);
+ row++;
+ }
+ /* Gaussian elimination: pivot on known columns first, then missing */
+ for (int col = 0; col < n_cols; col++) {
+ int pivot = -1;
+ for (int r = 0; r < n_rows; r++)
+ if (matrix[r][col]) { pivot = r; break; }
+ if (pivot < 0) continue;
+ /* Swap pivot row to stable position if needed */
+ /* Eliminate this column from all other rows */
+ uint8_t inv = gf_inv(matrix[pivot][col]);
+ for (int j = 0; j < n_cols; j++)
+ matrix[pivot][j] = gf_mul(matrix[pivot][j], inv);
+ for (int b = 0; b < chunk_size; b++)
+ augmented[pivot][b] = gf_mul(augmented[pivot][b], inv);
+ for (int r = 0; r < n_rows; r++) {
+ if (r != pivot && matrix[r][col]) {
+ uint8_t f = matrix[r][col];
+ for (int j = 0; j < n_cols; j++)
+ matrix[r][j] ^= gf_mul(f, matrix[pivot][j]);
+ for (int b = 0; b < chunk_size; b++)
+ augmented[r][b] ^= gf_mul(f, augmented[pivot][b]);
+ }
+ }
+ }
+ /* Find rows that have solved for missing chunks */
+ for (int i = 0; i < missing; i++) {
+ int col = missing_indices[i] - 1;
+ for (int r = 0; r < n_rows; r++) {
+ if (matrix[r][col] == 1) {
+ /* Check this row has only this column set */
+ int solo = 1;
+ for (int j = 0; j < n_cols && solo; j++)
+ if (j != col && matrix[r][j]) solo = 0;
+ if (solo) {
+ chunk_list_add(cl, 'N', missing_indices[i],
+ total_normal, augmented[r], chunk_size);
+ break;
+ }
+ }
+ }
+ }
+ result = 1;
+cleanup:
+ if (matrix) for (int i = 0; i < missing; i++) free(matrix[i]);
+ if (augmented) for (int i = 0; i < missing; i++) free(augmented[i]);
+ free(matrix); free(augmented); free(parity_used);
+ free(n_present); free(p_present); free(missing_indices);
+ return result;
+}
+static uint8_t *assemble_data(ChunkList *cl, int *out_len) {
+ int total = 0, pos = 0;
+ for (int i = 0; i < cl->count; i++)
+ if (cl->chunks[i].type == 'N') total += cl->chunks[i].data_len;
+ if (total == 0) { *out_len = 0; return NULL; }
+ uint8_t *data = malloc(total);
+ for (int i = 0; i < cl->count; i++)
+ if (cl->chunks[i].type == 'N') {
+ memcpy(data + pos, cl->chunks[i].data, cl->chunks[i].data_len);
+ pos += cl->chunks[i].data_len;
+ }
+ /* Parse length prefix: "1234 data..." */
+ int len_end = 0; uint32_t real_len = 0;
+ while (len_end < pos && data[len_end] >= '0' && data[len_end] <= '9')
+ real_len = real_len * 10 + (data[len_end++] - '0');
+ if (len_end < pos && data[len_end] == ' ') len_end++;
+ if (len_end == 0) { *out_len = pos; return data; }
+ int actual = (int)real_len > pos - len_end ? pos - len_end : (int)real_len;
+ memmove(data, data + len_end, actual);
+ *out_len = actual;
+ return data;
+}
+static const uint8_t aes_sbox[256] = {
+ 99,124,119,123,242,107,111,197,48,1,103,43,254,215,171,118,
+ 202,130,201,125,250,89,71,240,173,212,162,175,156,164,114,192,
+ 183,253,147,38,54,63,247,204,52,165,229,241,113,216,49,21,
+ 4,199,35,195,24,150,5,154,7,18,128,226,235,39,178,117,
+ 9,131,44,26,27,110,90,160,82,59,214,179,41,227,47,132,
+ 83,209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,
+ 208,239,170,251,67,77,51,133,69,249,2,127,80,60,159,168,
+ 81,163,64,143,146,157,56,245,188,182,218,33,16,255,243,210,
+ 205,12,19,236,95,151,68,23,196,167,126,61,100,93,25,115,
+ 96,129,79,220,34,42,144,136,70,238,184,20,222,94,11,219,
+ 224,50,58,10,73,6,36,92,194,211,172,98,145,149,228,121,
+ 231,200,55,109,141,213,78,169,108,86,244,234,101,122,174,8,
+ 186,120,37,46,28,166,180,198,232,221,116,31,75,189,139,138,
+ 112,62,181,102,72,3,246,14,97,53,87,185,134,193,29,158,
+ 225,248,152,17,105,217,142,148,155,30,135,233,206,85,40,223,
+ 140,161,137,13,191,230,66,104,65,153,45,15,176,84,187,22};
+static const uint8_t aes_rcon[11] = {
+ 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36
+};
+static uint8_t xtime(uint8_t x) {
+ return (x << 1) ^ ((x >> 7) * 0x1b);
+}
+static void aes_key_expand(const uint8_t *key, uint8_t *rk, int klen) {
+ int nk = klen / 4, nr = nk + 6, nb = 4; /* nk=words, nr=rounds */
+ uint8_t *round_keys = rk;
+ memcpy(round_keys, key, klen);
+ uint8_t temp[4];
+ int i = nk;
+ while (i < nb * (nr + 1)) {
+ memcpy(temp, round_keys + (i - 1) * 4, 4);
+ if (i % nk == 0) {
+ uint8_t t = temp[0];
+ temp[0] = temp[1]; temp[1] = temp[2];
+ temp[2] = temp[3]; temp[3] = t;
+ for (int j = 0; j < 4; j++) temp[j] = aes_sbox[temp[j]];
+ temp[0] ^= aes_rcon[i / nk];
+ } else if (nk > 6 && i % nk == 4)
+ for (int j = 0; j < 4; j++) temp[j] = aes_sbox[temp[j]];
+ for (int j = 0; j < 4; j++)
+ round_keys[i*4 + j] = round_keys[(i - nk)*4 + j] ^ temp[j];
+ i++;
+ }
+}
+static void sha256(const uint8_t *data, size_t len, uint8_t *hash);
+static uint8_t *gzip_decompress(const uint8_t *data, int len, int *out);
+static void sha1_transform(uint32_t *state, const uint8_t *data) {
+ uint32_t a, b, c, d, e, t, w[80];
+ for (int i = 0; i < 16; i++) {
+ w[i] = ((uint32_t)data[i*4] << 24) | ((uint32_t)data[i*4+1] << 16) |
+ ((uint32_t)data[i*4+2] << 8) | data[i*4+3];
+ }
+ for (int i = 16; i < 80; i++) {
+ t = w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16];
+ w[i] = (t << 1) | (t >> 31);
+ }
+ a = state[0]; b = state[1]; c = state[2]; d = state[3]; e = state[4];
+ #define SHA1_ROL(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+ for (int i = 0; i < 80; i++) {
+ uint32_t f, k;
+ if (i < 20) {
+ f = (b & c) | ((~b) & d);
+ k = 0x5A827999;
+ } else if (i < 40) {
+ f = b ^ c ^ d;
+ k = 0x6ED9EBA1;
+ } else if (i < 60) {
+ f = (b & c) | (b & d) | (c & d);
+ k = 0x8F1BBCDC;
+ } else {
+ f = b ^ c ^ d;
+ k = 0xCA62C1D6;
+ }
+ t = SHA1_ROL(a, 5) + f + e + k + w[i];
+ e = d; d = c; c = SHA1_ROL(b, 30); b = a; a = t;
+ }
+ #undef SHA1_ROL
+ state[0] += a; state[1] += b; state[2] += c;
+ state[3] += d; state[4] += e;
+}
+static void sha1(const uint8_t *data, size_t len, uint8_t *hash) {
+ uint32_t state[5] = {
+ 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0};
+ uint8_t buffer[64];
+ size_t pos = 0;
+ while (pos + 64 <= len) {
+ sha1_transform(state, data + pos);
+ pos += 64;
+ }
+ size_t remaining = len - pos;
+ memcpy(buffer, data + pos, remaining);
+ buffer[remaining++] = 0x80;
+ if (remaining > 56) {
+ memset(buffer + remaining, 0, 64 - remaining);
+ sha1_transform(state, buffer);
+ memset(buffer, 0, 56);
+ } else {
+ memset(buffer + remaining, 0, 56 - remaining);
+ }
+ uint64_t bits = len * 8;
+ buffer[56] = bits >> 56; buffer[57] = bits >> 48;
+ buffer[58] = bits >> 40; buffer[59] = bits >> 32;
+ buffer[60] = bits >> 24; buffer[61] = bits >> 16;
+ buffer[62] = bits >> 8; buffer[63] = bits;
+ sha1_transform(state, buffer);
+ for (int i = 0; i < 5; i++) {
+ hash[i*4] = state[i] >> 24; hash[i*4+1] = state[i] >> 16;
+ hash[i*4+2] = state[i] >> 8; hash[i*4+3] = state[i];
+ }
+}
+#define S2K_SIMPLE 0
+#define S2K_SALTED 1
+#define S2K_ITERATED 3
+#define CIPHER_AES128 7
+#define CIPHER_AES192 8
+#define CIPHER_AES256 9
+#define HASH_SHA1 2
+#define HASH_SHA256 8
+static uint32_t s2k_decode_count(uint8_t c) {
+ return (16 + (c & 15)) << ((c >> 4) + 6);
+}
+static void s2k_derive_key(const char *password, int pass_len,
+ const uint8_t *salt, int s2k_type, uint8_t hash_algo,
+ uint32_t count, uint8_t *key, int key_len) {
+ int hash_len = (hash_algo == HASH_SHA256) ? 32 : 20, pos = 0, preload = 0;
+ while (pos < key_len) {
+ uint8_t *hash_input = NULL;
+ int input_len = 0;
+ if (s2k_type == S2K_SIMPLE) {
+ input_len = preload + pass_len;
+ hash_input = malloc(input_len);
+ memset(hash_input, 0, preload);
+ memcpy(hash_input + preload, password, pass_len);
+ } else if (s2k_type == S2K_SALTED) {
+ input_len = preload + 8 + pass_len;
+ hash_input = malloc(input_len);
+ memset(hash_input, 0, preload);
+ memcpy(hash_input + preload, salt, 8);
+ memcpy(hash_input + preload + 8, password, pass_len);
+ } else { /* S2K_ITERATED */
+ int sp_len = 8 + pass_len; /* salt + password length */
+ uint32_t actual_count = count;
+ if (actual_count < (uint32_t)sp_len) actual_count = sp_len;
+ input_len = preload + actual_count;
+ hash_input = malloc(input_len);
+ memset(hash_input, 0, preload);
+ int fill_pos = preload;
+ while (fill_pos < input_len) {
+ int chunk = sp_len;
+ if (fill_pos + chunk > input_len) chunk = input_len - fill_pos;
+ if (chunk <= 8) {
+ memcpy(hash_input + fill_pos, salt, chunk);
+ } else {
+ memcpy(hash_input + fill_pos, salt, 8);
+ int pass_chunk = chunk - 8;
+ if (pass_chunk > pass_len) pass_chunk = pass_len;
+ memcpy(hash_input + fill_pos + 8, password, pass_chunk);
+ }
+ fill_pos += sp_len;
+ }
+ }
+ uint8_t hash[32];
+ if (hash_algo == HASH_SHA256) {
+ sha256(hash_input, input_len, hash);
+ } else {
+ sha1(hash_input, input_len, hash);
+ }
+ int copy_len = hash_len;
+ if (pos + copy_len > key_len) copy_len = key_len - pos;
+ memcpy(key + pos, hash, copy_len);
+ pos += copy_len;
+ free(hash_input);
+ preload++; /* Add another zero byte for next round if needed */
+ }
+}
+static void aes_encrypt_block(const uint8_t *in, uint8_t *out,
+ const uint8_t *round_keys, int nr) {
+ uint8_t state[16];
+ memcpy(state, in, 16);
+ for (int i = 0; i < 16; i++) state[i] ^= round_keys[i];
+ for (int round = 1; round <= nr; round++) {
+ for (int i = 0; i < 16; i++) state[i] = aes_sbox[state[i]];
+ uint8_t temp;
+ temp = state[1]; state[1] = state[5]; state[5] = state[9];
+ state[9] = state[13]; state[13] = temp;
+ temp = state[2]; state[2] = state[10]; state[10] = temp;
+ temp = state[6]; state[6] = state[14]; state[14] = temp;
+ temp = state[15]; state[15] = state[11]; state[11] = state[7];
+ state[7] = state[3]; state[3] = temp;
+ if (round < nr) {
+ for (int c = 0; c < 4; c++) {
+ uint8_t a[4];
+ for (int i = 0; i < 4; i++) a[i] = state[c * 4 + i];
+ state[c*4 + 0] = xtime(a[0]) ^ xtime(a[1])
+ ^ a[1] ^ a[2] ^ a[3];
+ state[c*4 + 1] = a[0] ^ xtime(a[1]) ^ xtime(a[2])
+ ^ a[2] ^ a[3];
+ state[c*4 + 2] = a[0] ^ a[1] ^ xtime(a[2])
+ ^ xtime(a[3]) ^ a[3];
+ state[c*4 + 3] = xtime(a[0]) ^ a[0] ^ a[1]
+ ^ a[2] ^ xtime(a[3]);
+ }
+ }
+ for (int i = 0; i < 16; i++) state[i] ^= round_keys[round * 16 + i];
+ }
+ memcpy(out, state, 16);
+}
+static void aes_cfb_decrypt(const uint8_t *data, int data_len,
+ const uint8_t *key, int key_len,
+ uint8_t *output) {
+ int nr = (key_len == 16) ? 10 : (key_len == 24) ? 12 : 14;
+ uint8_t round_keys[240];
+ aes_key_expand(key, round_keys, key_len);
+ uint8_t fr[16] = {0}, fre[16]; /* fr = Feedback reg, fre = encrypted */
+ int pos = 0;
+ while (pos < data_len) {
+ aes_encrypt_block(fr, fre, round_keys, nr);
+ int block_len = 16;
+ if (pos + block_len > data_len) block_len = data_len - pos;
+ for (int i = 0; i < block_len; i++) {
+ output[pos + i] = data[pos + i] ^ fre[i];
+ }
+ if (block_len == 16) {
+ memcpy(fr, data + pos, 16);
+ } else {
+ memmove(fr, fr + block_len, 16 - block_len);
+ memcpy(fr + 16 - block_len, data + pos, block_len);
+ }
+ pos += block_len;
+ }
+}
+static int pgp_parse_header(const uint8_t *data, int len,
+ int *body_len, int *header_len) {
+ if (len < 2) return -1;
+ uint8_t tag_byte = data[0];
+ if ((tag_byte & 0x80) == 0) return -1; /* Not a PGP packet */
+ int packet_tag, pos = 1;
+ if (tag_byte & 0x40) {
+ packet_tag = tag_byte & 0x3f;
+ if (data[pos] < 192) {
+ *body_len = data[pos];
+ pos++;
+ } else if (data[pos] < 224) {
+ if (len < pos + 2) return -1;
+ *body_len = ((data[pos] - 192) << 8) + data[pos+1] + 192;
+ pos += 2;
+ } else if (data[pos] == 255) {
+ if (len < pos + 5) return -1;
+ *body_len = ((uint32_t)data[pos+1] << 24) |
+ ((uint32_t)data[pos+2] << 16) |
+ ((uint32_t)data[pos+3] << 8) | data[pos+4];
+ pos += 5;
+ } else {
+ *body_len = 1 << (data[pos] & 0x1f);
+ pos++;
+ }
+ } else {
+ packet_tag = (tag_byte >> 2) & 0x0f;
+ int length_type = tag_byte & 0x03;
+ if (length_type == 0) {
+ *body_len = data[pos];
+ pos++;
+ } else if (length_type == 1) {
+ if (len < pos + 2) return -1;
+ *body_len = (data[pos] << 8) | data[pos+1];
+ pos += 2;
+ } else if (length_type == 2) {
+ if (len < pos + 4) return -1;
+ *body_len = ((uint32_t)data[pos] << 24) |
+ ((uint32_t)data[pos+1] << 16) |
+ ((uint32_t)data[pos+2] << 8) | data[pos+3];
+ pos += 4;
+ } else {
+ *body_len = len - pos;
+ }
+ }
+ *header_len = pos;
+ return packet_tag;
+}
+static int is_gpg_encrypted(const uint8_t *data, int len) {
+ if (len < 2) return 0;
+ if ((data[0] & 0x80) == 0) return 0;
+ int packet_tag = (data[0] & 0x40)
+ ? (data[0] & 0x3f) : ((data[0] >> 2) & 0x0f);
+ return (packet_tag == 3 || packet_tag == 9 || packet_tag == 18);
+}
+static uint8_t *gpg_decrypt(const uint8_t *data, int data_len,
+ const char *password, int *out_len) {
+ *out_len = 0;
+ if (!is_gpg_encrypted(data, data_len)) {
+ return NULL;
+ }
+ int pos = 0;
+ uint8_t session_key[32];
+ int session_key_len = 0, cipher_algo = CIPHER_AES128;
+ const uint8_t *encrypted_data = NULL; int encrypted_len = 0, has_mdc = 0;
+ while (pos < data_len) {
+ int body_len, header_len;
+ int packet_tag = pgp_parse_header(data + pos,
+ data_len - pos, &body_len, &header_len);
+ if (packet_tag < 0) break;
+ const uint8_t *body = data + pos + header_len;
+ if (packet_tag == 3) {
+ if (body_len < 4) return NULL;
+ int version = body[0];
+ if (version != 4) {
+ fprintf(stderr, "GPG: Unsupported SKESK v%d\n", version);
+ return NULL;
+ }
+ cipher_algo = body[1];
+ int s2k_type = body[2], s2k_pos = 3;
+ uint8_t hash_algo = body[s2k_pos++], salt[8] = {0};
+ uint32_t count = 65536;
+ if (s2k_type == S2K_SALTED || s2k_type == S2K_ITERATED) {
+ memcpy(salt, body + s2k_pos, 8); s2k_pos += 8;
+ if (s2k_type == S2K_ITERATED)
+ count = s2k_decode_count(body[s2k_pos++]);
+ } else if (s2k_type != S2K_SIMPLE) {
+ fprintf(stderr, "GPG: S2K type %d\n", s2k_type);
+ return NULL;
+ }
+ session_key_len = (cipher_algo == CIPHER_AES256) ? 32 :
+ (cipher_algo == CIPHER_AES192) ? 24 : 16;
+ if (cipher_algo < CIPHER_AES128 || cipher_algo > CIPHER_AES256) {
+ fprintf(stderr, "GPG: cipher %d\n", cipher_algo);
+ return NULL;
+ }
+ s2k_derive_key(password, strlen(password), salt, s2k_type,
+ hash_algo, count, session_key, session_key_len);
+ } else if (packet_tag == 9) {
+ encrypted_data = body;
+ encrypted_len = body_len;
+ has_mdc = 0;
+ } else if (packet_tag == 18) {
+ if (body_len < 1 || body[0] != 1) {
+ fprintf(stderr, "GPG: Unsupported SEIPD version\n");
+ return NULL;
+ }
+ encrypted_data = body + 1;
+ encrypted_len = body_len - 1;
+ has_mdc = 1;
+ }
+ pos += header_len + body_len;
+ }
+ if (!encrypted_data || session_key_len == 0) {
+ fprintf(stderr, "GPG: Missing data or key\n");
+ return NULL;
+ }
+ uint8_t *decrypted = malloc(encrypted_len);
+ aes_cfb_decrypt(encrypted_data, encrypted_len,
+ session_key, session_key_len, decrypted);
+ int block_size = 16; /* AES block size */
+ if (encrypted_len < block_size + 2) {
+ free(decrypted);
+ return NULL;
+ }
+ if (decrypted[block_size - 2] != decrypted[block_size] ||
+ decrypted[block_size - 1] != decrypted[block_size + 1]) {
+ fprintf(stderr, "GPG: prefix check failed (bad password?)\n");
+ free(decrypted);
+ return NULL;
+ }
+ int payload_start = block_size + 2;
+ int payload_len = encrypted_len - payload_start;
+ if (has_mdc) {
+ if (payload_len < 22) { free(decrypted); return NULL; }
+ int mdc_pos = payload_start + payload_len - 22;
+ if (decrypted[mdc_pos] != 0xD3 || decrypted[mdc_pos + 1] != 0x14) {
+ fprintf(stderr, "GPG: MDC not found\n");
+ free(decrypted); return NULL;
+ }
+ uint8_t *mdc_input = malloc(payload_start + payload_len - 20);
+ memcpy(mdc_input, decrypted, payload_start + payload_len - 20);
+ uint8_t computed_hash[20];
+ sha1(mdc_input, payload_start + payload_len - 20, computed_hash);
+ free(mdc_input);
+ if (memcmp(computed_hash, decrypted + mdc_pos + 2, 20) != 0) {
+ fprintf(stderr, "GPG: MDC failed\n");
+ free(decrypted); return NULL;
+ }
+ payload_len -= 22; /* Remove MDC from output */
+ }
+ int lit_blen, lit_hlen, lit_tag = pgp_parse_header(
+ decrypted + payload_start, payload_len, &lit_blen, &lit_hlen);
+ uint8_t *result = NULL;
+ if (lit_tag == 11) {
+ const uint8_t *lit_body = decrypted + payload_start + lit_hlen;
+ if (lit_blen < 6) { free(decrypted); return NULL; }
+ int filename_len = lit_body[1], data_off = 2 + filename_len + 4;
+ int data_len_final = lit_blen - data_off;
+ result = malloc(data_len_final);
+ if (result) {
+ memcpy(result, lit_body + data_off, data_len_final);
+ *out_len = data_len_final;
+ }
+ } else if (lit_tag == 8) {
+ const uint8_t *cb = decrypted + payload_start + lit_hlen;
+ int comp_algo = cb[0];
+ if (comp_algo == 0) {
+ result = malloc(lit_blen - 1);
+ if (result) {
+ memcpy(result, cb + 1, lit_blen - 1);
+ *out_len = lit_blen - 1;
+ }
+ } else if (comp_algo == 1 || comp_algo == 2) {
+ /* Wrap DEFLATE/ZLIB in gzip header for decompression */
+ int skip = (comp_algo == 2) ? 3 : 1; /* ZLIB: 2-byte hdr */
+ int trim = (comp_algo == 2) ? 6 : 0;
+ int comp_len = lit_blen - 1 - trim;
+ if (comp_len > 0) {
+ uint8_t *wrapped = malloc(comp_len + 18);
+ memcpy(wrapped, "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff", 10);
+ memcpy(wrapped + 10, cb + skip, comp_len);
+ memset(wrapped + 10 + comp_len, 0, 8);
+ result = gzip_decompress(wrapped, comp_len + 18, out_len);
+ free(wrapped);
+ }
+ } else {
+ fprintf(stderr, "GPG: compression %d\n", comp_algo);
+ }
+ } else {
+ fprintf(stderr, "GPG: packet type %d\n", lit_tag);
+ }
+ free(decrypted);
+ return result;
+}
+typedef struct {
+ uint16_t sym; /* Symbol or subtable offset */
+ uint8_t bits; /* Number of bits */
+} HuffEntry;
+typedef struct {
+ const uint8_t *in;
+ int in_len;
+ int in_pos;
+ uint32_t bit_buf;
+ int bit_cnt;
+ uint8_t *out;
+ int out_len;
+ int out_pos;
+ int out_cap;
+} InflateState;
+static uint32_t inf_read_bits(InflateState *s, int n) {
+ while (s->bit_cnt < n) {
+ if (s->in_pos >= s->in_len) return 0;
+ s->bit_buf |= (uint32_t)s->in[s->in_pos++] << s->bit_cnt;
+ s->bit_cnt += 8;
+ }
+ uint32_t val = s->bit_buf & ((1 << n) - 1);
+ s->bit_buf >>= n;
+ s->bit_cnt -= n;
+ return val;
+}
+static int inf_output(InflateState *s, uint8_t b) {
+ if (s->out_pos >= s->out_cap) {
+ int new_cap = s->out_cap ? s->out_cap * 2 : 4096;
+ s->out = realloc(s->out, new_cap);
+ s->out_cap = new_cap;
+ }
+ s->out[s->out_pos++] = b;
+ return 1;
+}
+static void get_fixed_lit_lengths(uint8_t *lengths) {
+ for (int i = 0; i < 144; i++) lengths[i] = 8;
+ for (int i = 144; i < 256; i++) lengths[i] = 9;
+ for (int i = 256; i < 280; i++) lengths[i] = 7;
+ for (int i = 280; i < 288; i++) lengths[i] = 8;
+}
+static int huff_build(const uint8_t *lengths, int count,
+ HuffEntry *table, int *table_bits) {
+ int bl_count[16] = {0};
+ int max_len = 0;
+ for (int i = 0; i < count; i++) {
+ if (lengths[i]) {
+ bl_count[lengths[i]]++;
+ if (lengths[i] > max_len) max_len = lengths[i];
+ }
+ }
+ *table_bits = max_len > 9 ? 9 : max_len;
+ int code = 0;
+ int next_code[16];
+ next_code[0] = 0;
+ for (int bits = 1; bits <= max_len; bits++) {
+ code = (code + bl_count[bits - 1]) << 1;
+ next_code[bits] = code;
+ }
+ for (int i = 0; i < count; i++) {
+ int len = lengths[i];
+ if (len) {
+ int c = next_code[len]++;
+ int rev = 0;
+ for (int j = 0; j < len; j++) {
+ rev = (rev << 1) | (c & 1);
+ c >>= 1;
+ }
+ if (len <= *table_bits) {
+ int fill = 1 << (*table_bits - len);
+ for (int j = 0; j < fill; j++) {
+ int idx = rev | (j << len);
+ table[idx].sym = i;
+ table[idx].bits = len;
+ }
+ }
+ }
+ }
+ return 1;
+}
+static int huff_decode(InflateState *s, HuffEntry *table, int table_bits) {
+ while (s->bit_cnt < table_bits) {
+ if (s->in_pos >= s->in_len) return -1;
+ s->bit_buf |= (uint32_t)s->in[s->in_pos++] << s->bit_cnt;
+ s->bit_cnt += 8;
+ }
+ int idx = s->bit_buf & ((1 << table_bits) - 1);
+ int bits = table[idx].bits;
+ int sym = table[idx].sym;
+ s->bit_buf >>= bits;
+ s->bit_cnt -= bits;
+ return sym;
+}
+static const int len_base[] = {
+ 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,
+ 35,43,51,59,67,83,99,115,131,163,195,227,258};
+static const int len_extra[] = {
+ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0};
+static const int dist_base[] = {
+ 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,
+ 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577};
+static const int dist_extra[] = {
+ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};
+static const int codelen_order[19] = {16, 17, 18, 0, 8, 7, 9, 6,
+ 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+static int inflate_with_tables(InflateState *s, HuffEntry *lt, int lb,
+ HuffEntry *dt, int db) {
+ HuffEntry *lit_table = lt, *dist_table = dt;
+ int lit_bits = lb, dist_bits = db;
+ while (1) {
+ int sym = huff_decode(s, lit_table, lit_bits);
+ if (sym < 0) return 0;
+ if (sym < 256) {
+ if (!inf_output(s, sym)) return 0;
+ } else if (sym == 256) {
+ return 1;
+ } else {
+ sym -= 257;
+ if (sym >= 29) return 0;
+ int len = len_base[sym] + inf_read_bits(s, len_extra[sym]);
+ int dist_sym = huff_decode(s, dist_table, dist_bits);
+ if (dist_sym < 0 || dist_sym >= 30) return 0;
+ int dist = dist_base[dist_sym]
+ + inf_read_bits(s, dist_extra[dist_sym]);
+ for (int i = 0; i < len; i++) {
+ int src = s->out_pos - dist;
+ if (src < 0) return 0;
+ if (!inf_output(s, s->out[src])) return 0;
+ }
+ }
+ }
+}
+static int inflate_block_fixed(InflateState *s) {
+ HuffEntry lit_table[512], dist_table[32];
+ int lit_bits, dist_bits;
+ uint8_t lit_len[288], dist_len[32];
+ get_fixed_lit_lengths(lit_len);
+ huff_build(lit_len, 288, lit_table, &lit_bits);
+ for (int i = 0; i < 32; i++) dist_len[i] = 5;
+ huff_build(dist_len, 32, dist_table, &dist_bits);
+ return inflate_with_tables(s, lit_table, lit_bits, dist_table, dist_bits);
+}
+static int inflate_block_dynamic(InflateState *s) {
+ int hlit = inf_read_bits(s, 5) + 257; /* Literal/length codes */
+ int hdist = inf_read_bits(s, 5) + 1; /* Distance codes */
+ int hclen = inf_read_bits(s, 4) + 4; /* Code length codes */
+ if (hlit > 286 || hdist > 30) return 0;
+ uint8_t codelen_lengths[19] = {0};
+ for (int i = 0; i < hclen; i++) {
+ codelen_lengths[codelen_order[i]] = inf_read_bits(s, 3);
+ }
+ HuffEntry codelen_table[128];
+ int codelen_bits;
+ if (!huff_build(codelen_lengths, 19, codelen_table, &codelen_bits))
+ return 0;
+ uint8_t lengths[286 + 30];
+ int total_codes = hlit + hdist;
+ int i = 0;
+ while (i < total_codes) {
+ int sym = huff_decode(s, codelen_table, codelen_bits);
+ if (sym < 0) return 0;
+ if (sym < 16) {
+ lengths[i++] = sym;
+ } else if (sym == 16) {
+ if (i == 0) return 0;
+ int repeat = 3 + inf_read_bits(s, 2);
+ uint8_t prev = lengths[i - 1];
+ while (repeat-- && i < total_codes) lengths[i++] = prev;
+ } else if (sym == 17) {
+ int repeat = 3 + inf_read_bits(s, 3);
+ while (repeat-- && i < total_codes) lengths[i++] = 0;
+ } else if (sym == 18) {
+ int repeat = 11 + inf_read_bits(s, 7);
+ while (repeat-- && i < total_codes) lengths[i++] = 0;
+ } else {
+ return 0;
+ }
+ }
+ HuffEntry lit_table[32768]; /* Need larger table for dynamic codes */
+ HuffEntry dist_table[32768];
+ int lit_bits, dist_bits;
+ if (!huff_build(lengths, hlit, lit_table, &lit_bits)) return 0;
+ if (!huff_build(lengths + hlit, hdist, dist_table, &dist_bits)) return 0;
+ return inflate_with_tables(s, lit_table, lit_bits, dist_table, dist_bits);
+}
+static uint8_t *gzip_decompress(const uint8_t *data, int data_len,
+ int *out_len) {
+ if (data_len < 10) { *out_len = 0; return NULL; }
+ if (data[0] != 0x1f || data[1] != 0x8b) { *out_len = 0; return NULL; }
+ if (data[2] != 8) { *out_len = 0; return NULL; }
+ int flags = data[3];
+ int pos = 10;
+ if (flags & 0x04) {
+ if (pos + 2 > data_len) { *out_len = 0; return NULL; }
+ int xlen = data[pos] | (data[pos+1] << 8);
+ pos += 2 + xlen;
+ }
+ if (flags & 0x08) {
+ while (pos < data_len && data[pos]) pos++;
+ pos++;
+ }
+ if (flags & 0x10) {
+ while (pos < data_len && data[pos]) pos++;
+ pos++;
+ }
+ if (flags & 0x02) pos += 2;
+ if (pos >= data_len) { *out_len = 0; return NULL; }
+ InflateState s = {0};
+ s.in = data + pos; s.in_len = data_len - pos - 8; s.in_pos = 0;
+ s.out = NULL; s.out_pos = 0; s.out_cap = 0;
+ int bfinal;
+ do {
+ bfinal = inf_read_bits(&s, 1);
+ int btype = inf_read_bits(&s, 2);
+ if (btype == 0) {
+ s.bit_buf = 0; s.bit_cnt = 0;
+ if (s.in_pos + 4 > s.in_len) break;
+ int len = s.in[s.in_pos] | (s.in[s.in_pos+1] << 8);
+ s.in_pos += 4;
+ for (int i = 0; i < len && s.in_pos < s.in_len; i++) {
+ if (!inf_output(&s, s.in[s.in_pos++])) break;
+ }
+ } else if (btype == 1) {
+ if (!inflate_block_fixed(&s)) break;
+ } else if (btype == 2) {
+ if (!inflate_block_dynamic(&s)) break;
+ } else {
+ break; /* Invalid block type */
+ }
+ } while (!bfinal);
+ *out_len = s.out_pos;
+ return s.out;
+}
+static const uint32_t sha256_k[64] = {
+ 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,
+ 0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
+ 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,
+ 0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
+ 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,
+ 0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
+ 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,
+ 0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
+ 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,
+ 0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
+ 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,
+ 0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
+ 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,
+ 0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
+ 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,
+ 0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2};
+#define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
+#define CH(x, y, z) (((x) & (y)) ^ (~(x) & (z)))
+#define MAJ(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+#define EP0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22))
+#define EP1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25))
+#define SIG0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ ((x) >> 3))
+#define SIG1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ ((x) >> 10))
+static void sha256_transform(uint32_t *state, const uint8_t *block) {
+ uint32_t w[64];
+ for (int i = 0; i < 16; i++) {
+ w[i] = ((uint32_t)block[i*4] << 24) |
+ ((uint32_t)block[i*4+1] << 16) |
+ ((uint32_t)block[i*4+2] << 8) |
+ (uint32_t)block[i*4+3];
+ }
+ for (int i = 16; i < 64; i++) {
+ w[i] = SIG1(w[i-2]) + w[i-7] + SIG0(w[i-15]) + w[i-16];
+ }
+ uint32_t a = state[0], b = state[1], c = state[2], d = state[3];
+ uint32_t e = state[4], f = state[5], g = state[6], h = state[7];
+ for (int i = 0; i < 64; i++) {
+ uint32_t t1 = h + EP1(e) + CH(e, f, g) + sha256_k[i] + w[i];
+ uint32_t t2 = EP0(a) + MAJ(a, b, c);
+ h = g; g = f; f = e; e = d + t1;
+ d = c; c = b; b = a; a = t1 + t2;
+ }
+ state[0] += a; state[1] += b; state[2] += c; state[3] += d;
+ state[4] += e; state[5] += f; state[6] += g; state[7] += h;
+}
+static void sha256(const uint8_t *data, size_t len, uint8_t *hash) {
+ uint32_t state[8] = {
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
+ };
+ size_t i;
+ for (i = 0; i + 64 <= len; i += 64) {
+ sha256_transform(state, data + i);
+ }
+ uint8_t block[64];
+ size_t remaining = len - i;
+ memcpy(block, data + i, remaining);
+ block[remaining] = 0x80;
+ if (remaining >= 56) {
+ memset(block + remaining + 1, 0, 63 - remaining);
+ sha256_transform(state, block);
+ memset(block, 0, 56);
+ } else {
+ memset(block + remaining + 1, 0, 55 - remaining);
+ }
+ uint64_t bit_len = len * 8;
+ for (int j = 0; j < 8; j++) {
+ block[63 - j] = bit_len & 0xff;
+ bit_len >>= 8;
+ }
+ sha256_transform(state, block);
+ for (int j = 0; j < 8; j++) {
+ hash[j*4] = (state[j] >> 24) & 0xff;
+ hash[j*4+1] = (state[j] >> 16) & 0xff;
+ hash[j*4+2] = (state[j] >> 8) & 0xff;
+ hash[j*4+3] = state[j] & 0xff;
+ }
+}
+
+static int valid_qr_triple(FinderPattern *p0, FinderPattern *p1,
+ FinderPattern *p2) {
+ float ms[3] = {p0->module_size, p1->module_size, p2->module_size};
+ float avg_ms = (ms[0] + ms[1] + ms[2]) / 3.0f;
+ for (int i = 0; i < 3; i++)
+ if (ms[i] < avg_ms * 0.75f || ms[i] > avg_ms * 1.25f) return 0;
+ int d[3] = {distance_sq(p0->x, p0->y, p1->x, p1->y),
+ distance_sq(p0->x, p0->y, p2->x, p2->y),
+ distance_sq(p1->x, p1->y, p2->x, p2->y)};
+ /* Sort distances */
+ if (d[0] > d[1]) { int t = d[0]; d[0] = d[1]; d[1] = t; }
+ if (d[1] > d[2]) { int t = d[1]; d[1] = d[2]; d[2] = t; }
+ if (d[0] > d[1]) { int t = d[0]; d[0] = d[1]; d[1] = t; }
+ /* Check: two sides similar, hypotenuse ~= sqrt(2) * side */
+ if (d[1] > d[0] * 2) return 0;
+ float hyp_ratio = (float)d[2] / (d[0] + d[1]);
+ if (hyp_ratio < 0.75f || hyp_ratio > 1.25f) return 0;
+ float version = (sqrtf((float)d[0]) / avg_ms - 10.0f) / 4.0f;
+ return version <= 50;
+}
+static int scan_image_for_qr(Image *img, ChunkList *chunks) {
+ FinderPattern patterns[500];
+ int npatterns = find_finder_patterns(img, patterns, 500);
+ if (npatterns < 3) return 0; /* Need >= 3 finder patterns */
+ int *used = calloc(npatterns, sizeof(int));
+ int decoded_count = 0;
+ for (int i = 0; i < npatterns - 2 && decoded_count < MAX_CHUNKS; i++) {
+ if (used[i]) continue;
+ for (int j = i + 1; j < npatterns - 1; j++) {
+ if (used[j]) continue;
+ for (int k = j + 1; k < npatterns; k++) {
+ if (used[k]) continue;
+ if (!valid_qr_triple(&patterns[i], &patterns[j],
+ &patterns[k])) {
+ continue;
+ }
+ FinderPattern triple[3] = {
+ patterns[i], patterns[j], patterns[k]};
+ char qr_content[4096];
+ int len = decode_qr_from_finders(img, triple, 3,
+ qr_content, sizeof(qr_content));
+ if (len > 0) {
+ char type;
+ int index, total;
+ const char *data_start;
+ if (parse_chunk_label(qr_content, &type, &index,
+ &total, &data_start)) {
+ uint8_t decoded[MAX_CHUNK_SIZE];
+ int decoded_len = base64_decode(data_start,
+ strlen(data_start), decoded, sizeof(decoded));
+ if (decoded_len > 0) {
+ chunk_list_add(chunks, type, index, total,
+ decoded, decoded_len);
+ decoded_count++;
+ used[i] = used[j] = used[k] = 1;
+ goto next_i; /* Move to next starting pattern */
+ }
+ }
+ }
+ }
+ }
+ next_i:;
+ }
+ free(used);
+ return decoded_count;
+}
+static int parse_args(int argc, char **argv, const char **password,
+ const char **output_file, int *first_file) {
+ for (int i = 1; i < argc; i++) {
+ if (argv[i][0] != '-') {
+ if (!*first_file) *first_file = i;
+ continue;
+ }
+ if (!strcmp(argv[i], "-p") && i+1 < argc) *password = argv[++i];
+ else if (!strcmp(argv[i], "-o") && i+1 < argc)
+ *output_file = argv[++i];
+ else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "-vv")
+ || !strcmp(argv[i], "--debug")) continue;
+ else {
+ fprintf(stderr, "Usage: %s [-p pass] [-o out] img.pbm\n",
+ argv[0]);
+ return argv[i][1] == 'h' ? 0 : -1;
+ }
+ }
+ if (!*first_file) {
+ fprintf(stderr, "Usage: %s [-p pass] [-o out] img.pbm\n", argv[0]);
+ return -1;
+ }
+ return 1;
+}
+static int load_images(int argc, char **argv, int first_file,
+ ChunkList *chunks) {
+ for (int i = first_file; i < argc; i++) {
+ if (argv[i][0] == '-') continue;
+ Image *img = image_read_pbm(argv[i]);
+ if (img) { scan_image_for_qr(img, chunks); image_free(img); }
+ }
+ return chunks->count > 0;
+}
+static uint8_t *collect_chunks(ChunkList *chunks, int *data_len) {
+ chunk_list_sort_dedupe(chunks);
+ int tn = 0, tp = 0;
+ for (int i = 0; i < chunks->count; i++) {
+ Chunk *c = &chunks->chunks[i];
+ if (c->type == 'N' && c->total > tn) tn = c->total;
+ if (c->type == 'P' && c->total > tp) tp = c->total;
+ }
+ if (!erasure_recover(chunks, tn, tp))
+ fprintf(stderr, "Warning: recovery incomplete\n");
+ return assemble_data(chunks, data_len);
+}
+static uint8_t *decrypt_if_needed(uint8_t *data, int *len,
+ const char *password) {
+ if (!is_gpg_encrypted(data, *len)) return data;
+ if (!password) {
+ fprintf(stderr, "Error: GPG encrypted, need -p\n");
+ free(data); return NULL;
+ }
+ int dec_len;
+ uint8_t *dec = gpg_decrypt(data, *len, password, &dec_len);
+ if (!dec) {
+ fprintf(stderr, "GPG decryption failed\n");
+ free(data); return NULL;
+ }
+ free(data); *len = dec_len; return dec;
+}
+static uint8_t *decompress_if_needed(uint8_t *data, int *len) {
+ if (*len < 2 || data[0] != 0x1f || data[1] != 0x8b) return data;
+ int dec_len;
+ uint8_t *dec = gzip_decompress(data, *len, &dec_len);
+ if (!dec) return data;
+ free(data); *len = dec_len; return dec;
+}
+static int write_output(uint8_t *data, int len, const char *output_file) {
+ uint8_t hash[32]; sha256(data, len, hash);
+ fprintf(stderr, "SHA256: ");
+ for (int i = 0; i < 32; i++) fprintf(stderr, "%02x", hash[i]);
+ fprintf(stderr, "\n");
+ FILE *out = output_file ? fopen(output_file, "wb") : stdout;
+ if (!out) return 0;
+ fwrite(data, 1, len, out); if (output_file) fclose(out);
+ return 1;
+}
+int main(int argc, char **argv) {
+ const char *password = NULL, *output_file = NULL;
+ int first_file = 0;
+ int r = parse_args(argc, argv, &password, &output_file, &first_file);
+ if (r <= 0) return r < 0 ? 1 : 0;
+ ChunkList chunks; chunk_list_init(&chunks);
+ if (!load_images(argc, argv, first_file, &chunks))
+ { fprintf(stderr, "No QR codes found\n"); return 1; }
+ int len;
+ uint8_t *data = collect_chunks(&chunks, &len);
+ chunk_list_free(&chunks);
+ if (!data) { fprintf(stderr, "Failed to assemble data\n"); return 1; }
+ data = decrypt_if_needed(data, &len, password); if (!data) return 1;
+ data = decompress_if_needed(data, &len);
+ (void)xtime;
+ int ok = write_output(data, len, output_file);
+ free(data);
+ return ok ? 0 : 1;
+}
--- /dev/null
+#include<stdio.h>
+#include<stdlib.h>
+#include<string.h>
+#include<stdint.h>
+#include<math.h>
+typedef uint8_t U;typedef uint32_t V;typedef
+uint16_t W;typedef struct{float x,y;}du;typedef
+struct{float h[3][3];}bF;typedef struct{int G,aH;
+float Z;bF fs;}aJ;typedef struct{int aT;int bf;U*l;}
+bG;typedef struct{char bp;int aE;int aA;U*l;int D;}
+cY;typedef struct{cY*I;int F;int aF;}av;static int
+fG(FILE*f){int c;while((c=fgetc(f))!=EOF){if(c=='#')
+while((c=fgetc(f))!=EOF&&c!='\n');else if(c>' '){
+ungetc(c,f);return 1;}}return 0;}static void dW(U*dl
+,U*hP,int aT,int bf){int bs=32;for(int by=0;by<bf;by
++=bs){for(int bx=0;bx<aT;bx+=bs){int bw=(bx+bs*2<aT)
+?bs*2:aT-bx;int bh=(by+bs*2<bf)?bs*2:bf-by;int hx=0;
+for(int y=by;y<by+bh;y++)for(int x=bx;x<bx+bw;x++)hx
++=dl[y*aT+x];int gs=hx/(bw*bh)-10;if(gs<20)gs=20;int
+ew=(bx+bs<aT)?bs:aT-bx;int eh=(by+bs<bf)?bs:bf-by;
+for(int y=by;y<by+eh;y++)for(int x=bx;x<bx+ew;x++){
+int gE=y*aT+x;hP[gE]=(dl[gE]<gs)?1:0;}}}}static bG*
+fz(const char*hl){FILE*f=fopen(hl,"rb");if(!f)return
+0;bG*ag=calloc(1,sizeof(bG));U*dl=0;char eZ[3]={0};
+if(fread(eZ,1,2,f)!=2)goto eC;int gd=0;if(eZ[1]=='1'
+)gd=1;else if(eZ[1]=='4')gd=4;else if(eZ[1]=='2')gd=
+2;else if(eZ[1]=='5')gd=5;if(eZ[0]!='P'||!gd)goto eC
+;if(!fG(f)||fscanf(f,"%d",&ag->aT)!=1)goto eC;if(!fG
+(f)||fscanf(f,"%d",&ag->bf)!=1)goto eC;int eD=1;if(
+gd==2||gd==5){if(!fG(f)||fscanf(f,"%d",&eD)!=1)goto
+eC;if(eD<=0)eD=255;}fgetc(f);int n=ag->aT*ag->bf;ag
+->l=malloc(n);if(gd==4){int rb=(ag->aT+7)/8;U*fa=
+malloc(rb);for(int y=0;y<ag->bf;y++){if(fread(fa,1,
+rb,f)!=(size_t)rb){free(fa);goto eC;}for(int x=0;x<
+ag->aT;x++)ag->l[y*ag->aT+x]=(fa[x/8]>>(7-x%8))&1;}
+free(fa);}else if(gd==1){for(int i=0;i<n;i++){int c;
+while((c=fgetc(f))!=EOF&&c<=' ');if(c==EOF)goto eC;
+ag->l[i]=(c=='1')?1:0;}}else{dl=malloc(n);if(gd==5){
+if(fread(dl,1,n,f)!=(size_t)n)goto eC;}else{for(int
+i=0;i<n;i++){int fH;if(fscanf(f,"%d",&fH)!=1)goto eC
+;dl[i]=fH;}}if(eD!=255)for(int i=0;i<n;i++)dl[i]=dl[
+i]*255/eD;dW(dl,ag->l,ag->aT,ag->bf);free(dl);dl=0;}
+fclose(f);return ag;eC:free(dl);if(ag)free(ag->l);
+free(ag);fclose(f);return 0;}static void gF(bG*ag){
+if(ag){free(ag->l);free(ag);}}static int cZ(bG*ag,
+int x,int y){if(x<0||x>=ag->aT||y<0||y>=ag->bf)
+return 0;return ag->l[y*ag->aT+x];}typedef struct{
+int x,y;float Z;float fI;float fJ;}ac;typedef struct
+{int E;int aK;int aL;}X;typedef struct{int eE,eF;int
+cD,cE;int cF;int F;float bY;}aa;static float da(int*
+J,float gR){int aA=J[0]+J[1]+J[2]+J[3]+J[4];if(aA<7)
+return 0;if(!J[0]||!J[1]||!J[2]||!J[3]||!J[4])return
+0;float dN=aA/7.0f,im=dN*gR*1.2f;float eb=(float)J[2
+]/aA;if(eb<0.25f||eb>0.55f)return 0;for(int i=0;i<5;
+i+=(i==1?2:1))if(J[i]<dN-im||J[i]>dN+im)return 0;if(
+J[2]<2.0f*dN||J[2]>4.0f*dN)return 0;return dN;}
+static int fb(X*ej,int F,int io,int E,int ip,int iq)
+{if(F<io){ej[F].E=E;ej[F].aK=ip;ej[F].aL=iq;}return
+F<io?F+1:F;}static int fA(int a0,int a1,int b0,int
+b1){int ov=((a1<b1?a1:b1)-(a0>b0?a0:b0));int hy=((a1
+-a0)<(b1-b0))?(a1-a0):(b1-b0);return ov>=0&&ov>=hy/2
+;}static int bO(X*ej,int dO,aa**ft,int*ec){if(dO==0)
+return 0;aa*bg=*ft;int aF=*ec;int fK=0;typedef
+struct{int ek;int dm;int dn;int co;}fL;fL br[128];
+int dv=0;int co=-999;for(int i=0;i<dO;i++){X*line=&
+ej[i];if(line->E!=co){int dc=0;for(int a=0;a<dv;a++)
+{if(line->E-br[a].co<=3+1){if(dc!=a){br[dc]=br[a];}
+dc++;}}dv=dc;co=line->E;}int eG=-1;int eH=(line->aK+
+line->aL)/2;for(int a=0;a<dv;a++){if(fA(line->aK,
+line->aL,br[a].dm,br[a].dn)){int iQ=line->E-br[a].co
+;if(iQ>1){aa*r=&bg[br[a].ek];int dX=r->cD;int dY=r->
+cE;if(eH<dX-10||eH>dY+10){continue;}}eG=a;break;}}
+int ri,ai;if(eG>=0){ri=br[eG].ek;ai=eG;}else if(dv<
+128){if(fK>=aF){aF*=2;bg=realloc(bg,aF*sizeof(aa));*
+ft=bg;*ec=aF;}ri=fK++;ai=dv++;br[ai].ek=ri;bg[ri].eE
+=line->E;bg[ri].cD=line->aL;bg[ri].cE=line->aK;bg[ri
+].cF=0;bg[ri].bY=0;bg[ri].F=0;}else continue;aa*r=&
+bg[ri];r->eF=line->E;if(line->aK<r->cD)r->cD=line->
+aK;if(line->aL>r->cE)r->cE=line->aL;r->cF+=(line->aK
++line->aL)/2;r->bY+=(line->aL-line->aK)/7.0f;r->F++;
+br[ai].dm=line->aK;br[ai].dn=line->aL;br[ai].co=line
+->E;}int hz=0;for(int i=0;i<fK;i++){aa*r=&bg[i];int
+el=(int)(r->bY/r->F*1.4f);if(el<3)el=3;if(r->F>=el&&
+r->eF-r->eE>=4)bg[hz++]=*r;}return hz;}static int aS
+(const void*a,const void*b){const X*la=(const X*)a;
+const X*lb=(const X*)b;return la->E-lb->E;}static
+int cq(bG*ag,X*ej,int hA){int ic=hA?ag->aT:ag->bf;
+int id=hA?ag->bf:ag->aT,dO=0;for(int o=0;o<ic;o++){
+int J[5]={0},q=0;for(int i=0;i<id;i++){int hB=hA?cZ(
+ag,o,i):cZ(ag,i,o);int hn=(q&1);if(q==0){if(hB){q=1;
+J[0]=1;}}else if(hB==hn){J[q-1]++;}else if(q<5){q++;
+J[q-1]=1;}else{if(da(J,0.8f)>0){int w=J[0]+J[1]+J[2]
++J[3]+J[4];dO=fb(ej,dO,16000,o,i-w,i-1);}J[0]=J[2];J
+[1]=J[3];J[2]=J[4];J[3]=1;J[4]=0;q=4;}}}return dO;}
+static int dq(bG*ag,ac*ah,int ed){X*eI=malloc(16000*
+sizeof(X));X*eJ=malloc(16000*sizeof(X));int ir=128,
+is=128;aa*fc=malloc(ir*sizeof(aa));aa*fd=malloc(is*
+sizeof(aa));int it=cq(ag,eI,0);int iu=cq(ag,eJ,1);
+qsort(eI,it,sizeof(X),aS);qsort(eJ,iu,sizeof(X),aS);
+int iR=bO(eI,it,&fc,&ir);int iS=bO(eJ,iu,&fd,&is);
+int F=0;for(int hi=0;hi<iR&&F<ed;hi++){aa*hr=&fc[hi]
+;int fx=hr->cF/hr->F;float hm=hr->bY/hr->F;for(int
+vi=0;vi<iS&&F<ed;vi++){aa*vr=&fd[vi];int fy=vr->cF/
+vr->F;float vm=vr->bY/vr->F;if(fx<vr->eE-hm*1.5f||fx
+>vr->eF+hm*1.5f)continue;if(fy<hr->eE-vm*1.5f||fy>hr
+->eF+vm*1.5f)continue;float ie=(hm>vm)?hm/vm:vm/hm;
+if(ie>((hm>3.0f||vm>3.0f)?3.0f:2.5f))continue;float
+fm=sqrtf(hm*vm);int gt=0;for(int i=0;i<F&&!gt;i++){
+int dx=ah[i].x-fx,dy=ah[i].y-fy;gt=dx*dx+dy*dy<fm*fm
+*16;}if(!gt){ah[F].x=fx;ah[F].y=fy;ah[F].Z=fm;ah[F].
+fI=hm;ah[F].fJ=vm;F++;}}}free(eI);free(eJ);free(fc);
+free(fd);return F;}static int dZ(int v){return 17+4*
+v;}typedef struct{U eK[177][177];int aH;int G;}fE;
+static void cN(int G,int E[8]){if(G==1){E[0]=0;
+return;}int aH=17+G*4,hX=aH-7,hQ=G/7+2;int aA=hX-6,
+iv=hQ-1;int iw=((aA*2+iv)/(iv*2)+1)&~1;E[0]=6;for(
+int i=hQ-1;i>=1;i--)E[i]=hX-(hQ-1-i)*iw;E[hQ]=0;}
+static int ck(int G,int x,int y){int s=dZ(G);if((x<9
+&&y<9)||(x<8&&y>=s-8)||(x>=s-8&&y<9))return 1;if((y
+==8&&(x<9||x>=s-8))||(x==8&&(y<9||y>=s-8)))return 1;
+if(x==6||y==6)return 1;if(G>=2){int E[8];cN(G,E);for
+(int i=0;E[i];i++)for(int j=0;E[j];j++){int ax=E[i],
+ay=E[j];if((ax<9&&ay<9)||(ax<9&&ay>=s-8)||(ax>=s-8&&
+ay<9))continue;if(x>=ax-2&&x<=ax+2&&y>=ay-2&&y<=ay+2
+)return 1;}}if(G>=7){if((x<6&&y>=s-11&&y<s-8)||(y<6
+&&x>=s-11&&x<s-8))return 1;}return 0;}static int ar(
+bG*ag,const aJ*O,int mx,int my,int hf);static int cG
+(fE*qr,bG*ag,const aJ*O){static const int xs[15]={8,
+8,8,8,8,8,8,8,7,5,4,3,2,1,0};static const int ys[15]
+={0,1,2,3,4,5,7,8,8,8,8,8,8,8,8};V fM=0;for(int i=14
+;i>=0;i--){int gG;if(ag)gG=ar(ag,O,xs[i],ys[i],0);
+else gG=qr->eK[ys[i]][xs[i]]&1;fM=(fM<<1)|gG;}return
+fM^0x5412;}static const W bH[32]={0x0000,0x0537,
+0x0a6e,0x0f59,0x11eb,0x14dc,0x1b85,0x1eb2,0x23d6,
+0x26e1,0x29b8,0x2c8f,0x323d,0x370a,0x3853,0x3d64,
+0x429b,0x47ac,0x48f5,0x4dc2,0x5370,0x5647,0x591e,
+0x5c29,0x614d,0x647a,0x6b23,0x6e14,0x70a6,0x7591,
+0x7ac8,0x7fff};static int ap(int am){int em=am;for(
+int i=14;i>=10;i--){if(em&(1<<i))em^=(0x537<<(i-10))
+;}if(em==0)return am;int fu=-1,dz=5;for(int i=0;i<32
+;i++){int hC=am^bH[i];int fe=0;while(hC){fe+=hC&1;hC
+>>=1;}if(fe<dz){dz=fe;fu=bH[i];}}return fu;}static U
+bI[512],dP[256];static void cR(void){static int gH=0
+;if(gH)return;gH=1;int x=1;for(int i=0;i<255;i++){bI
+[i]=x;dP[x]=i;x=(x<<1)^((x>>7)*0x11d);}for(int i=255
+;i<512;i++)bI[i]=bI[i-255];}static U bt(U a,U b){
+return(a&&b)?bI[dP[a]+dP[b]]:0;}static U gu(U a,U b)
+{return(a&&b)?bI[(dP[a]+255-dP[b])%255]:0;}static U
+hR(U a){return a?bI[255-dP[a]]:0;}static void cr(U*l
+,int bv,int aB,U*aj){cR();for(int i=0;i<aB;i++){U s=
+0;for(int j=0;j<bv;j++){s=bt(s,bI[i])^l[j];}aj[i]=s;
+}}static int dA(U*aj,int aB,U*cs){cR();U C[256]={0};
+U B[256]={0};C[0]=1;B[0]=1;int L=0,m=1;U b=1;for(int
+n=0;n<aB;n++){U d=aj[n];for(int i=1;i<=L;i++){d^=bt(
+C[i],aj[n-i]);}if(d==0){m++;}else if(2*L<=n){U T[256
+];memcpy(T,C,sizeof(T));U hD=gu(d,b);for(int i=0;i<
+aB-m;i++){C[i+m]^=bt(hD,B[i]);}memcpy(B,T,sizeof(B))
+;L=n+1-L;b=d;m=1;}else{U hD=gu(d,b);for(int i=0;i<aB
+-m;i++){C[i+m]^=bt(hD,B[i]);}m++;}}memcpy(cs,C,aB+1)
+;return L;}static int ff(U*cs,int Q,int n,int*aM){cR
+();int fN=0;for(int i=0;i<n;i++){U hx=cs[0];for(int
+j=1;j<=Q;j++){U ig=((255-i)*j)%255;hx^=bt(cs[j],bI[
+ig]);}if(hx==0)aM[fN++]=n-1-i;}return(fN==Q)?fN:-1;}
+static void gS(U*aj,U*cs,int Q,int*aM,int n,U*eL){cR
+();U hE[256]={0};for(int i=0;i<Q;i++){U v=0;for(int
+j=0;j<=i;j++){v^=bt(aj[i-j],cs[j]);}hE[i]=v;}U eM[
+256]={0};for(int i=1;i<=Q;i+=2){eM[i-1]=cs[i];}for(
+int i=0;i<Q;i++){int E=aM[i];int ee=(n-1-E)%255;U fO
+=0;for(int j=0;j<Q;j++){fO^=bt(hE[j],bI[(ee*j)%255])
+;}U bP=0;for(int j=0;j<Q;j++){int p=(ee*j)%255;bP^=
+bt(eM[j],bI[p]);}if(bP!=0){eL[i]=gu(fO,bP);}else{eL[
+i]=0;}}}static int en(U*l,int D,int aB){cR();int bv=
+D+aB,gI=aB/2;U aj[256];cr(l,bv,aB,aj);int gj=1;for(
+int i=0;i<aB;i++)if(aj[i]!=0){gj=0;break;}if(gj)
+return 0;U cs[256]={0};int Q=dA(aj,aB,cs);if(Q>gI)
+return-1;int aM[256];int fN=ff(cs,Q,bv,aM);if(fN!=Q)
+return-1;U eL[256];gS(aj,cs,Q,aM,bv,eL);for(int i=0;
+i<Q;i++){if(aM[i]>=0&&aM[i]<bv)l[aM[i]]^=eL[i];}cr(l
+,bv,aB,aj);for(int i=0;i<aB;i++)if(aj[i]!=0)return-1
+;return Q;}static int ho(int m,int x,int y){switch(m
+){case 0:return(y+x)%2==0;case 1:return y%2==0;case
+2:return x%3==0;case 3:return(y+x)%3==0;case 4:
+return(y/2+x/3)%2==0;case 5:return(y*x)%2+(y*x)%3==0
+;case 6:return((y*x)%2+(y*x)%3)%2==0;default:return(
+(y+x)%2+(y*x)%3)%2==0;}}static void gT(fE*qr,int iz)
+{for(int y=0;y<qr->aH;y++)for(int x=0;x<qr->aH;x++)
+if(!ck(qr->G,x,y)&&ho(iz,x,y))qr->eK[y][x]^=1;}
+typedef struct{int bs,dw,ns;}dB;typedef struct{int
+hY;dB iT[4];}gv;static const gv ge[41]={{0},{26,{{26
+,16,1},{26,19,1},{26,9,1},{26,13,1}}},{44,{{44,28,1}
+,{44,34,1},{44,16,1},{44,22,1}}},{70,{{70,44,1},{70,
+55,1},{35,13,2},{35,17,2}}},{100,{{50,32,2},{100,80,
+1},{25,9,4},{50,24,2}}},{134,{{67,43,2},{134,108,1},
+{33,11,2},{33,15,2}}},{172,{{43,27,4},{86,68,2},{43,
+15,4},{43,19,4}}},{196,{{49,31,4},{98,78,2},{39,13,4
+},{32,14,2}}},{242,{{60,38,2},{121,97,2},{40,14,4},{
+40,18,4}}},{292,{{58,36,3},{146,116,2},{36,12,4},{36
+,16,4}}},{346,{{69,43,4},{86,68,2},{43,15,6},{43,19,
+6}}},{404,{{80,50,1},{101,81,4},{36,12,3},{50,22,4}}
+},{466,{{58,36,6},{116,92,2},{42,14,7},{46,20,4}}},{
+532,{{59,37,8},{133,107,4},{33,11,12},{44,20,8}}},{
+581,{{64,40,4},{145,115,3},{36,12,11},{36,16,11}}},{
+655,{{65,41,5},{109,87,5},{36,12,11},{54,24,5}}},{
+733,{{73,45,7},{122,98,5},{45,15,3},{43,19,15}}},{
+815,{{74,46,10},{135,107,1},{42,14,2},{50,22,1}}},{
+901,{{69,43,9},{150,120,5},{42,14,2},{50,22,17}}},{
+991,{{70,44,3},{141,113,3},{39,13,9},{47,21,17}}},{
+1085,{{67,41,3},{135,107,3},{43,15,15},{54,24,15}}},
+{1156,{{68,42,17},{144,116,4},{46,16,19},{50,22,17}}
+},{1258,{{74,46,17},{139,111,2},{37,13,34},{54,24,7}
+}},{1364,{{75,47,4},{151,121,4},{45,15,16},{54,24,11
+}}},{1474,{{73,45,6},{147,117,6},{46,16,30},{54,24,
+11}}},{1588,{{75,47,8},{132,106,8},{45,15,22},{54,24
+,7}}},{1706,{{74,46,19},{142,114,10},{46,16,33},{50,
+22,28}}},{1828,{{73,45,22},{152,122,8},{45,15,12},{
+53,23,8}}},{1921,{{73,45,3},{147,117,3},{45,15,11},{
+54,24,4}}},{2051,{{73,45,21},{146,116,7},{45,15,19},
+{53,23,1}}},{2185,{{75,47,19},{145,115,5},{45,15,23}
+,{54,24,15}}},{2323,{{74,46,2},{145,115,13},{45,15,
+23},{54,24,42}}},{2465,{{74,46,10},{145,115,17},{45,
+15,19},{54,24,10}}},{2611,{{74,46,14},{145,115,17},{
+45,15,11},{54,24,29}}},{2761,{{74,46,14},{145,115,13
+},{46,16,59},{54,24,44}}},{2876,{{75,47,12},{151,121
+,12},{45,15,22},{54,24,39}}},{3034,{{75,47,6},{151,
+121,6},{45,15,2},{54,24,46}}},{3196,{{74,46,29},{152
+,122,17},{45,15,24},{54,24,49}}},{3362,{{74,46,13},{
+152,122,4},{45,15,42},{54,24,48}}},{3532,{{75,47,40}
+,{147,117,20},{45,15,10},{54,24,43}}},{3706,{{75,47,
+18},{148,118,19},{45,15,20},{54,24,34}}}};static int
+ea(U*hS,int bJ,int bc,int dw,int bs,int ns,int dd,U*
+fP){int eN=(dd>=ns);int dQ=eN?dw+1:dw;int eo=bs-dw;
+int hp=eN?bs+1:bs;int E=0;for(int j=0;j<dQ;j++){int
+ct;if(j<dw){ct=j*bc+dd;}else{ct=dw*bc+(dd-ns);}if(ct
+<bJ){fP[E++]=hS[ct];}}int gJ=dw*bc+(bc-ns);for(int j
+=0;j<eo;j++){int ct=gJ+j*bc+dd;if(ct<bJ){fP[E++]=hS[
+ct];}}return hp;}static int cC(U*hS,int bJ,int G,int
+ep,U*hq,int gK,int*cH,int*cu){if(G<1||G>40)return 0;
+const dB*sb=&ge[G].iT[ep];int ns=sb->ns;int dw=sb->
+dw;int bs=sb->bs;int gw=ns*bs;int M=bJ-gw;int nl=(M>
+0)?M/(bs+1):0;int bc=ns+nl;int eo=bs-dw;int dR=0,ef=
+0,aU=0;for(int i=0;i<bc&&dR<gK;i++){U aY[256];(void)
+ea(hS,bJ,bc,dw,bs,ns,i,aY);int eN=(i>=ns);int dQ=eN?
+dw+1:dw;int eq=en(aY,dQ,eo);if(eq<0){aU++;}else if(
+eq>0){ef+=eq;}for(int j=0;j<dQ&&dR<gK;j++){hq[dR++]=
+aY[j];}}if(cH)*cH=ef;if(cu)*cu=aU;if(aU==bc){return-
+1;}return dR;}static int fg(fE*qr,U*l,int bZ){int aH
+=qr->aH,bi=0,gG=7,up=1;memset(l,0,bZ);for(int cO=aH-
+1;cO>=0;cO-=2){if(cO==6)cO--;int hT=up?aH-1:0;int iA
+=up?-1:aH;int ih=up?-1:1;for(int fa=hT;fa!=iA;fa+=ih
+)for(int c=0;c<2&&cO-c>=0;c++)if(!ck(qr->G,cO-c,fa)
+&&bi<bZ){if(qr->eK[fa][cO-c])l[bi]|=(1<<gG);if(--gG<
+0){gG=7;bi++;}}up=!up;}return bi+(gG<7?1:0);}static
+int er(U*l,int D,int G,char*cI,int bB){int eO=0,aZ=0
+;
+#define es(n)({int fH=0;for(int i=0;i<(n);i++){int \
+gk=eO/8;int hF=7-(eO%8);if(gk<D){fH=(fH<<1)|((l[gk \
+]>>hF)&1);}eO++;}fH;})
+while(eO<D*8&&aZ<bB-1){int hZ=es(4);if(hZ==0)break;
+if(hZ==4){int gL=G<=9?8:16;int F=es(gL);for(int i=0;
+i<F&&aZ<bB-1;i++)cI[aZ++]=es(8);}else break;}cI[aZ]=
+'\0';return aZ;}static int bj(int x1,int y1,int x2,
+int y2){int dx=x2-x1,dy=y2-y1;return dx*dx+dy*dy;}
+static int fQ(int x1,int y1,int x2,int y2,int x3,int
+y3){return(x2-x1)*(y3-y1)-(y2-y1)*(x3-x1);}static
+int de(ac*fp,int*tl,int*tr,int*bl){int d[3]={bj(fp[0
+].x,fp[0].y,fp[1].x,fp[1].y),bj(fp[0].x,fp[0].y,fp[2
+].x,fp[2].y),bj(fp[1].x,fp[1].y,fp[2].x,fp[2].y)};
+int eP=(d[0]>=d[1]&&d[0]>=d[2])?2:(d[1]>=d[2])?1:0;
+int p1=(eP==0)?1:0,p2=(eP==2)?1:2;*tl=eP;int cp=fQ(
+fp[eP].x,fp[eP].y,fp[p1].x,fp[p1].y,fp[p2].x,fp[p2].
+y);*tr=(cp>0)?p1:p2;*bl=(cp>0)?p2:p1;return 1;}
+static void dr(du gf[4],bF*H){float x0=gf[0].x,y0=gf
+[0].y,x1=gf[1].x,y1=gf[1].y;float x2=gf[2].x,y2=gf[2
+].y,x3=gf[3].x,y3=gf[3].y;float iB=x1-x3,iC=y1-y3,iD
+=x2-x3,iE=y2-y3;float sx=x0-x1+x3-x2,sy=y0-y1+y3-y2;
+float hU=iB*iE-iD*iC;if(fabsf(hU)<1e-10f)hU=1e-10f;
+float g=(sx*iE-iD*sy)/hU;float h=(iB*sy-sx*iC)/hU;H
+->h[0][0]=x1-x0+g*x1;H->h[0][1]=x2-x0+h*x2;H->h[0][2
+]=x0;H->h[1][0]=y1-y0+g*y1;H->h[1][1]=y2-y0+h*y2;H->
+h[1][2]=y0;H->h[2][0]=g;H->h[2][1]=h;H->h[2][2]=1.0f
+;}static du eQ(const bF*H,float x,float y){du as;
+float w=H->h[2][0]*x+H->h[2][1]*y+H->h[2][2];if(fabs
+(w)<1e-10)w=1e-10;as.x=(H->h[0][0]*x+H->h[0][1]*y+H
+->h[0][2])/w;as.y=(H->h[1][0]*x+H->h[1][1]*y+H->h[1]
+[2])/w;return as;}static aJ bC(ac*fp,int tl,int tr,
+int bl,int cJ){aJ O={0};float gU=fp[tr].x-fp[tl].x,
+gV=fp[tr].y-fp[tl].y;float gW=fp[bl].x-fp[tl].x,gX=
+fp[bl].y-fp[tl].y;float hG=sqrtf(gU*gU+gV*gV);float
+hH=sqrtf(gW*gW+gX*gX);float gm=cbrtf(fp[tl].Z*fp[tr]
+.Z*fp[bl].Z);if(gm<1.0f)gm=1.0f;float gx=(hG+hH)/(
+2.0f*gm);int G=cJ>0?cJ:((int)(gx+7.5f)-17+2)/4;if(G<
+1)G=1;if(G>40)G=40;int aH=17+4*G;O.G=G;O.aH=aH;O.Z=
+gm;float hg=aH-7;float a=gU/hg,b=gW/hg;float c=gV/hg
+,d=gX/hg;float tx=fp[tl].x-3.5f*(a+b);float ty=fp[tl
+].y-3.5f*(c+d);du gf[4]={{tx,ty},{a*aH+tx,c*aH+ty},{
+b*aH+tx,d*aH+ty},{(a+b)*aH+tx,(c+d)*aH+ty}};dr(gf,&O
+.fs);return O;}static int ar(bG*ag,const aJ*tf,int
+mx,int my,int hf){float u=(mx+0.5f)/tf->aH,v=(my+
+0.5f)/tf->aH;du p=eQ(&tf->fs,u,v);if(!hf)return cZ(
+ag,(int)(p.x+0.5f),(int)(p.y+0.5f))?1:0;int hI=0;for
+(int dy=-1;dy<=1;dy++)for(int dx=-1;dx<=1;dx++){int
+ix=(int)(p.x+dx+0.5f),iy=(int)(p.y+dy+0.5f);hI+=cZ(
+ag,ix,iy)*((dx|dy)?1:2);}return hI>5?1:0;}static int
+cS(bG*ag,ac*fp,int iU,char*cI,int bB){if(iU<3){
+return 0;}int tl,tr,bl;if(!de(fp,&tl,&tr,&bl)){
+return 0;}aJ cv=bC(fp,tl,tr,bl,0);int aN=cv.G;int bQ
+[10];int bX=0;bQ[bX++]=aN;float Z=cv.Z;if(aN>=25||Z<
+8.0f){for(int gY=-2;gY<=2;gY++){int v=aN+gY;if(v>=1
+&&v<=40&&v!=aN){bQ[bX++]=v;}}}for(int vi=0;vi<bX;vi
+++){int gy=bQ[vi];aJ O=bC(fp,tl,tr,bl,gy);fE qr;qr.G
+=O.G;qr.aH=O.aH;for(int my=0;my<O.aH;my++){for(int
+mx=0;mx<O.aH;mx++){qr.eK[my][mx]=ar(ag,&O,mx,my,1);}
+}int am=cG(0,ag,&O);int aV=ap(am);if(aV<0){am=cG(&qr
+,0,0);aV=ap(am);}if(aV<0){continue;}am=aV;int gg=(am
+>>10)&0x07;int ep=(am>>13)&0x03;gT(&qr,gg);U cw[4096
+];int hJ=fg(&qr,cw,sizeof(cw));U et[4096];int gZ=0,
+cK=0;int D=cC(cw,hJ,O.G,ep,et,sizeof(et),&gZ,&cK);
+int as=er(et,D,O.G,cI,bB);if(as>0){if(bX>1&&cK>0){
+continue;}return as;}}return 0;}static int cx(bG*ag,
+av*I);static void fh(av*cl){cl->I=0;cl->F=0;cl->aF=0
+;}static void fi(av*cl){for(int i=0;i<cl->F;i++)free
+(cl->I[i].l);free(cl->I);cl->I=0;cl->F=0;cl->aF=0;}
+static int dp(av*cl,char bp,int aE,int aA,const U*l,
+int D){if(cl->F>=cl->aF){int dC=cl->aF?cl->aF*2:64;
+cY*fv=realloc(cl->I,dC*sizeof(cY));if(!fv)return 0;
+cl->I=fv;cl->aF=dC;}cY*c=&cl->I[cl->F];c->bp=bp;c->
+aE=aE;c->aA=aA;c->l=malloc(D);memcpy(c->l,l,D);c->D=
+D;cl->F++;return 1;}static int eu(const char*dD,char
+*bp,int*aE,int*aA,const char**cc){if(dD[0]!='N'&&dD[
+0]!='P')return 0;*bp=dD[0];int n=0;if(sscanf(dD+1,
+"%d/%d%n",aE,aA,&n)!=2)return 0;const char*p=dD+1+n;
+while(*p==':'||*p==' ')p++;*cc=p;return 1;}static
+int fR(const void*a,const void*b){const cY*ca=a,*cb=
+b;return(ca->bp!=cb->bp)?ca->bp-cb->bp:ca->aE-cb->aE
+;}static void cT(av*cl){if(cl->F>1)qsort(cl->I,cl->F
+,sizeof(cY),fR);if(cl->F<2)return;int w=1;for(int r=
+1;r<cl->F;r++){if(cl->I[r].bp!=cl->I[w-1].bp||cl->I[
+r].aE!=cl->I[w-1].aE)cl->I[w++]=cl->I[r];else free(
+cl->I[r].l);}cl->F=w;}static int8_t ad[256];static
+void hs(void){static int gH=0;if(gH)return;gH=1;
+memset(ad,-1,256);for(int i=0;i<26;i++){ad['A'+i]=i;
+ad['a'+i]=26+i;}for(int i=0;i<10;i++)ad['0'+i]=52+i;
+ad['+']=62;ad['/']=63;}static int fS(const char*ii,
+int ak,U*cI,int bB){hs();int ab=0,aW=0;V ha=0;for(
+int i=0;i<ak;i++){int c=(unsigned char)ii[i],fH=ad[c
+];if(c=='='||c=='\n'||c=='\r'||c==' '||fH<0)continue
+;ha=(ha<<6)|fH;aW+=6;if(aW>=8){aW-=8;if(ab<bB)cI[ab
+++]=(ha>>aW)&0xff;}}return ab;}static int fj(av*cl,
+int Y,int bq){cR();int as=0,bK=0,ba=0,an=0;int*ev=
+calloc(Y+1,sizeof(int));int*ex=calloc(bq+1,sizeof(
+int));int*bk=calloc(Y,sizeof(int));int*cm=calloc(bq,
+sizeof(int));U**az=0,**aw=0;for(int i=0;i<cl->F;i++)
+{cY*c=&cl->I[i];if(c->bp=='N'&&c->aE<=Y)ev[c->aE]=1;
+else if(c->bp=='P'&&c->aE<=bq)ex[c->aE]=1;if(!ba)ba=
+c->D;}for(int i=1;i<=Y;i++)if(!ev[i])bk[bK++]=i;if(
+bK==0){as=1;goto gM;}for(int p=1;p<=bq&&an<bK;p++)if
+(ex[p])cm[an++]=p;if(an<bK){fprintf(stderr,
+"Warning: %d bK, only %d parity\n",bK,an);goto gM;}
+int hK=Y-bK;int dS=an+hK;int eR=Y+an;az=malloc(dS*
+sizeof(U*));aw=malloc(dS*sizeof(U*));for(int i=0;i<
+dS;i++){az[i]=calloc(eR,1);aw[i]=calloc(ba,1);}for(
+int i=0;i<an;i++){int ij=cm[i];for(int d=1;d<=Y;d++)
+az[i][d-1]=bI[((d-1)*(ij-1))%255];az[i][Y+i]=1;}int
+fa=an;for(int i=0;i<cl->F;i++){cY*c=&cl->I[i];int cO
+;if(c->bp=='N'&&c->aE<=Y)cO=c->aE-1;else if(c->bp==
+'P'&&c->aE<=bq){int pi=-1;for(int j=0;j<an;j++)if(cm
+[j]==c->aE){pi=j;break;}if(pi<0)continue;cO=Y+pi;}
+else continue;az[fa][cO]=1;memcpy(aw[fa],c->l,ba);fa
+++;}for(int cO=0;cO<eR;cO++){int cP=-1;for(int r=0;r
+<dS;r++)if(az[r][cO]){cP=r;break;}if(cP<0)continue;U
+iF=hR(az[cP][cO]);for(int j=0;j<eR;j++)az[cP][j]=bt(
+az[cP][j],iF);for(int b=0;b<ba;b++)aw[cP][b]=bt(aw[
+cP][b],iF);for(int r=0;r<dS;r++){if(r!=cP&&az[r][cO]
+){U f=az[r][cO];for(int j=0;j<eR;j++)az[r][j]^=bt(f,
+az[cP][j]);for(int b=0;b<ba;b++)aw[r][b]^=bt(f,aw[cP
+][b]);}}}for(int i=0;i<bK;i++){int cO=bk[i]-1;for(
+int r=0;r<dS;r++){if(az[r][cO]==1){int hL=1;for(int
+j=0;j<eR&&hL;j++)if(j!=cO&&az[r][j])hL=0;if(hL){dp(
+cl,'N',bk[i],Y,aw[r],ba);break;}}}}as=1;gM:if(az)for
+(int i=0;i<bK;i++)free(az[i]);if(aw)for(int i=0;i<bK
+;i++)free(aw[i]);free(az);free(aw);free(cm);free(ev)
+;free(ex);free(bk);return as;}static U*fT(av*cl,int*
+ab){int aA=0,E=0;for(int i=0;i<cl->F;i++)if(cl->I[i]
+.bp=='N')aA+=cl->I[i].D;if(aA==0){*ab=0;return 0;}U*
+l=malloc(aA);for(int i=0;i<cl->F;i++)if(cl->I[i].bp
+=='N'){memcpy(l+E,cl->I[i].l,cl->I[i].D);E+=cl->I[i]
+.D;}int bb=0;V dT=0;while(bb<E&&l[bb]>='0'&&l[bb]<=
+'9')dT=dT*10+(l[bb++]-'0');if(bb<E&&l[bb]==' ')bb++;
+if(bb==0){*ab=E;return l;}int hh=(int)dT>E-bb?E-bb:(
+int)dT;memmove(l,l+bb,hh);*ab=hh;return l;}static
+const U fk[256]={99,124,119,123,242,107,111,197,48,1
+,103,43,254,215,171,118,202,130,201,125,250,89,71,
+240,173,212,162,175,156,164,114,192,183,253,147,38,
+54,63,247,204,52,165,229,241,113,216,49,21,4,199,35,
+195,24,150,5,154,7,18,128,226,235,39,178,117,9,131,
+44,26,27,110,90,160,82,59,214,179,41,227,47,132,83,
+209,0,237,32,252,177,91,106,203,190,57,74,76,88,207,
+208,239,170,251,67,77,51,133,69,249,2,127,80,60,159,
+168,81,163,64,143,146,157,56,245,188,182,218,33,16,
+255,243,210,205,12,19,236,95,151,68,23,196,167,126,
+61,100,93,25,115,96,129,79,220,34,42,144,136,70,238,
+184,20,222,94,11,219,224,50,58,10,73,6,36,92,194,211
+,172,98,145,149,228,121,231,200,55,109,141,213,78,
+169,108,86,244,234,101,122,174,8,186,120,37,46,28,
+166,180,198,232,221,116,31,75,189,139,138,112,62,181
+,102,72,3,246,14,97,53,87,185,134,193,29,158,225,248
+,152,17,105,217,142,148,155,30,135,233,206,85,40,223
+,140,161,137,13,191,230,66,104,65,153,45,15,176,84,
+187,22};static const U ht[11]={0x00,0x01,0x02,0x04,
+0x08,0x10,0x20,0x40,0x80,0x1b,0x36};static U df(U x)
+{return(x<<1)^((x>>7)*0x1b);}static void fB(const U*
+hM,U*rk,int ia){int nk=ia/4,nr=nk+6,nb=4;U*at=rk;
+memcpy(at,hM,ia);U aX[4];int i=nk;while(i<nb*(nr+1))
+{memcpy(aX,at+(i-1)*4,4);if(i%nk==0){U t=aX[0];aX[0]
+=aX[1];aX[1]=aX[2];aX[2]=aX[3];aX[3]=t;for(int j=0;j
+<4;j++)aX[j]=fk[aX[j]];aX[0]^=ht[i/nk];}else if(nk>6
+&&i%nk==4)for(int j=0;j<4;j++)aX[j]=fk[aX[j]];for(
+int j=0;j<4;j++)at[i*4+j]=at[(i-nk)*4+j]^aX[j];i++;}
+}static void gz(const U*l,size_t au,U*cd);static U*
+bR(const U*l,int au,int*fl);static void ci(V*q,const
+U*l){V a,b,c,d,e,t,w[80];for(int i=0;i<16;i++){w[i]=
+((V)l[i*4]<<24)|((V)l[i*4+1]<<16)|((V)l[i*4+2]<<8)|l
+[i*4+3];}for(int i=16;i<80;i++){t=w[i-3]^w[i-8]^w[i-
+14]^w[i-16];w[i]=(t<<1)|(t>>31);}a=q[0];b=q[1];c=q[2
+];d=q[3];e=q[4];
+#define gl(x,n)(((x)<<(n))|((x)>>(32-(n))))
+for(int i=0;i<80;i++){V f,k;if(i<20){f=(b&c)|((~b)&d
+);k=0x5a827999;}else if(i<40){f=b^c^d;k=0x6ed9eba1;}
+else if(i<60){f=(b&c)|(b&d)|(c&d);k=0x8f1bbcdc;}else
+{f=b^c^d;k=0xca62c1d6;}t=gl(a,5)+f+e+k+w[i];e=d;d=c;
+c=gl(b,30);b=a;a=t;}q[0]+=a;q[1]+=b;q[2]+=c;q[3]+=d;
+q[4]+=e;}static void ib(const U*l,size_t au,U*cd){V
+q[5]={0x67452301,0xefcdab89,0x98badcfe,0x10325476,
+0xc3d2e1f0};U aO[64];size_t E=0;while(E+64<=au){ci(q
+,l+E);E+=64;}size_t M=au-E;memcpy(aO,l+E,M);aO[M++]=
+0x80;if(M>56){memset(aO+M,0,64-M);ci(q,aO);memset(aO
+,0,56);}else{memset(aO+M,0,56-M);}uint64_t aW=au*8;
+aO[56]=aW>>56;aO[57]=aW>>48;aO[58]=aW>>40;aO[59]=aW
+>>32;aO[60]=aW>>24;aO[61]=aW>>16;aO[62]=aW>>8;aO[63]
+=aW;ci(q,aO);for(int i=0;i<5;i++){cd[i*4]=q[i]>>24;
+cd[i*4+1]=q[i]>>16;cd[i*4+2]=q[i]>>8;cd[i*4+3]=q[i];
+}}static V eS(U c){return(16+(c&15))<<((c>>4)+6);}
+static void fC(const char*aq,int bS,const U*gn,int
+bm,U dg,V F,U*hM,int cy){int hu=(dg==8)?32:20,E=0,bd
+=0;while(E<cy){U*R=0;int ak=0;if(bm==0){ak=bd+bS;R=
+malloc(ak);memset(R,0,bd);memcpy(R+bd,aq,bS);}else
+if(bm==1){ak=bd+8+bS;R=malloc(ak);memset(R,0,bd);
+memcpy(R+bd,gn,8);memcpy(R+bd+8,aq,bS);}else{int fF=
+8+bS;V cQ=F;if(cQ<(V)fF)cQ=fF;ak=bd+cQ;R=malloc(ak);
+memset(R,0,bd);int bT=bd;while(bT<ak){int fU=fF;if(
+bT+fU>ak)fU=ak-bT;if(fU<=8){memcpy(R+bT,gn,fU);}else
+{memcpy(R+bT,gn,8);int dE=fU-8;if(dE>bS)dE=bS;memcpy
+(R+bT+8,aq,dE);}bT+=fF;}}U cd[32];if(dg==8){gz(R,ak,
+cd);}else{ib(R,ak,cd);}int dU=hu;if(E+dU>cy)dU=cy-E;
+memcpy(hM+E,cd,dU);E+=dU;free(R);bd++;}}static void
+ey(const U*in,U*fl,const U*at,int nr){U q[16];memcpy
+(q,in,16);for(int i=0;i<16;i++)q[i]^=at[i];for(int
+round=1;round<=nr;round++){for(int i=0;i<16;i++)q[i]
+=fk[q[i]];U aX;aX=q[1];q[1]=q[5];q[5]=q[9];q[9]=q[13
+];q[13]=aX;aX=q[2];q[2]=q[10];q[10]=aX;aX=q[6];q[6]=
+q[14];q[14]=aX;aX=q[15];q[15]=q[11];q[11]=q[7];q[7]=
+q[3];q[3]=aX;if(round<nr){for(int c=0;c<4;c++){U a[4
+];for(int i=0;i<4;i++)a[i]=q[c*4+i];q[c*4+0]=df(a[0]
+)^df(a[1])^a[1]^a[2]^a[3];q[c*4+1]=a[0]^df(a[1])^df(
+a[2])^a[2]^a[3];q[c*4+2]=a[0]^a[1]^df(a[2])^df(a[3])
+^a[3];q[c*4+3]=df(a[0])^a[0]^a[1]^a[2]^df(a[3]);}}
+for(int i=0;i<16;i++)q[i]^=at[round*16+i];}memcpy(fl
+,q,16);}static void fn(const U*l,int D,const U*hM,
+int cy,U*cI){int nr=(cy==16)?10:(cy==24)?12:14;U at[
+240];fB(hM,at,cy);U fr[16]={0},iG[16];int E=0;while(
+E<D){ey(fr,iG,at,nr);int aP=16;if(E+aP>D)aP=D-E;for(
+int i=0;i<aP;i++){cI[E+i]=l[E+i]^iG[i];}if(aP==16){
+memcpy(fr,l+E,16);}else{memmove(fr,fr+aP,16-aP);
+memcpy(fr+16-aP,l+E,aP);}E+=aP;}}static int cL(const
+U*l,int au,int*al,int*ce){if(au<2)return-1;U cU=l[0]
+;if((cU&0x80)==0)return-1;int ae,E=1;if(cU&0x40){ae=
+cU&0x3f;if(l[E]<192){*al=l[E];E++;}else if(l[E]<224)
+{if(au<E+2)return-1;*al=((l[E]-192)<<8)+l[E+1]+192;E
++=2;}else if(l[E]==255){if(au<E+5)return-1;*al=((V)l
+[E+1]<<24)|((V)l[E+2]<<16)|((V)l[E+3]<<8)|l[E+4];E+=
+5;}else{*al=1<<(l[E]&0x1f);E++;}}else{ae=(cU>>2)&
+0x0f;int dh=cU&0x03;if(dh==0){*al=l[E];E++;}else if(
+dh==1){if(au<E+2)return-1;*al=(l[E]<<8)|l[E+1];E+=2;
+}else if(dh==2){if(au<E+4)return-1;*al=((V)l[E]<<24)
+|((V)l[E+1]<<16)|((V)l[E+2]<<8)|l[E+3];E+=4;}else{*
+al=au-E;}}*ce=E;return ae;}static int cM(const U*l,
+int au){if(au<2)return 0;if((l[0]&0x80)==0)return 0;
+int ae=(l[0]&0x40)?(l[0]&0x3f):((l[0]>>2)&0x0f);
+return(ae==3||ae==9||ae==18);}static U*gA(const U*l,
+int D,const char*aq,int*ab){*ab=0;if(!cM(l,D)){
+return 0;}int E=0;U eT[32];int bn=0,bo=7;const U*bu=
+0;int aG=0,fV=0;while(E<D){int al,ce;int ae=cL(l+E,D
+-E,&al,&ce);if(ae<0)break;const U*eU=l+E+ce;if(ae==3
+){if(al<4)return 0;int G=eU[0];if(G!=4){fprintf(
+stderr,"GPG: Unsupported SKESK v%d\n",G);return 0;}
+bo=eU[1];int bm=eU[2],eV=3;U dg=eU[eV++],gn[8]={0};V
+F=65536;if(bm==1||bm==3){memcpy(gn,eU+eV,8);eV+=8;if
+(bm==3)F=eS(eU[eV++]);}else if(bm!=0){fprintf(stderr
+,"GPG: S2K bp %d\n",bm);return 0;}bn=(bo==9)?32:(bo
+==8)?24:16;if(bo<7||bo>9){fprintf(stderr,
+"GPG: cipher %d\n",bo);return 0;}fC(aq,strlen(aq),gn
+,bm,dg,F,eT,bn);}else if(ae==9){bu=eU;aG=al;fV=0;}
+else if(ae==18){if(al<1||eU[0]!=1){fprintf(stderr,
+"GPG: Unsupported SEIPD G\n");return 0;}bu=eU+1;aG=
+al-1;fV=1;}E+=ce+al;}if(!bu||bn==0){fprintf(stderr,
+"GPG: Missing l or hM\n");return 0;}U*K=malloc(aG);
+fn(bu,aG,eT,bn,K);int bD=16;if(aG<bD+2){free(K);
+return 0;}if(K[bD-2]!=K[bD]||K[bD-1]!=K[bD+1]){
+fprintf(stderr,
+"GPG: prefix check failed (bad aq?)\n");free(K);
+return 0;}int ao=bD+2;int aQ=aG-ao;if(fV){if(aQ<22){
+free(K);return 0;}int fW=ao+aQ-22;if(K[fW]!=0xd3||K[
+fW+1]!=0x14){fprintf(stderr,"GPG: MDC not fN\n");
+free(K);return 0;}U*ez=malloc(ao+aQ-20);memcpy(ez,K,
+ao+aQ-20);U dF[20];ib(ez,ao+aQ-20,dF);free(ez);if(
+memcmp(dF,K+fW+2,20)!=0){fprintf(stderr,
+"GPG: MDC failed\n");free(K);return 0;}aQ-=22;}int
+bU,fo,fX=cL(K+ao,aQ,&bU,&fo);U*as=0;if(fX==11){const
+U*go=K+ao+fo;if(bU<6){free(K);return 0;}int gh=go[1]
+,gp=2+gh+4;int cj=bU-gp;as=malloc(cj);if(as){memcpy(
+as,go+gp,cj);*ab=cj;}}else if(fX==8){const U*cb=K+ao
++fo;int bV=cb[0];if(bV==0){as=malloc(bU-1);if(as){
+memcpy(as,cb+1,bU-1);*ab=bU-1;}}else if(bV==1||bV==2
+){int iH=(bV==2)?3:1;int iI=(bV==2)?6:0;int cV=bU-1-
+iI;if(cV>0){U*dG=malloc(cV+18);memcpy(dG,
+"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff",10);
+memcpy(dG+10,cb+iH,cV);memset(dG+10+cV,0,8);as=bR(dG
+,cV+18,ab);free(dG);}}else{fprintf(stderr,
+"GPG: compression %d\n",bV);}}else{fprintf(stderr,
+"GPG: packet bp %d\n",fX);}free(K);return as;}
+typedef struct{W cW;U aW;}aR;typedef struct{const U*
+in;int eW;int bL;V cz;int bM;U*fl;int ab;int aZ;int
+dH;}aD;static V S(aD*s,int n){while(s->bM<n){if(s->
+bL>=s->eW)return 0;s->cz|=(V)s->in[s->bL++]<<s->bM;s
+->bM+=8;}V fH=s->cz&((1<<n)-1);s->cz>>=n;s->bM-=n;
+return fH;}static int dI(aD*s,U b){if(s->aZ>=s->dH){
+int dC=s->dH?s->dH*2:4096;s->fl=realloc(s->fl,dC);s
+->dH=dC;}s->fl[s->aZ++]=b;return 1;}static void di(U
+*af){for(int i=0;i<144;i++)af[i]=8;for(int i=144;i<
+256;i++)af[i]=9;for(int i=256;i<280;i++)af[i]=7;for(
+int i=280;i<288;i++)af[i]=8;}static int cf(const U*
+af,int F,aR*fY,int*bE){int gq[16]={0};int bZ=0;for(
+int i=0;i<F;i++){if(af[i]){gq[af[i]]++;if(af[i]>bZ)
+bZ=af[i];}}*bE=bZ>9?9:bZ;int hN=0;int eA[16];eA[0]=0
+;for(int aW=1;aW<=bZ;aW++){hN=(hN+gq[aW-1])<<1;eA[aW
+]=hN;}for(int i=0;i<F;i++){int au=af[i];if(au){int c
+=eA[au]++;int ik=0;for(int j=0;j<au;j++){ik=(ik<<1)|
+(c&1);c>>=1;}if(au<=*bE){int iJ=1<<(*bE-au);for(int
+j=0;j<iJ;j++){int gE=ik|(j<<au);fY[gE].cW=i;fY[gE].
+aW=au;}}}}return 1;}static int dj(aD*s,aR*fY,int bE)
+{while(s->bM<bE){if(s->bL>=s->eW)return-1;s->cz|=(V)
+s->in[s->bL++]<<s->bM;s->bM+=8;}int gE=s->cz&((1<<bE
+)-1);int aW=fY[gE].aW;int cW=fY[gE].cW;s->cz>>=aW;s
+->bM-=aW;return cW;}static const int hv[]={3,4,5,6,7
+,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99
+,115,131,163,195,227,258};static const int hb[]={0,0
+,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5
+,0};static const int hc[]={1,2,3,4,5,7,9,13,17,25,33
+,49,65,97,129,193,257,385,513,769,1025,1537,2049,
+3073,4097,6145,8193,12289,16385,24577};static const
+int gN[]={0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,
+9,10,10,11,11,12,12,13,13};static const int fZ[19]={
+16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15};
+static int cg(aD*s,aR*lt,int lb,aR*dt,int db){aR*bz=
+lt,*be=dt;int bW=lb,bA=db;while(1){int cW=dj(s,bz,bW
+);if(cW<0)return 0;if(cW<256){if(!dI(s,cW))return 0;
+}else if(cW==256){return 1;}else{cW-=257;if(cW>=29)
+return 0;int au=hv[cW]+S(s,hb[cW]);int dV=dj(s,be,bA
+);if(dV<0||dV>=30)return 0;int iK=hc[dV]+S(s,gN[dV])
+;for(int i=0;i<au;i++){int iL=s->aZ-iK;if(iL<0)
+return 0;if(!dI(s,s->fl[iL]))return 0;}}}}static int
+dJ(aD*s){aR bz[512],be[32];int bW,bA;U gO[288],gr[32
+];di(gO);cf(gO,288,bz,&bW);for(int i=0;i<32;i++)gr[i
+]=5;cf(gr,32,be,&bA);return cg(s,bz,bW,be,bA);}
+static int dk(aD*s){int hj=S(s,5)+257;int hd=S(s,5)+
+1;int il=S(s,4)+4;if(hj>286||hd>30)return 0;U cX[19]
+={0};for(int i=0;i<il;i++){cX[fZ[i]]=S(s,3);}aR dK[
+128];int eg;if(!cf(cX,19,dK,&eg))return 0;U af[286+
+30];int cn=hj+hd;int i=0;while(i<cn){int cW=dj(s,dK,
+eg);if(cW<0)return 0;if(cW<16){af[i++]=cW;}else if(
+cW==16){if(i==0)return 0;int eX=3+S(s,2);U iM=af[i-1
+];while(eX--&&i<cn)af[i++]=iM;}else if(cW==17){int
+eX=3+S(s,3);while(eX--&&i<cn)af[i++]=0;}else if(cW==
+18){int eX=11+S(s,7);while(eX--&&i<cn)af[i++]=0;}
+else{return 0;}}aR bz[32768];aR be[32768];int bW,bA;
+if(!cf(af,hj,bz,&bW))return 0;if(!cf(af+hj,hd,be,&bA
+))return 0;return cg(s,bz,bW,be,bA);}static U*bR(
+const U*l,int D,int*ab){if(D<10){*ab=0;return 0;}if(
+l[0]!=0x1f||l[1]!=0x8b){*ab=0;return 0;}if(l[2]!=8){
+*ab=0;return 0;}int gB=l[3];int E=10;if(gB&0x04){if(
+E+2>D){*ab=0;return 0;}int iN=l[E]|(l[E+1]<<8);E+=2+
+iN;}if(gB&0x08){while(E<D&&l[E])E++;E++;}if(gB&0x10)
+{while(E<D&&l[E])E++;E++;}if(gB&0x02)E+=2;if(E>=D){*
+ab=0;return 0;}aD s={0};s.in=l+E;s.eW=D-E-8;s.bL=0;s
+.fl=0;s.aZ=0;s.dH=0;int hk;do{hk=S(&s,1);int he=S(&s
+,2);if(he==0){s.cz=0;s.bM=0;if(s.bL+4>s.eW)break;int
+au=s.in[s.bL]|(s.in[s.bL+1]<<8);s.bL+=4;for(int i=0;
+i<au&&s.bL<s.eW;i++){if(!dI(&s,s.in[s.bL++]))break;}
+}else if(he==1){if(!dJ(&s))break;}else if(he==2){if(
+!dk(&s))break;}else{break;}}while(!hk);*ab=s.aZ;
+return s.fl;}static const V hw[64]={0x428a2f98,
+0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,
+0x59f111f1,0x923f82a4,0xab1c5ed5,0xd807aa98,
+0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,
+0x80deb1fe,0x9bdc06a7,0xc19bf174,0xe49b69c1,
+0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,
+0x4a7484aa,0x5cb0a9dc,0x76f988da,0x983e5152,
+0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,
+0xd5a79147,0x06ca6351,0x14292967,0x27b70a85,
+0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,
+0x766a0abb,0x81c2c92e,0x92722c85,0xa2bfe8a1,
+0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,
+0xd6990624,0xf40e3585,0x106aa070,0x19a4c116,
+0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,
+0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,0x748f82ee,
+0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,
+0xa4506ceb,0xbef9a3f7,0xc67178f2};
+#define ei(x,n)(((x)>>(n))|((x)<<(32-(n))))
+#define CH(x,y,z)(((x)&(y))^(~(x)&(z)))
+#define iV(x,y,z)(((x)&(y))^((x)&(z))^((y)&(z)))
+#define iW(x)(ei(x,2)^ei(x,13)^ei(x,22))
+#define iX(x)(ei(x,6)^ei(x,11)^ei(x,25))
+#define iO(x)(ei(x,7)^ei(x,18)^((x)>>3))
+#define iP(x)(ei(x,17)^ei(x,19)^((x)>>10))
+static void bN(V*q,const U*aY){V w[64];for(int i=0;i
+<16;i++){w[i]=((V)aY[i*4]<<24)|((V)aY[i*4+1]<<16)|((
+V)aY[i*4+2]<<8)|(V)aY[i*4+3];}for(int i=16;i<64;i++)
+{w[i]=iP(w[i-2])+w[i-7]+iO(w[i-15])+w[i-16];}V a=q[0
+],b=q[1],c=q[2],d=q[3];V e=q[4],f=q[5],g=q[6],h=q[7]
+;for(int i=0;i<64;i++){V t1=h+iX(e)+CH(e,f,g)+hw[i]+
+w[i];V t2=iW(a)+iV(a,b,c);h=g;g=f;f=e;e=d+t1;d=c;c=b
+;b=a;a=t1+t2;}q[0]+=a;q[1]+=b;q[2]+=c;q[3]+=d;q[4]+=
+e;q[5]+=f;q[6]+=g;q[7]+=h;}static void gz(const U*l,
+size_t au,U*cd){V q[8]={0x6a09e667,0xbb67ae85,
+0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,
+0x1f83d9ab,0x5be0cd19};size_t i;for(i=0;i+64<=au;i+=
+64){bN(q,l+i);}U aY[64];size_t M=au-i;memcpy(aY,l+i,
+M);aY[M]=0x80;if(M>=56){memset(aY+M+1,0,63-M);bN(q,
+aY);memset(aY,0,56);}else{memset(aY+M+1,0,55-M);}
+uint64_t gP=au*8;for(int j=0;j<8;j++){aY[63-j]=gP&
+0xff;gP>>=8;}bN(q,aY);for(int j=0;j<8;j++){cd[j*4]=(
+q[j]>>24)&0xff;cd[j*4+1]=(q[j]>>16)&0xff;cd[j*4+2]=(
+q[j]>>8)&0xff;cd[j*4+3]=q[j]&0xff;}}static int fq(ac
+*p0,ac*p1,ac*p2){float ms[3]={p0->Z,p1->Z,p2->Z};
+float gC=(ms[0]+ms[1]+ms[2])/3.0f;for(int i=0;i<3;i
+++)if(ms[i]<gC*0.75f||ms[i]>gC*1.25f)return 0;int d[
+3]={bj(p0->x,p0->y,p1->x,p1->y),bj(p0->x,p0->y,p2->x
+,p2->y),bj(p1->x,p1->y,p2->x,p2->y)};if(d[0]>d[1]){
+int t=d[0];d[0]=d[1];d[1]=t;}if(d[1]>d[2]){int t=d[1
+];d[1]=d[2];d[2]=t;}if(d[0]>d[1]){int t=d[0];d[0]=d[
+1];d[1]=t;}if(d[1]>d[0]*2)return 0;float ga=(float)d
+[2]/(d[0]+d[1]);if(ga<0.75f||ga>1.25f)return 0;float
+G=(sqrtf((float)d[0])/gC-10.0f)/4.0f;return G<=50;}
+static int cx(bG*ag,av*I){ac ah[500];int cA=dq(ag,ah
+,500);if(cA<3)return 0;int*gb=calloc(cA,sizeof(int))
+;int cB=0;for(int i=0;i<cA-2&&cB<1024;i++){if(gb[i])
+continue;for(int j=i+1;j<cA-1;j++){if(gb[j])continue
+;for(int k=j+1;k<cA;k++){if(gb[k])continue;if(!fq(&
+ah[i],&ah[j],&ah[k])){continue;}ac hV[3]={ah[i],ah[j
+],ah[k]};char dL[4096];int au=cS(ag,hV,3,dL,sizeof(
+dL));if(au>0){char bp;int aE,aA;const char*cc;if(eu(
+dL,&bp,&aE,&aA,&cc)){U gc[4096];int eY=fS(cc,strlen(
+cc),gc,sizeof(gc));if(eY>0){dp(I,bp,aE,aA,gc,eY);cB
+++;gb[i]=gb[j]=gb[k]=1;goto hW;}}}}}hW:;}free(gb);
+return cB;}static int gQ(int fw,char**ch,const char*
+*aq,const char**aC,int*aI){for(int i=1;i<fw;i++){if(
+ch[i][0]!='-'){if(!*aI)*aI=i;continue;}if(!strcmp(ch
+[i],"-p")&&i+1<fw)*aq=ch[++i];else if(!strcmp(ch[i],
+"-o")&&i+1<fw)*aC=ch[++i];else if(!strcmp(ch[i],"-v"
+)||!strcmp(ch[i],"-vv")||!strcmp(ch[i],"--debug"))
+continue;else{fprintf(stderr,
+"Usage: %s [-p pass] [-o fl] ag.pbm\n",ch[0]);return
+ch[i][1]=='h'?0:-1;}}if(!*aI){fprintf(stderr,
+"Usage: %s [-p pass] [-o fl] ag.pbm\n",ch[0]);return
+-1;}return 1;}static int gD(int fw,char**ch,int aI,
+av*I){for(int i=aI;i<fw;i++){if(ch[i][0]=='-')
+continue;bG*ag=fz(ch[i]);if(ag){cx(ag,I);gF(ag);}}
+return I->F>0;}static U*fD(av*I,int*D){cT(I);int tn=
+0,tp=0;for(int i=0;i<I->F;i++){cY*c=&I->I[i];if(c->
+bp=='N'&&c->aA>tn)tn=c->aA;if(c->bp=='P'&&c->aA>tp)
+tp=c->aA;}if(!fj(I,tn,tp))fprintf(stderr,
+"Warning: recovery incomplete\n");return fT(I,D);}
+static U*eB(U*l,int*au,const char*aq){if(!cM(l,*au))
+return l;if(!aq){fprintf(stderr,
+"Error: GPG encrypted, need -p\n");free(l);return 0;
+}int dM;U*hO=gA(l,*au,aq,&dM);if(!hO){fprintf(stderr
+,"GPG decryption failed\n");free(l);return 0;}free(l
+);*au=dM;return hO;}static U*ds(U*l,int*au){if(*au<2
+||l[0]!=0x1f||l[1]!=0x8b)return l;int dM;U*hO=bR(l,*
+au,&dM);if(!hO)return l;free(l);*au=dM;return hO;}
+static int gi(U*l,int au,const char*aC){U cd[32];gz(
+l,au,cd);fprintf(stderr,"SHA256: ");for(int i=0;i<32
+;i++)fprintf(stderr,"%02x",cd[i]);fprintf(stderr,
+"\n");FILE*fl=aC?fopen(aC,"wb"):stdout;if(!fl)return
+0;fwrite(l,1,au,fl);if(aC)fclose(fl);return 1;}int
+main(int fw,char**ch){const char*aq=0,*aC=0;int aI=0
+;int r=gQ(fw,ch,&aq,&aC,&aI);if(r<=0)return r<0?1:0;
+av I;fh(&I);if(!gD(fw,ch,aI,&I)){fprintf(stderr,
+"No QR codes fN\n");return 1;}int au;U*l=fD(&I,&au);
+fi(&I);if(!l){fprintf(stderr,
+"Failed to assemble l\n");return 1;}l=eB(l,&au,aq);
+if(!l)return 1;l=ds(l,&au);(void)df;int ok=gi(l,au,
+aC);free(l);return ok?0:1;}