Kako sam povratio podatke u nepoznatom formatu sa magnetne trake

prapovijest

Pošto sam zaljubljenik u retro hardver, jednom sam kupio ZX Spectrum+ od prodavca u Velikoj Britaniji. Uz sam kompjuter sam dobio nekoliko audio kaseta sa igricama (u originalnom pakovanju sa uputstvom), kao i programe snimljene na kasetama bez posebnih oznaka. Iznenađujuće, podaci sa kaseta starih 40 godina bili su čitljivi i mogao sam da skinem skoro sve igre i programe sa njih.

Kako sam povratio podatke u nepoznatom formatu sa magnetne trake

Međutim, na nekim kasetama sam našao snimke koje očito nije napravio ZX Spectrum kompjuter. Zvučale su potpuno drugačije i, za razliku od snimaka sa pomenutog kompjutera, nisu počinjale kratkim BASIC bootloaderom koji je inače prisutan na snimcima svih programa i igrica.

To me je neko vrijeme proganjalo – zaista sam želio saznati šta se krije u njima. Ako biste mogli pročitati audio signal kao niz bajtova, mogli biste potražiti znakove ili bilo šta što ukazuje na porijeklo signala. Neka vrsta retroarheologije.

Sad kad sam otišao do kraja i pogledao etikete na samim kasetama, smiješim se jer

odgovor mi je cijelo vrijeme bio pred očima
Na etiketi lijeve kasete je naziv računara TRS-80, a odmah ispod imena proizvođača: “Proizvođač Radio Shack u SAD”

(Ako želite da zadržite intrigu do kraja, ne ulazite ispod spojlera)

Poređenje audio signala

Prije svega, digitalizirajmo audio snimke. Možete poslušati kako to zvuči:


I kao i obično zvuči snimak sa računara ZX Spectrum:


U oba slučaja, na početku snimka nalazi se tzv pilot ton - zvuk iste frekvencije (na prvom snimku je vrlo kratak <1 sekunde, ali se razlikuje). Pilot ton signalizira kompjuteru da se pripremi za prijem podataka. Po pravilu, svaki kompjuter prepoznaje samo svoj „vlastiti“ pilot ton po obliku signala i njegovoj frekvenciji.

Potrebno je reći nešto o samom obliku signala. Na primjer, na ZX Spectrumu njegov oblik je pravokutni:

Kako sam povratio podatke u nepoznatom formatu sa magnetne trake

Kada se detektuje pilot ton, ZX Spectrum prikazuje naizmenične crvene i plave trake na ivici ekrana kako bi označio da je signal prepoznat. Pilot ton se završava sinhro puls, koji signalizira računaru da počne da prima podatke. Karakterizira ga kraće trajanje (u poređenju sa pilot tonom i naknadnim podacima) (vidi sliku)

Nakon što je primljen sinhronizacioni impuls, računar beleži svaki porast/pad signala, mereći njegovo trajanje. Ako je trajanje manje od određene granice, bit 1 se upisuje u memoriju, u suprotnom 0. Bitovi se skupljaju u bajtove i proces se ponavlja dok se ne primi N bajtova. Broj N se obično uzima iz zaglavlja preuzete datoteke. Redoslijed učitavanja je sljedeći:

  1. pilot ton
  2. zaglavlje (fiksna dužina), sadrži veličinu preuzetih podataka (N), naziv datoteke i tip
  3. pilot ton
  4. sami podaci

Kako bi bili sigurni da su podaci ispravno učitani, ZX Spectrum očitava tzv bajt parnosti (paritetni bajt), koji se izračunava prilikom snimanja datoteke XOR-om svih bajtova upisanih podataka. Prilikom čitanja datoteke računar izračunava paritetni bajt iz primljenih podataka i, ako se rezultat razlikuje od sačuvanog, prikazuje poruku o grešci “R Tape loading error”. Strogo govoreći, računar može izdati ovu poruku ranije ako pri čitanju ne može prepoznati puls (propušten ili njegovo trajanje ne odgovara određenim granicama)

Dakle, da vidimo kako izgleda nepoznati signal:

Kako sam povratio podatke u nepoznatom formatu sa magnetne trake

Ovo je pilot ton. Oblik signala je značajno drugačiji, ali je jasno da se signal sastoji od ponavljanja kratkih impulsa određene frekvencije. Na frekvenciji uzorkovanja od 44100 Hz, razmak između "pikova" je približno 48 uzoraka (što odgovara frekvenciji od ~918 Hz).

Pogledajmo sada fragment podataka:

Kako sam povratio podatke u nepoznatom formatu sa magnetne trake

Ako izmjerimo udaljenost između pojedinačnih impulsa, ispada da je udaljenost između “dugih” impulsa i dalje ~48 uzoraka, a između kratkih ~24. Gledajući malo unaprijed, reći ću da se na kraju ispostavilo da “referentni” impulsi frekvencije 918 Hz slijede kontinuirano, od početka do kraja datoteke. Može se pretpostaviti da prilikom prijenosa podataka, ako se naiđe na dodatni impuls između referentnih impulsa, smatramo ga bitom 1, inače 0.

Šta je sa sinhronizacionim pulsom? Pogledajmo početak podataka:

Kako sam povratio podatke u nepoznatom formatu sa magnetne trake

Pilot ton se završava i podaci počinju odmah. Nešto kasnije, nakon analize nekoliko različitih audio zapisa, uspjeli smo otkriti da je prvi bajt podataka uvijek isti (10100101b, A5h). Računar može početi čitati podatke nakon što ih primi.

Također možete obratiti pažnju na pomak prvog referentnog impulsa odmah nakon posljednjeg 1. u bajtu za sinhronizaciju. Otkriveno je mnogo kasnije u procesu razvoja programa za prepoznavanje podataka, kada se podaci na početku datoteke nisu mogli stabilno čitati.

Pokušajmo sada opisati algoritam koji će obraditi audio datoteku i učitati podatke.

Učitavanje podataka

Prvo, pogledajmo nekoliko pretpostavki kako bi algoritam bio jednostavan:

  1. Mi ćemo uzeti u obzir samo datoteke u WAV formatu;
  2. Audio datoteka mora početi pilot tonom i ne smije sadržavati tišinu na početku
  3. Izvorni fajl mora imati brzinu uzorkovanja od 44100 Hz. U ovom slučaju, udaljenost između referentnih impulsa od 48 uzoraka je već određena i ne moramo je programski izračunati;
  4. Format uzorka može biti bilo koji (8/16 bita/pokretni zarez) - pošto ga prilikom čitanja možemo konvertovati u željeni;
  5. Pretpostavljamo da je izvorni fajl normalizovan amplitudom, što bi trebalo da stabilizuje rezultat;

Algoritam čitanja će biti sljedeći:

  1. Čitamo datoteku u memoriju, istovremeno pretvarajući format uzorka u 8 bita;
  2. Odredite poziciju prvog impulsa u audio podacima. Da biste to učinili, morate izračunati broj uzorka s maksimalnom amplitudom. Radi jednostavnosti, izračunat ćemo ga jednom ručno. Sačuvajmo ga u varijablu prev_pos;
  3. Dodajte 48 na poziciju posljednjeg pulsa (poz := prev_pos + 48)
  4. Budući da povećanje pozicije za 48 ne garantuje da ćemo doći do pozicije sljedećeg referentnog impulsa (defekti trake, nestabilan rad mehanizma trake itd.), potrebno je podesiti poziciju impulsa pos. Da biste to učinili, uzmite mali dio podataka (pos-8;pos+8) i na njemu pronađite maksimalnu vrijednost amplitude. Položaj koji odgovara maksimumu bit će pohranjen u poz. Ovdje je 8 = 48/6 eksperimentalno dobijena konstanta, koja garantuje da ćemo odrediti tačan maksimum i neće uticati na druge impulse koji se mogu nalaziti u blizini. U vrlo lošim slučajevima, kada je razmak između impulsa mnogo manji ili veći od 48, možete implementirati prisilno traženje impulsa, ali u okviru članka to neću opisivati ​​u algoritmu;
  5. U prethodnom koraku također bi bilo potrebno provjeriti da li je referentni puls uopće pronađen. Odnosno, ako jednostavno tražite maksimum, to ne garantuje da je impuls prisutan u ovom segmentu. U svojoj najnovijoj implementaciji programa za čitanje, provjeravam razliku između maksimalne i minimalne vrijednosti amplitude na segmentu, a ako pređe određenu granicu, računam prisustvo impulsa. Pitanje je i šta učiniti ako referentni puls nije pronađen. Postoje 2 opcije: ili su podaci završili i slijedi tišina ili ovo treba smatrati greškom čitanja. Međutim, ovo ćemo izostaviti kako bismo pojednostavili algoritam;
  6. U sljedećem koraku moramo utvrditi prisustvo impulsa podataka (bit 0 ili 1), za to uzimamo sredinu segmenta (prev_pos;pos) middle_pos jednaku middle_pos := (prev_pos+pos)/2 i u nekom susjedstvu mid_pos na segmentu (middle_pos-8;middle_pos +8) izračunajmo maksimalnu i minimalnu amplitudu. Ako je razlika između njih veća od 10, u rezultat upisujemo bit 1, u suprotnom 0. 10 je konstanta dobijena eksperimentalno;
  7. Sačuvajte trenutnu poziciju u prev_pos (prev_pos := pos)
  8. Ponovite počevši od koraka 3 dok ne pročitamo cijeli fajl;
  9. Rezultirajući niz bitova mora biti sačuvan kao skup bajtova. S obzirom da pri čitanju nismo uzeli u obzir sinhroni bajt, broj bitova možda nije višekratnik 8, a potreban pomak bita je također nepoznat. U prvoj implementaciji algoritma nisam znao za postojanje bajta za sinhronizaciju i stoga sam jednostavno sačuvao 8 fajlova sa različitim brojem pomaka bitova. Jedan od njih je sadržavao tačne podatke. U konačnom algoritmu jednostavno uklanjam sve bitove do A5h, što mi omogućava da odmah dobijem ispravan izlazni fajl

Algoritam u Rubyju, za zainteresovane
Odabrao sam Ruby kao jezik za pisanje programa, jer... Većinu vremena programiram na njemu. Opcija nije visokih performansi, ali zadatak da ubrzate čitanje što je brže moguće se ne isplati.

# Используем gem 'wavefile'
require 'wavefile'

reader = WaveFile::Reader.new('input.wav')
samples = []
format = WaveFile::Format.new(:mono, :pcm_8, 44100)

# Читаем WAV файл, конвертируем в формат Mono, 8 bit 
# Массив samples будет состоять из байт со значениями 0-255
reader.each_buffer(10000) do |buffer|
  samples += buffer.convert(format).samples
end

# Позиция первого импульса (вместо 0)
prev_pos = 0
# Расстояние между импульсами
distance = 48
# Значение расстояния для окрестности поиска локального максимума
delta = (distance / 6).floor
# Биты будем сохранять в виде строки из "0" и "1"
bits = ""

loop do
  # Рассчитываем позицию следующего импульса
  pos = prev_pos + distance
  
  # Выходим из цикла если данные закончились 
  break if pos + delta >= samples.size

  # Корректируем позицию pos обнаружением максимума на отрезке [pos - delta;pos + delta]
  (pos - delta..pos + delta).each { |p| pos = p if samples[p] > samples[pos] }

  # Находим середину отрезка [prev_pos;pos]
  middle_pos = ((prev_pos + pos) / 2).floor

  # Берем окрестность в середине 
  sample = samples[middle_pos - delta..middle_pos + delta]

  # Определяем бит как "1" если разница между максимальным и минимальным значением на отрезке превышает 10
  bit = sample.max - sample.min > 10
  bits += bit ? "1" : "0"
end

# Определяем синхро-байт и заменяем все предшествующие биты на 256 бит нулей (согласно спецификации формата) 
bits.gsub! /^[01]*?10100101/, ("0" * 256) + "10100101"

# Сохраняем выходной файл, упаковывая биты в байты
File.write "output.cas", [bits].pack("B*")

rezultat

Nakon što sam isprobao nekoliko varijanti algoritma i konstanti, imao sam sreću da sam dobio nešto izuzetno zanimljivo:

Kako sam povratio podatke u nepoznatom formatu sa magnetne trake

Dakle, sudeći po znakovnim nizovima, imamo program za crtanje grafova. Međutim, u tekstu programa nema ključnih riječi. Sve ključne riječi su kodirane kao bajtovi (svaka vrijednost > 80h). Sada treba da saznamo koji računar iz 80-ih je mogao da čuva programe u ovom formatu.

U stvari, veoma je sličan BASIC programu. Računar ZX Spectrum pohranjuje programe u približno istom formatu u memoriju i sprema programe na traku. Za svaki slučaj, provjerio sam ključne riječi sto. Međutim, rezultat je očito bio negativan.

Provjerio sam i BASIC ključne riječi popularnih Atari, Commodore 64 i nekoliko drugih kompjutera tog vremena, za koje sam uspio pronaći dokumentaciju, ali bezuspješno - pokazalo se da moje znanje o vrstama retro kompjutera nije tako široko.

Onda sam odlučio da idem lista, a onda mi je pogled pao na naziv proizvođača Radio Shack i kompjuter TRS-80. Ovo su imena koja su ispisana na etiketama kaseta koje su ležale na mom stolu! Nisam ranije znao ova imena i nisam bio upoznat sa kompjuterom TRS-80, pa mi se činilo da je Radio Shack proizvođač audio kaseta kao što su BASF, Sony ili TDK, a TRS-80 je bio vrijeme reprodukcije. Zašto ne?

Computer Tandy/Radio Shack TRS-80

Vrlo je vjerovatno da je dotični audio snimak, koji sam naveo kao primjer na početku članka, napravljen na računaru ovako:

Kako sam povratio podatke u nepoznatom formatu sa magnetne trake

Ispostavilo se da su ovaj računar i njegove varijante (Model I/Model III/Model IV, itd.) u jednom trenutku bili veoma popularni (naravno, ne u Rusiji). Važno je napomenuti da je procesor koji su koristili takođe Z80. Za ovaj računar možete pronaći na internetu puno informacija. U 80-im godinama su se distribuirale kompjuterske informacije časopisi. Trenutno ih ima nekoliko emulatori računara za različite platforme.

Skinuo sam emulator trs80gp i po prvi put sam mogao da vidim kako ovaj kompjuter radi. Naravno, računar nije podržavao izlaz u boji, rezolucija ekrana je bila samo 128x48 piksela, ali bilo je mnogo proširenja i modifikacija koje su mogle povećati rezoluciju ekrana. Postojale su i mnoge opcije za operativne sisteme za ovaj računar i opcije za implementaciju BASIC jezika (koji, za razliku od ZX Spectruma, u nekim modelima nije bio ni „flešovan“ u ROM i bilo koja opcija se mogla učitati sa diskete, baš kao sam OS)

I ja sam našao korisnost da konvertujem audio zapise u CAS format, koji podržavaju emulatori, ali iz nekog razloga nije bilo moguće čitati snimke sa mojih kaseta pomoću njih.

Nakon što sam shvatio CAS format datoteke (za koji se ispostavilo da je samo bit-po-bit kopija podataka sa trake koju sam već imao pri ruci, osim zaglavlja sa prisustvom bajta za sinhronizaciju), napravio sam nekoliko izmena u mom programu i uspeo sam da izbacim radni CAS fajl koji je radio u emulatoru (TRS-80 Model III):

Kako sam povratio podatke u nepoznatom formatu sa magnetne trake

Najnoviju verziju uslužnog programa za konverziju sa automatskim određivanjem prvog impulsa i udaljenosti između referentnih impulsa dizajnirao sam kao GEM paket, izvorni kod je dostupan na GitHub.

zaključak

Put koji smo prešli pokazao se kao fascinantno putovanje u prošlost i drago mi je da sam na kraju pronašao odgovor. Između ostalog, ja:

  • Shvatio sam format za čuvanje podataka u ZX Spectrumu i proučio ugrađene ROM rutine za čuvanje/čitanje podataka sa audio kaseta
  • Upoznao sam računar TRS-80 i njegove varijante, proučavao operativni sistem, pogledao uzorke programa i čak imao priliku da radim otklanjanje grešaka u mašinskim kodovima (na kraju krajeva, svi Z80 mnemonici su mi poznati)
  • Napisao je punopravni uslužni program za pretvaranje audio zapisa u CAS format, koji može čitati podatke koje "službeni" uslužni program ne prepoznaje

izvor: www.habr.com

Dodajte komentar