Hvordan jeg gjenopprettet data i et ukjent format fra magnetbånd

forhistorie

Som en elsker av retro-maskinvare, kjøpte jeg en gang en ZX Spectrum+ fra en selger i Storbritannia. Inkludert med selve datamaskinen fikk jeg flere lydkassetter med spill (i originalemballasje med instruksjoner), samt programmer innspilt på kassetter uten spesielle merker. Overraskende nok var data fra 40 år gamle kassetter godt lesbare, og jeg var i stand til å laste ned nesten alle spillene og programmene fra dem.

Hvordan jeg gjenopprettet data i et ukjent format fra magnetbånd

På noen kassetter fant jeg imidlertid opptak som tydeligvis ikke ble gjort av ZX Spectrum-datamaskinen. De hørtes helt annerledes ut og i motsetning til opptakene fra den nevnte datamaskinen startet de ikke med en kort BASIC bootloader, som vanligvis finnes i opptakene av alle programmer og spill.

I noen tid forfulgte dette meg - jeg ønsket virkelig å finne ut hva som var skjult i dem. Hvis du kunne lese lydsignalet som en sekvens av bytes, kan du se etter tegn eller annet som indikerer opprinnelsen til signalet. En slags retro-arkeologi.

Nå som jeg har gått hele veien og sett på etikettene til selve kassettene, smiler jeg fordi

svaret var rett foran øynene mine hele tiden
På etiketten til venstre kassett står navnet på TRS-80-datamaskinen, og like under navnet på produsenten: "Produsert av Radio Shack i USA"

(Hvis du vil beholde intrigen til slutten, ikke gå under spoileren)

Sammenligning av lydsignaler

Først av alt, la oss digitalisere lydopptakene. Du kan høre hvordan det høres ut:


Og som vanlig lyder opptaket fra ZX Spectrum-datamaskinen:


I begge tilfeller er det i begynnelsen av opptaket en såkalt pilot tone - en lyd med samme frekvens (i den første innspillingen er den veldig kort <1 sekund, men kan skilles). Pilottonen signaliserer datamaskinen til å forberede seg på å motta data. Som regel gjenkjenner hver datamaskin bare sin "egen" pilottone etter formen på signalet og dens frekvens.

Det er nødvendig å si noe om selve signalformen. For eksempel, på ZX Spectrum er formen rektangulær:

Hvordan jeg gjenopprettet data i et ukjent format fra magnetbånd

Når en pilottone oppdages, viser ZX Spectrum vekslende røde og blå streker på kanten av skjermen for å indikere at signalet har blitt gjenkjent. Pilottonen slutter synkron puls, som signaliserer datamaskinen til å begynne å motta data. Den er preget av en kortere varighet (sammenlignet med pilottonen og påfølgende data) (se figur)

Etter at synkroniseringspulsen er mottatt, registrerer datamaskinen hver stigning/fall av signalet, og måler dets varighet. Hvis varigheten er mindre enn en viss grense, skrives bit 1 til minnet, ellers 0. Bitene samles til byte og prosessen gjentas inntil N byte mottas. Tallet N er vanligvis hentet fra overskriften på den nedlastede filen. Lastesekvensen er som følger:

  1. pilot tone
  2. header (fast lengde), inneholder størrelsen på de nedlastede dataene (N), filnavn og type
  3. pilot tone
  4. selve dataene

For å være sikker på at dataene lastes inn riktig, leser ZX Spectrum den såkalte paritetsbyte (paritetsbyte), som beregnes når du lagrer en fil ved å XORinge alle byte av de skrevne dataene. Når du leser en fil, beregner datamaskinen paritetsbyten fra de mottatte dataene, og hvis resultatet er forskjellig fra den lagrede, viser den feilmeldingen "R Tape loading error". Strengt tatt kan datamaskinen gi denne meldingen tidligere hvis den under lesing ikke kan gjenkjenne en puls (tappet eller dens varighet ikke samsvarer med visse grenser)

Så, la oss nå se hvordan et ukjent signal ser ut:

Hvordan jeg gjenopprettet data i et ukjent format fra magnetbånd

Dette er pilottonen. Formen på signalet er vesentlig forskjellig, men det er tydelig at signalet består av repeterende korte pulser med en viss frekvens. Ved en samplingsfrekvens på 44100 Hz er avstanden mellom "toppene" omtrent 48 prøver (som tilsvarer en frekvens på ~918 Hz). La oss huske denne figuren.

La oss nå se på datafragmentet:

Hvordan jeg gjenopprettet data i et ukjent format fra magnetbånd

Hvis vi måler avstanden mellom individuelle pulser, viser det seg at avstanden mellom "lange" pulser fortsatt er ~48 prøver, og mellom korte - ~24. Ser jeg litt fremover, vil jeg si at det til slutt viste seg at "referanse"-pulser med en frekvens på 918 Hz følger kontinuerlig, fra begynnelsen til slutten av filen. Det kan antas at ved overføring av data, hvis det oppstår en ekstra puls mellom referansepulsene, anser vi den som bit 1, ellers 0.

Hva med synkroniseringspulsen? La oss se på begynnelsen av dataene:

Hvordan jeg gjenopprettet data i et ukjent format fra magnetbånd

Pilottonen slutter og dataene begynner umiddelbart. Litt senere, etter å ha analysert flere forskjellige lydopptak, kunne vi oppdage at den første byten med data alltid er den samme (10100101b, A5h). Datamaskinen kan begynne å lese data etter at den mottar dem.

Du kan også være oppmerksom på forskyvningen av den første referansepulsen umiddelbart etter den siste 1. i synkroniseringsbyten. Det ble oppdaget mye senere i prosessen med å utvikle et datagjenkjenningsprogram, da dataene i begynnelsen av filen ikke kunne leses stabilt.

La oss nå prøve å beskrive en algoritme som vil behandle en lydfil og laste inn data.

Laster inn data

La oss først se på noen få antagelser for å holde algoritmen enkel:

  1. Vi vil kun vurdere filer i WAV-format;
  2. Lydfilen må begynne med en pilottone og må ikke inneholde stillhet i begynnelsen
  3. Kildefilen må ha en samplingsfrekvens på 44100 Hz. I dette tilfellet er avstanden mellom referansepulsene til 48 prøver allerede bestemt, og vi trenger ikke å beregne den programmatisk;
  4. Eksempelformatet kan være hvilket som helst (8/16 biter/flytende komma) - siden vi ved lesing kan konvertere det til ønsket;
  5. Vi antar at kildefilen er normalisert etter amplitude, noe som skal stabilisere resultatet;

Lesealgoritmen vil være som følger:

  1. Vi leser filen inn i minnet, og konverterer samtidig prøveformatet til 8 biter;
  2. Bestem posisjonen til den første pulsen i lyddataene. For å gjøre dette må du beregne antallet av prøven med maksimal amplitude. For enkelhets skyld vil vi beregne det én gang manuelt. La oss lagre den i variabelen prev_pos;
  3. Legg til 48 til posisjonen til den siste pulsen (pos := prev_pos + 48)
  4. Siden økning av posisjonen med 48 ikke garanterer at vi kommer til posisjonen til neste referansepuls (bånddefekter, ustabil drift av bånddrivmekanismen, etc.), må vi justere posisjonen til pulsposen. For å gjøre dette, ta et lite stykke data (pos-8;pos+8) og finn den maksimale amplitudeverdien på den. Posisjonen som tilsvarer maksimum vil bli lagret i pos. Her er 8 = 48/6 en eksperimentelt oppnådd konstant, som garanterer at vi vil bestemme riktig maksimum og ikke vil påvirke andre impulser som kan være i nærheten. I svært dårlige tilfeller, når avstanden mellom pulser er mye mindre enn eller større enn 48, kan du implementere et tvunget søk etter en puls, men innenfor rammen av artikkelen vil jeg ikke beskrive dette i algoritmen;
  5. Ved forrige trinn ville det også være nødvendig å kontrollere at referansepulsen i det hele tatt ble funnet. Det vil si at hvis du bare ser etter det maksimale, garanterer ikke dette at impulsen er tilstede i dette segmentet. I min siste implementering av leseprogrammet sjekker jeg forskjellen mellom maksimale og laveste amplitudeverdier på et segment, og hvis det overskrider en viss grense, teller jeg tilstedeværelsen av en impuls. Spørsmålet er også hva du skal gjøre hvis referansepulsen ikke blir funnet. Det er 2 alternativer: enten er dataene avsluttet og stillhet følger, eller dette bør betraktes som en lesefeil. Vi vil imidlertid utelate dette for å forenkle algoritmen;
  6. På neste trinn må vi bestemme tilstedeværelsen av en datapuls (bit 0 eller 1), for dette tar vi midten av segmentet (prev_pos;pos) middle_pos lik middle_pos := (prev_pos+pos)/2 og i et eller annet nabolag til middle_pos på segmentet (middle_pos-8;middle_pos +8) la oss beregne maksimum og minimum amplitude. Hvis forskjellen mellom dem er mer enn 10, skriver vi bit 1 inn i resultatet, ellers 0. 10 er en konstant oppnådd eksperimentelt;
  7. Lagre gjeldende posisjon i prev_pos (prev_pos := pos)
  8. Gjenta fra trinn 3 til vi leser hele filen;
  9. Den resulterende bitmatrisen må lagres som et sett med byte. Siden vi ikke tok hensyn til synkroniseringsbyten ved lesing, kan det hende at antall biter ikke er et multiplum av 8, og den nødvendige bitforskyvningen er også ukjent. I den første implementeringen av algoritmen visste jeg ikke om eksistensen av synkroniseringsbyten og lagret derfor ganske enkelt 8 filer med forskjellig antall offsetbiter. En av dem inneholdt korrekte data. I den endelige algoritmen fjerner jeg ganske enkelt alle biter opp til A5h, noe som lar meg umiddelbart få riktig utdatafil

Algoritme i Ruby, for de som er interessert
Jeg valgte Ruby som språk for å skrive programmet, fordi... Jeg programmerer på det mesteparten av tiden. Alternativet er ikke høy ytelse, men oppgaven med å gjøre lesehastigheten så rask som mulig er ikke verdt det.

# Используем 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*")

Resultat

Etter å ha prøvd flere varianter av algoritmen og konstantene, var jeg heldig som fikk noe ekstremt interessant:

Hvordan jeg gjenopprettet data i et ukjent format fra magnetbånd

Så, etter tegnstrengene å dømme, har vi et program for å plotte grafer. Det er imidlertid ingen nøkkelord i programteksten. Alle nøkkelord er kodet som byte (hver verdi > 80 timer). Nå må vi finne ut hvilken datamaskin fra 80-tallet som kunne lagre programmer i dette formatet.

Faktisk er det veldig likt et BASIC-program. ZX Spectrum-datamaskinen lagrer programmer i omtrent samme format i minnet og lagrer programmer på bånd. For sikkerhets skyld sjekket jeg søkeordene mot bord. Resultatet var imidlertid åpenbart negativt.

Jeg sjekket også BASIC-nøkkelordene til de populære Atari, Commodore 64 og flere andre datamaskiner på den tiden, som jeg klarte å finne dokumentasjon for, men uten hell - kunnskapen min om typen retro-datamaskiner viste seg å ikke være så bred.

Da bestemte jeg meg for å gå listen, og så falt blikket mitt på navnet til produsenten Radio Shack og datamaskinen TRS-80. Dette er navnene som var skrevet på etikettene til kassettene som lå på bordet mitt! Jeg kjente ikke til disse navnene fra før og var ikke kjent med TRS-80-datamaskinen, så det virket for meg som om Radio Shack var en lydkassettprodusent som BASF, Sony eller TDK, og TRS-80 var avspillingstiden. Hvorfor ikke?

Datamaskin Tandy/Radio Shack TRS-80

Det er svært sannsynlig at det aktuelle lydopptaket, som jeg ga som eksempel i begynnelsen av artikkelen, ble gjort på en datamaskin som dette:

Hvordan jeg gjenopprettet data i et ukjent format fra magnetbånd

Det viste seg at denne datamaskinen og dens varianter (Model I/Model III/Model IV, etc.) var veldig populære på en gang (selvfølgelig ikke i Russland). Det er bemerkelsesverdig at prosessoren de brukte også var Z80. For denne datamaskinen kan du finne på Internett mye informasjon. På 80-tallet ble datainformasjon distribuert i magasiner. For øyeblikket er det flere emulatorer datamaskiner for ulike plattformer.

Jeg lastet ned emulatoren trs80gp og for første gang kunne jeg se hvordan denne datamaskinen fungerte. Datamaskinen støttet selvfølgelig ikke fargeutdata, skjermoppløsningen var bare 128x48 piksler, men det var mange utvidelser og modifikasjoner som kunne øke skjermoppløsningen. Det var også mange alternativer for operativsystemer for denne datamaskinen og alternativer for å implementere BASIC-språket (som, i motsetning til ZX Spectrum, i noen modeller ikke engang ble "flashet" til ROM og et hvilket som helst alternativ kunne lastes fra en diskett, akkurat som selve operativsystemet)

Jeg fant også nytte å konvertere lydopptak til CAS-format, som støttes av emulatorer, men av en eller annen grunn var det ikke mulig å lese opptak fra kassettene mine ved å bruke dem.

Etter å ha funnet ut CAS-filformatet (som viste seg å være bare en bit-for-bit kopi av dataene fra båndet som jeg allerede hadde for hånden, bortsett fra overskriften med tilstedeværelsen av en synkroniseringsbyte), laget jeg en noen endringer i programmet mitt og var i stand til å sende ut en fungerende CAS-fil som fungerte i emulatoren (TRS-80 Model III):

Hvordan jeg gjenopprettet data i et ukjent format fra magnetbånd

Jeg designet siste versjon av konverteringsverktøyet med automatisk bestemmelse av første puls og avstanden mellom referansepulser som en GEM-pakke, kildekoden er tilgjengelig på Github.

Konklusjon

Stien vi har gått viste seg å være en fascinerende reise inn i fortiden, og jeg er glad for at jeg til slutt fant svaret. Jeg har blant annet:

  • Jeg fant ut formatet for å lagre data i ZX Spectrum og studerte de innebygde ROM-rutinene for å lagre/lese data fra lydkassetter
  • Jeg ble kjent med TRS-80-datamaskinen og dens varianter, studerte operativsystemet, så på eksempelprogrammer og hadde til og med muligheten til å gjøre feilsøking i maskinkoder (tross alt er alle Z80-mnemonikkene kjent for meg)
  • Skrev et fullverdig verktøy for å konvertere lydopptak til CAS-format, som kan lese data som ikke gjenkjennes av det "offisielle" verktøyet

Kilde: www.habr.com

Legg til en kommentar