Files
sasheg dev b6e847e981 Инструменты для сверки с оригинальным байткодом
struct_diff сравнивает классы по составу и показывает, каких не хватает.
Но он отвечает на вопрос «того же ли набора методов», а не «тот ли код».
Оба вопроса приходится задавать: прыжки и звуки шагов были сломаны именно
кодом, который компилировался без ошибок и состоял из правильных методов.

javap_diff сравнивает инструкции построчно, вызывая javap на обоих деревьях
классификации. Наивное сравнение текста здесь бесполезно: перекомпиляция
перенумеровывает constant pool, поэтому почти каждая строка отличается
номером #123, хотя код совпадает. Инструмент убирает номера пула, сдвиги
смещений от ldc/ldc_w и форму инструкций, после чего остаётся реальная
разница.

На звуке инструмент показал, что все аудио-классы совпадают с оригиналом.
Остатки объясняются версией компилятора, а не правками: javac 8 собирает
конкатенацию строк через StringBuilder, javac 17 через invokedynamic, а
$values() у enum появился в более новых версиях.

ogg_duration читает частоту дискретизации из заголовка Vorbis и число
отсчётов из granule position последней страницы OggS. Отсюда видно, что файл
сам по себе короче или что его просто обрывают при воспроизведении, —
вопрос, который иначе приходится решать на слух.

Проверено, что все 1085 ogg совпадают с индексом по SHA-1.
2026-10-19 00:25:35 +03:00

69 lines
1.9 KiB
Python

#!/usr/bin/env python3
"""Report the real playback duration of Ogg Vorbis files, without decoding audio.
Reads the sample rate out of the Vorbis identification header and the total
sample count from the granule position of the final OggS page, then divides.
This is how you tell a sound file that is genuinely shorter from a sound that
merely stops early: the file either contains fewer samples or it does not.
Usage:
ogg_duration.py <file.ogg> [file.ogg ...]
"""
import struct
import sys
def info(path):
with open(path, "rb") as fh:
data = fh.read()
mark = data.find(b"\x01vorbis")
if mark < 0:
return None
# packet type(1) + 'vorbis'(6) + version(4) + channels(1) -> rate(4)
if len(data) < mark + 16:
return None
rate = struct.unpack_from("<I", data, mark + 12)[0]
channels = data[mark + 11]
granule = 0
pos = 0
while True:
pos = data.find(b"OggS", pos)
if pos < 0:
break
if pos + 27 <= len(data):
granule = struct.unpack_from("<q", data, pos + 6)[0]
seg_count = data[pos + 26]
table_end = pos + 27 + seg_count
body = sum(data[pos + 27:table_end])
pos = table_end + body
else:
pos += 4
seconds = granule / float(rate) if rate else 0.0
return {
"bytes": len(data),
"rate": rate,
"channels": channels,
"samples": granule,
"seconds": seconds,
}
def main(argv):
for path in argv:
got = info(path)
if got is None:
print("{:<52} not a Vorbis stream".format(path[-52:]))
continue
print("{:<52} {:>7.3f}s {:>6} Hz {:>2}ch {:>8} samples {:>6} B".format(
path[-52:], got["seconds"], got["rate"], got["channels"],
got["samples"], got["bytes"]))
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))