| | while (!feof($f)) { |
| | $chunk = fread($f, 128); // Read small chunks for the header |
| | - $data .= inflate_add($ctx, $chunk, ZLIB_NO_FLUSH); |
| | + $inflated = @inflate_add($ctx, $chunk, ZLIB_NO_FLUSH); |
| | + if ($inflated === false) break; |
| | + $data .= $inflated; |
| | if (strpos($data, "\0") !== false) break; // Stop once we have the header |
| | } |
 |
| | |
| | fseek($pf, $info['offset']); |
| | - |
| | + |
| | // Read Pack Object Header |
| | $byte = ord(fread($pf, 1)); |
 |
| | $buffer = ''; |
| | $found = false; |
| | - |
| | + |
| | // We only need the first two VLQ integers from the stream |
| | while (!$found && !feof($pf)) { |
| | $chunk = fread($pf, 512); |
| | - $buffer .= inflate_add($ctx, $chunk, ZLIB_NO_FLUSH); |
| | - |
| | + $inflated = @inflate_add($ctx, $chunk, ZLIB_NO_FLUSH); |
| | + if ($inflated === false) { fclose($pf); return 0; } |
| | + |
| | + $buffer .= $inflated; |
| | + |
| | // Check if we have enough bytes to decode two VLQs |
| | - // (Just a heuristic check, the decoding loop below handles the actual logic) |
| | - if (strlen($buffer) > 20) $found = true; |
| | + if (strlen($buffer) > 32) $found = true; |
| | } |
| | - |
| | + |
| | // Decode Delta Header: [Source Size VLQ] [Target Size VLQ] |
| | $pos = 0; |
| | - |
| | + |
| | // Skip Source Size |
| | + if (!isset($buffer[$pos])) { fclose($pf); return 0; } |
| | $byte = ord($buffer[$pos++]); |
| | - while ($byte & 128) { $byte = ord($buffer[$pos++]); } |
| | - |
| | + while ($byte & 128) { |
| | + if (!isset($buffer[$pos])) break; |
| | + $byte = ord($buffer[$pos++]); |
| | + } |
| | + |
| | // Read Target Size (The full file size) |
| | + if (!isset($buffer[$pos])) { fclose($pf); return 0; } |
| | $byte = ord($buffer[$pos++]); |
| | $size = $byte & 127; |
| | $shift = 7; |
| | while ($byte & 128) { |
| | + if (!isset($buffer[$pos])) break; |
| | $byte = ord($buffer[$pos++]); |
| | $size |= (($byte & 127) << $shift); |
 |
| | $packs = glob("{$this->objPath}/pack/*.idx"); |
| | if (!$packs) return null; |
| | + |
| | + $binSha = hex2bin($sha); |
| | + $firstByte = ord($binSha[0]); |
| | + |
| | foreach ($packs as $idxFile) { |
| | $f = @fopen($idxFile, 'rb'); |
| | if (!$f) continue; |
| | - fseek($f, 8 + (hexdec(substr($sha, 0, 2)) * 4)); |
| | - $count = unpack('N', fread($f, 4))[1]; |
| | - fseek($f, 8 + (255 * 4)); |
| | - $total = unpack('N', fread($f, 4))[1]; |
| | - fseek($f, 8 + (256 * 4)); |
| | - $idx = -1; |
| | - for ($i = 0; $i < $total; $i++) { |
| | - if (bin2hex(fread($f, 20)) === $sha) { $idx = $i; break; } |
| | + |
| | + // Verify V2 Header |
| | + $sig = fread($f, 4); |
| | + $ver = unpack('N', fread($f, 4))[1]; |
| | + if ($sig !== "\377tOc" || $ver !== 2) { |
| | + fclose($f); continue; // Only supports V2 for now |
| | } |
| | - if ($idx === -1) { fclose($f); continue; } |
| | - fseek($f, 8 + (256 * 4) + ($total * 20) + ($total * 4) + ($idx * 4)); |
| | - $offset = unpack('N', fread($f, 4))[1]; |
| | + |
| | + // 1. Read Fanout Table to find range |
| | + // Range start: Value at index [firstByte - 1] (or 0 if firstByte is 0) |
| | + // Range end: Value at index [firstByte] |
| | + $fanoutOffset = 8; // After header |
| | + if ($firstByte > 0) { |
| | + fseek($f, $fanoutOffset + (($firstByte - 1) * 4)); |
| | + $start = unpack('N', fread($f, 4))[1]; |
| | + } else { |
| | + $start = 0; |
| | + } |
| | + |
| | + fseek($f, $fanoutOffset + ($firstByte * 4)); |
| | + $end = unpack('N', fread($f, 4))[1]; |
| | + |
| | + // Get Total Objects (last entry in fanout) |
| | + if ($end <= $start) { fclose($f); continue; } |
| | + |
| | + fseek($f, $fanoutOffset + (255 * 4)); |
| | + $totalObjects = unpack('N', fread($f, 4))[1]; |
| | + |
| | + // 2. Binary Search or Linear Scan in the specific range |
| | + // The SHA table starts after the fanout (1024 bytes) |
| | + $shaTableOffset = 8 + 1024; |
| | + |
| | + // We scan only [$start, $end) |
| | + fseek($f, $shaTableOffset + ($start * 20)); |
| | + |
| | + $foundIdx = -1; |
| | + for ($i = $start; $i < $end; $i++) { |
| | + if (fread($f, 20) === $binSha) { |
| | + $foundIdx = $i; |
| | + break; |
| | + } |
| | + } |
| | + |
| | + if ($foundIdx === -1) { fclose($f); continue; } |
| | + |
| | + // 3. Read Offset |
| | + // Offsets table starts after: Header + Fanout + SHAs + CRCs |
| | + // CRCs are $totalObjects * 4 bytes |
| | + $crcOffset = $shaTableOffset + ($totalObjects * 20); |
| | + $offsetTableOffset = $crcOffset + ($totalObjects * 4); |
| | + |
| | + fseek($f, $offsetTableOffset + ($foundIdx * 4)); |
| | + $offset32 = unpack('N', fread($f, 4))[1]; |
| | + |
| | + // 4. Handle Large Offsets (MSB set) |
| | + if ($offset32 & 0x80000000) { |
| | + // It's an index into the 64-bit offset table |
| | + $largeOffsetIdx = $offset32 & 0x7FFFFFFF; |
| | + $largeOffsetTablePos = $offsetTableOffset + ($totalObjects * 4); |
| | + |
| | + fseek($f, $largeOffsetTablePos + ($largeOffsetIdx * 8)); |
| | + $data = unpack('J', fread($f, 8)); // 'J' is 64-bit big endian integer |
| | + $offset = $data[1]; |
| | + } else { |
| | + $offset = $offset32; |
| | + } |
| | + |
| | fclose($f); |
| | return ['file' => str_replace('.idx', '.pack', $idxFile), 'offset' => $offset]; |