getSize(); echo "Generated QR Code for: '{$data}'\n"; echo "Matrix Size: {$size}x{$size}\n\n"; // Extract format information to determine mask pattern echo "=== Format Information Analysis ===\n"; // Read format bits from horizontal position $formatBitsHorizontal = ''; for ($col = 0; $col < 9; $col++) { if ($col === 6) continue; // Skip timing column $formatBitsHorizontal .= $matrix->getModuleAt(8, $col)->isDark() ? '1' : '0'; } echo "Horizontal format bits: {$formatBitsHorizontal}\n"; // Decode format information // Format info is 15 bits with error correction // Bits 0-4 contain EC level (2 bits) and mask pattern (3 bits) $formatInt = bindec($formatBitsHorizontal); // Try to find matching format pattern echo "\nSearching for matching format pattern...\n"; $formatTable = [ 'M' => [ 0 => 0b101010000010010, 1 => 0b101000100100101, 2 => 0b101111001111100, 3 => 0b101101101001011, 4 => 0b100010111111001, 5 => 0b100000011001110, 6 => 0b100111110010111, 7 => 0b100101010100000, ], ]; $detectedMask = null; foreach ($formatTable['M'] as $maskNum => $formatValue) { $formatBits = str_pad(decbin($formatValue), 15, '0', STR_PAD_LEFT); $firstByte = substr($formatBits, 0, 8); if ($formatBitsHorizontal === $firstByte) { echo "Found match! Mask Pattern: {$maskNum}\n"; $detectedMask = $maskNum; break; } } if ($detectedMask === null) { echo "Could not determine mask pattern from format info.\n"; echo "Trying all masks...\n\n"; // Try all 8 mask patterns for ($maskNum = 0; $maskNum < 8; $maskNum++) { echo "=== Testing Mask Pattern {$maskNum} ===\n"; $maskPattern = MaskPattern::from($maskNum); $unmaskedBits = ''; $bitIndex = 0; // Unmask data region for ($col = $size - 1; $col >= 1; $col -= 2) { if ($col === 6) { $col--; } $upward = ((int) (($size - 1 - $col) / 2) % 2) === 0; for ($i = 0; $i < $size; $i++) { $row = $upward ? ($size - 1 - $i) : $i; for ($c = 0; $c < 2; $c++) { $currentCol = $col - $c; // Skip function patterns if ($row <= 8 && $currentCol <= 8) continue; if ($row <= 8 && $currentCol >= $size - 8) continue; if ($row >= $size - 8 && $currentCol <= 8) continue; if ($row === 6 || $currentCol === 6) continue; if ($row === 8 || $currentCol === 8) continue; $maskedBit = $matrix->getModuleAt($row, $currentCol)->isDark() ? '1' : '0'; // Apply mask to reverse it $shouldInvert = $maskPattern->shouldInvert($row, $currentCol); $originalBit = $shouldInvert ? ($maskedBit === '1' ? '0' : '1') : $maskedBit; $unmaskedBits .= $originalBit; $bitIndex++; if ($bitIndex >= 64) { break 4; // Break all loops } } } } // Decode first bytes echo "First 64 unmasked bits: " . substr($unmaskedBits, 0, 64) . "\n"; echo "Bytes:\n"; for ($i = 0; $i < min(8, strlen($unmaskedBits) / 8); $i++) { $byte = substr($unmaskedBits, $i * 8, 8); $decimal = bindec($byte); $char = $decimal >= 32 && $decimal < 127 ? chr($decimal) : '.'; echo " Byte " . ($i + 1) . ": {$byte} = {$decimal} ('{$char}')\n"; } // Check if it matches expected pattern $expected = '0100' . '00000101' . '01001000' . '01000101' . '01001100' . '01001100' . '01001111'; if (str_starts_with($unmaskedBits, $expected)) { echo "✅ MATCH! This is the correct mask pattern!\n"; break; } else { echo "❌ No match.\n"; } echo "\n"; } } echo "\n=== Expected Unmasked Data ===\n"; echo "Mode (Byte): 0100\n"; echo "Count (5): 00000101\n"; echo "H (72): 01001000\n"; echo "E (69): 01000101\n"; echo "L (76): 01001100\n"; echo "L (76): 01001100\n"; echo "O (79): 01001111\n"; echo "Combined: 010000000101010010000100010101001100010011000100111100...\n";