Difference between revisions of "Databar decoding"
m (→=Principles) |
m (→Code) |
||
(2 intermediate revisions by the same user not shown) | |||
Line 31: | Line 31: | ||
Note that the databars we got are 2 bytes longer than the ones described in the patent. |
Note that the databars we got are 2 bytes longer than the ones described in the patent. |
||
===Principles=== |
===Principles=== |
||
− | The high level steps of the Python script are: |
+ | The high level steps of the Python script are, for each input file: |
* Convert the image to 1-bit graylevels (not needed here but more future-proofs for other scan sources) |
* Convert the image to 1-bit graylevels (not needed here but more future-proofs for other scan sources) |
||
* Scan horizontal lines, look for at least 30 consecutive lines with at least 20% black pixels, there is likely one databar there. Remember its middle line and its width. |
* Scan horizontal lines, look for at least 30 consecutive lines with at least 20% black pixels, there is likely one databar there. Remember its middle line and its width. |
||
Line 41: | Line 41: | ||
** Check that the last stripe is at least 5x larger than the first one, that's our EOL symbol which is 7 or 8-unit large, depending on the previous symbol. |
** Check that the last stripe is at least 5x larger than the first one, that's our EOL symbol which is 7 or 8-unit large, depending on the previous symbol. |
||
** Convert pulses/stripes into nibbles |
** Convert pulses/stripes into nibbles |
||
+ | *** Use the first stripes as we know they are units, to measure their width. There are some adjusting factors, also to adapt that value over time, not sure they are really needed anymore, depends on the quality of the scan. |
||
+ | *** Convert pulse widths from pixels to units, being 1, 2 or 8 (for the EOL, actually 7 or 8 depending on previous symbol) |
||
+ | *** Expand pulse widths into series of zeros and ones |
||
+ | *** Group by 7 and decode into nibbles |
||
** Parse nibbles |
** Parse nibbles |
||
+ | *** Check first nibble is '0xd' |
||
+ | *** Convert remaining nibbles into bytes (beware they are least-significant-nibble encoded!) |
||
+ | *** Interpret first byte=line number |
||
+ | *** Interpret second byte=function byte |
||
+ | *** Interpret third byte=number of nibbles in the line, check it |
||
+ | *** Interpret fourth byte as machine type if function byte is Control Array |
||
+ | *** Interpret last byte as checksum, check it (sum of all bytes beside initial '0xd' must be zero) |
||
+ | *** Isolate and print payload if function byte is one of the Data Line types |
||
+ | |||
===Code=== |
===Code=== |
||
+ | As it's proving to be more useful than just for this write-up, the code is now available on Github: https://github.com/doegox/Oscar |
||
− | You can download the following Python script [{{#file: oscar.py}} as oscar.py]: |
||
− | <source lang=python> |
||
− | #!/usr/bin/env python |
||
− | |||
− | import sys |
||
− | from PIL import Image |
||
− | |||
− | debuglevel=0 |
||
− | |||
− | # Reference: patent US4550247 |
||
− | conv={ |
||
− | 0b0100110:0x0, 0b0101010:0x1, 0b0110010:0x2, 0b0110110:0x3, |
||
− | 0b1011010:0x4, 0b1010010:0x5, 0b1010110:0x6, 0b1001010:0x7, |
||
− | 0b0100101:0x8, 0b0101101:0x9, 0b0101001:0xa, 0b0110101:0xb, |
||
− | 0b1011001:0xc, 0b1010101:0xd, 0b1001101:0xe, 0b1001001:0xf, |
||
− | 0b1111111:0x10} |
||
− | |||
− | class Pix: |
||
− | def __init__(self, filename): |
||
− | self.im = Image.open(filename).convert('1') |
||
− | self.pixels = list(self.im.getdata()) |
||
− | self.width, self.height = self.im.size |
||
− | def getpix(self, x, y): |
||
− | return self.pixels[x+y*self.width] |
||
− | def getline(self, y): |
||
− | return self.pixels[y*self.width:(y+1)*self.width] |
||
− | def getavgline(self, y, radius): |
||
− | lines=[] |
||
− | lines.append(self.getline(lineindex)) |
||
− | for i in range(radius): |
||
− | lines.append(self.getline(lineindex-i)) |
||
− | lines.append(self.getline(lineindex+i)) |
||
− | line=[] |
||
− | for i in range(self.width): |
||
− | line.append(sum([lines[x][i] |
||
− | for x in range(radius*2+1)]) < 255*radius) |
||
− | return line |
||
− | |||
− | def findlines(pix): |
||
− | # find middle of horizontal lines |
||
− | # candlines = lines with at least 20% black |
||
− | candlines=[sum(pix.getline(i))<(255*pix.width)*0.80 |
||
− | for i in xrange(pix.height)] |
||
− | goodlines=[] |
||
− | offset=0 |
||
− | while True in candlines: |
||
− | startline=candlines.index(True) |
||
− | stopline=candlines.index(False,startline) |
||
− | candlines=candlines[stopline:] |
||
− | oldoffset, offset=offset, offset+stopline |
||
− | bandwidth=stopline - startline |
||
− | if bandwidth < 30: |
||
− | continue |
||
− | midline=startline+(bandwidth/2)+oldoffset |
||
− | if debuglevel > 0: |
||
− | print "Found %i-px wide line at y=%i" % (bandwidth, midline) |
||
− | goodlines.append((bandwidth, midline)) |
||
− | return goodlines |
||
− | |||
− | def linepx2pulses(linepx): |
||
− | pulses=[] |
||
− | while linepx: |
||
− | if linepx[0]==0: |
||
− | if 1 in linepx: |
||
− | stop=linepx.index(1) |
||
− | linepx=linepx[stop:] |
||
− | else: |
||
− | stop=len(linepx) |
||
− | linepx=[] |
||
− | if len(pulses)>0 and 1 in linepx: |
||
− | pulses.append((0,stop)) |
||
− | else: |
||
− | if 0 in linepx: |
||
− | stop=linepx.index(0) |
||
− | linepx=linepx[stop:] |
||
− | else: |
||
− | stop=len(linepx) |
||
− | linepx=[] |
||
− | pulses.append((1,stop)) |
||
− | # where is the END marker? |
||
− | # revert line if needed |
||
− | if pulses[0][1] > pulses[-1][1]: |
||
− | pulses=pulses[::-1] |
||
− | return pulses |
||
− | |||
− | def pulses2nibbles(v): |
||
− | factor_learn=0.5 |
||
− | factor_squeeze=0.95 |
||
− | # train width on START seq |
||
− | # train ones and zeroes separately due to image contrast |
||
− | smallw=[(v[1][1]+v[3][1]+v[5][1])/3.0*factor_squeeze, |
||
− | (v[0][1]+v[2][1]+v[4][1])/3.0*factor_squeeze] |
||
− | if debuglevel > 1: |
||
− | print "bitwidths:", smallw |
||
− | symbols=[] |
||
− | currentsymbol=1 |
||
− | for i in range(len(v)): |
||
− | b,w = v[i] |
||
− | if debuglevel > 2: |
||
− | print w, smallw[currentsymbol], w/smallw[currentsymbol], |
||
− | print round(w/smallw[currentsymbol],0) |
||
− | if w < smallw[currentsymbol] * 1.5: |
||
− | guess=1 |
||
− | elif w > smallw[currentsymbol] * 5: |
||
− | guess=8 # max is EOL + last bit==1 |
||
− | else: |
||
− | guess=2 |
||
− | if guess <=2: |
||
− | sw=float(w)/guess |
||
− | smallw[currentsymbol]=factor_squeeze*\ |
||
− | (smallw[currentsymbol]/factor_squeeze+sw*factor_learn)/\ |
||
− | (1+factor_learn) |
||
− | if guess <=2: |
||
− | for k in range(guess): |
||
− | symbols.append(b) |
||
− | else: #EOL |
||
− | for k in range(guess): |
||
− | symbols.append(1) |
||
− | currentsymbol^=1 |
||
− | if debuglevel > 1: |
||
− | print "symbols:", len(symbols) |
||
− | if debuglevel > 2: |
||
− | print symbols |
||
− | nibbles=[] |
||
− | nn=0 |
||
− | while symbols: |
||
− | s, symbols=symbols[:7],symbols[7:] |
||
− | bit7=0 |
||
− | for k in s: |
||
− | bit7<<=1 |
||
− | bit7+=k |
||
− | if debuglevel > 2: |
||
− | print "symbols #%02i:" % nn, s, ("7-bit:%x" % bit7), |
||
− | nn+=1 |
||
− | assert bit7 in conv |
||
− | if debuglevel > 2: |
||
− | print "conv: %x" % conv[bit7] |
||
− | if conv[bit7]<0x10: |
||
− | nibbles.append(conv[bit7]) |
||
− | else: |
||
− | assert len(symbols)<=1 |
||
− | break |
||
− | return nibbles |
||
− | |||
− | def parsenibbles(nibbles): |
||
− | if debuglevel > 1: |
||
− | print "raw: ", ''.join(["%x" % x for x in nibbles]) |
||
− | rawlen=len(nibbles) |
||
− | assert nibbles[0]==0xd |
||
− | nibbles.pop(0) |
||
− | assert len(nibbles)%2==0 |
||
− | bytes=[] |
||
− | for i in range(len(nibbles)/2): |
||
− | bytes.append((nibbles[i*2+1]<<4) + nibbles[i*2]) |
||
− | linenumber=bytes[0] |
||
− | if debuglevel > 0: |
||
− | print "linenumber: %3i" % linenumber |
||
− | functionbyte=bytes[1] |
||
− | assert functionbyte < 9 |
||
− | function=['GENERATE TONES', 'CONTROL ARRAY', 'RAM FIRMWARE', |
||
− | 'DATA LINE3', 'DATA LINE4', 'DATA LINE5', 'DATA LINE6', |
||
− | 'DATA LINE7', 'ENDFW'][functionbyte] |
||
− | if debuglevel > 0: |
||
− | print "function: %3i = %s" % (functionbyte, function) |
||
− | lastnibblenumber=bytes[2] |
||
− | assert lastnibblenumber==rawlen |
||
− | if function == 'CONTROL ARRAY': |
||
− | assert linenumber==0 |
||
− | mtbyte=bytes[3] |
||
− | assert 0 < mtbyte < 10 |
||
− | machinetype=['Atari400/800/1200', 'Commodore', 'TI99/4', |
||
− | 'Timex 1000', 'Timex 2000', 'RADIO SHACK', |
||
− | 'RS-232', 'Commodore no loader', |
||
− | 'Commodore no loader/no header'][mtbyte-1] |
||
− | if debuglevel > 0: |
||
− | print "machinetype:%3i = %s" % (mtbyte, machinetype) |
||
− | check=bytes[-1] |
||
− | if debuglevel > 0: |
||
− | print "checksum: 0x%02X = %s" % (bytes[-1], |
||
− | ['incorrect!', 'correct'][(sum(bytes) & 0xff)==0]) |
||
− | if 'DATA LINE' in function: |
||
− | data=bytes[3:-1] |
||
− | print(''.join(["%02x" % x for x in data])) |
||
− | for f in sys.argv[1:]: |
||
− | pix=Pix(f) |
||
− | goodlines=findlines(pix) |
||
− | for n in range(len(goodlines)): |
||
− | bandwidth, lineindex = goodlines[n] |
||
− | if debuglevel > 1: |
||
− | print "line #%i: y=%i" % (n, lineindex) |
||
− | # sample average of the middle 2/3 of the databar |
||
− | radius=int(bandwidth/3) |
||
− | linepx=pix.getavgline(lineindex, radius) |
||
− | pulses=linepx2pulses(linepx) |
||
− | # do we have a END marker? |
||
− | assert pulses[-1][1] > (pulses[0][1]*5) |
||
− | nibbles=pulses2nibbles(pulses) |
||
− | parsenibbles(nibbles) |
||
− | </source> |
||
===Usage=== |
===Usage=== |
||
Latest revision as of 21:37, 22 June 2016
Intro
PoC||GTFO 12 contains two pages (pp53-54) of giant barcodes as a puzzle:
The pages are available in high resolution here and here.
Identification
The hardest part was to find which obscure format was used.
Initially I tried to parse manually the barcode: we easily see start and stop sequences, pulse widths being unit or double, and and some repetitions 7-unit large. But I couldn't go further.
The revelation came when I stumbled upon this example:
It's taken from http://www.mainbyte.com/ti99/hardware/oscar/oscar.html where we learn about Oscar, a "databar" optical reader meant to input programs into computers:
The manual (pdf) refers to several supported computers of the 80' : Atari 1200XL/1400XL, Atari 400/600/800, Commodore Pet, Commodore VIC 20/64, TI99/4A, TRS 80. Regarding the computer it acts as an ordinary cassette reader.
Patent
Oscar was produced by Databar Corporation.
Looking a patents from Databar Corporation, we can find two describing the Oscar reader, perfect!
The most relevant one for us is US4550247A which is easier to read as pdf.
Note that both patents have expired.
Python decoder
With the patent in hands, it's doable to write a Python script to decode the symbols and do some consistency checks and interpretations. Note that the databars we got are 2 bytes longer than the ones described in the patent.
Principles
The high level steps of the Python script are, for each input file:
- Convert the image to 1-bit graylevels (not needed here but more future-proofs for other scan sources)
- Scan horizontal lines, look for at least 30 consecutive lines with at least 20% black pixels, there is likely one databar there. Remember its middle line and its width.
- For each databar:
- Average the core 2/3 of the horizontal lines into into single line
- Isolate pulses/stripes
- Get a list of pulses with their value (0=white, 1=black) and duration (in pixels)
- If first pulse is larger than last one, revert the list, it's probably starting with an EOL.
- Check that the last stripe is at least 5x larger than the first one, that's our EOL symbol which is 7 or 8-unit large, depending on the previous symbol.
- Convert pulses/stripes into nibbles
- Use the first stripes as we know they are units, to measure their width. There are some adjusting factors, also to adapt that value over time, not sure they are really needed anymore, depends on the quality of the scan.
- Convert pulse widths from pixels to units, being 1, 2 or 8 (for the EOL, actually 7 or 8 depending on previous symbol)
- Expand pulse widths into series of zeros and ones
- Group by 7 and decode into nibbles
- Parse nibbles
- Check first nibble is '0xd'
- Convert remaining nibbles into bytes (beware they are least-significant-nibble encoded!)
- Interpret first byte=line number
- Interpret second byte=function byte
- Interpret third byte=number of nibbles in the line, check it
- Interpret fourth byte as machine type if function byte is Control Array
- Interpret last byte as checksum, check it (sum of all bytes beside initial '0xd' must be zero)
- Isolate and print payload if function byte is one of the Data Line types
Code
As it's proving to be more useful than just for this write-up, the code is now available on Github: https://github.com/doegox/Oscar
Usage
First we've to extract the images from the Poc||GTFO 12 pdf, then use our script:
pdftk pocorgtfo12.pdf cat 53-54 output databar.pdf
pdfimages -all databar.pdf databar
./oscar.py databar-000.png databar-001.png |tee oscar_output.hex
ff0edb392537fe3fff0302392702f8394202ee395f02e4397d02da399e02d039bc02c639e002bc3a0402b23a28 02a83a60029e3a7e02943a9f028a3aa202803aa6fa0000000000000000ff02763a46026c3aae02623ac002583a c4024e3acf02443aed023a3b0f02303b2f02263b50021c3b7302123b7702083b7e01fe3b8a01f43b8e01ea3ba5 01e03bc3200000000000000000ff01d63be301cc3be701c23c0201b83c1601ae3c1901a43c30019a3c3f01903c 4301863c5c017c3c7f01723c8301683ca1015e3cc401543ce4014a3d0601403d25ff0000000000000000ff0136 3d47012c3d6801223d8901183dad010e3dcd01043df000fa3e1200f03e3500e63e5500dc3e7600d23e7a00c83e 9e00be3eb600b43eba00aa3ed800a03ef99d0000000000000000ff00963f17008c3f3500823f5300783f71006e ...
This is only the data part being extracted from the databar encapsulation.
With debuglevel=1 we get some more details:
linenumber: 0 function: 1 = CONTROL ARRAY machinetype: 3 = TI99/4 checksum: 0x4E = correct linenumber: 1 function: 3 = DATA LINE3 checksum: 0x75 = correct ff0edb392537fe3fff0302392702f8394202ee395f02e4397d02da399e02d039bc02c639e002bc3a0402b23a28 linenumber: 2 function: 3 = DATA LINE3 checksum: 0x8A = correct 02a83a60029e3a7e02943a9f028a3aa202803aa6fa0000000000000000ff02763a46026c3aae02623ac002583a ...
The control array contains far more info, ainly to tell to Oscar how to emulate a cassette reader for the specific machine.
According to the patent description, the control array contains a byte that tells us the databar is foreseen for a TI99/4.
But we've seen in the User Manual that it's more likely to be for its successor TI99/4A.
TI99/4A payload
So the databar payload is likely to be interpretable by a TI99/4A.
We can observe regular repetitions of "0000000000000000", let's realign the data:
cat oscar_output.hex|tr -d '\n'|sed 's/00000000ff/00000000\nff/g'|tee oscar_output2.hex
ff0edb392537fe3fff0302392702f8394202ee395f02e4397d02da399e02d039bc02c639e002bc3a0402b23a2802a83a60029e3a7e02943a9f028a3aa202803aa6fa0000000000000000 ff02763a46026c3aae02623ac002583ac4024e3acf02443aed023a3b0f02303b2f02263b50021c3b7302123b7702083b7e01fe3b8a01f43b8e01ea3ba501e03bc3200000000000000000 ... ff4e30393937303030370080808080b0b2a5b3b380a580b4af80a5b8a9b480808080808080808080808080808080808080808080808080808080808080808080801d0000000000000000 000000000000000000000000000000000000000000000000000000000000000000
So the structure seems to be something like
ff................................................................................................................................cc0000000000000000
with cc being a kind of checksum byte (see especially the last line and this will be confirmed when extracting strings later.
Let's isolate what's supposed to be the payload and let's see if we can observe strings:
sed 's/^ff//;s/..0000000000000000$//' oscar_output2.hex > oscar_output3.hex
xxd -r -p oscar_output3.hex > oscar_output3.hex.bin
hexdump -C -v oscar_output3.hex.bin
00000000 0e db 39 25 37 fe 3f ff 03 02 39 27 02 f8 39 42 |..9%7.?...9'..9B| 00000010 02 ee 39 5f 02 e4 39 7d 02 da 39 9e 02 d0 39 bc |..9_..9}..9...9.| 00000020 02 c6 39 e0 02 bc 3a 04 02 b2 3a 28 02 a8 3a 60 |..9...:...:(..:`| 00000030 02 9e 3a 7e 02 94 3a 9f 02 8a 3a a2 02 80 3a a6 |..:~..:...:...:.| 00000040 02 76 3a 46 02 6c 3a ae 02 62 3a c0 02 58 3a c4 |.v:F.l:..b:..X:.| 00000050 02 4e 3a cf 02 44 3a ed 02 3a 3b 0f 02 30 3b 2f |.N:..D:..:;..0;/| 00000060 02 26 3b 50 02 1c 3b 73 02 12 3b 77 02 08 3b 7e |.&;P..;s..;w..;~| 00000070 01 fe 3b 8a 01 f4 3b 8e 01 ea 3b a5 01 e0 3b c3 |..;...;...;...;.| 00000080 01 d6 3b e3 01 cc 3b e7 01 c2 3c 02 01 b8 3c 16 |..;...;...<...<.| 00000090 01 ae 3c 19 01 a4 3c 30 01 9a 3c 3f 01 90 3c 43 |..<...<0..<?..<C| 000000a0 01 86 3c 5c 01 7c 3c 7f 01 72 3c 83 01 68 3c a1 |..<\.|<..r<..h<.| 000000b0 01 5e 3c c4 01 54 3c e4 01 4a 3d 06 01 40 3d 25 |.^<..T<..J=..@=%| 000000c0 01 36 3d 47 01 2c 3d 68 01 22 3d 89 01 18 3d ad |.6=G.,=h."=...=.| 000000d0 01 0e 3d cd 01 04 3d f0 00 fa 3e 12 00 f0 3e 35 |..=...=...>...>5| 000000e0 00 e6 3e 55 00 dc 3e 76 00 d2 3e 7a 00 c8 3e 9e |..>U..>v..>z..>.| 000000f0 00 be 3e b6 00 b4 3e ba 00 aa 3e d8 00 a0 3e f9 |..>...>...>...>.| 00000100 00 96 3f 17 00 8c 3f 35 00 82 3f 53 00 78 3f 71 |..?...?5..?S.x?q| 00000110 00 6e 3f 75 00 64 3f 79 00 5a 3f 97 00 50 3f a9 |.n?u.d?y.Z?..P?.| 00000120 00 46 3f bc 00 3c 3f c0 00 32 3f c4 00 28 3f d9 |.F?..<?..2?..(?.| 00000130 1a 9a 20 49 4e 20 54 48 45 20 4e 45 58 54 20 46 |.. IN THE NEXT F| 00000140 45 57 20 49 53 53 55 45 53 2e 00 1c 9a 20 50 52 |EW ISSUES.... PR| 00000150 4f 47 52 41 4d 53 20 44 4f 20 57 48 41 54 20 59 |OGRAMS DO WHAT Y| 00000160 4f 55 20 57 41 4e 54 00 1d 9a 20 57 45 27 4c 4c |OU WANT... WE'LL| 00000170 20 54 45 4c 4c 20 59 4f 55 20 48 4f 57 20 54 4f | TELL YOU HOW TO| 00000180 20 4d 41 4b 45 00 20 9a 20 4d 41 4b 45 20 54 48 | MAKE. . MAKE TH| 00000190 49 53 20 50 52 4f 47 52 41 4d 20 53 54 41 52 54 |IS PROGRAM START| 000001a0 20 4f 56 45 52 3f 00 1d 9a 20 57 48 41 54 20 4c | OVER?... WHAT L| 000001b0 49 4e 45 20 57 4f 55 4c 44 20 59 4f 55 20 41 44 |INE WOULD YOU AD| 000001c0 44 20 54 4f 00 23 93 c8 07 31 32 33 39 34 2e 30 |D TO.#...12394.0| 000001d0 b3 c8 04 31 38 2e 34 b3 c8 07 31 32 36 35 37 2e |...18.4...12657.| 000001e0 37 b3 c8 04 31 36 2e 32 00 23 93 c8 07 31 31 38 |7...16.2.#...118| 000001f0 32 33 2e 32 b3 c8 04 38 2e 36 35 b3 c8 07 31 32 |23.2...8.65...12| 00000200 31 31 38 2e 34 b3 c8 04 32 30 2e 37 00 23 93 c8 |118.4...20.7.#..| 00000210 07 31 31 35 32 30 2e 37 b3 c8 04 37 2e 33 30 b3 |.11520.7...7.30.| 00000220 c8 07 31 31 36 37 38 2e 36 b3 c8 04 39 2e 38 30 |..11678.6...9.80| 00000230 00 1d 9a 20 20 4d 49 4c 45 53 2d 2d 20 47 41 4c |... MILES-- GAL| 00000240 53 2d 2d 4d 49 4c 45 53 2d 2d 47 41 4c 53 00 19 |S--MILES--GALS..| 00000250 9c 4d 32 b4 fc b7 c8 02 31 32 b6 b4 47 b4 fc b7 |.M2.....12..G...| 00000260 c8 02 32 30 b6 b4 4d 47 00 1d 9a 20 47 41 4c 4c |..20..MG... GALL| 00000270 4f 4e 53 20 55 53 45 44 20 49 53 20 4e 45 58 54 |ONS USED IS NEXT| 00000280 2e 20 20 20 20 20 00 20 9a 20 54 48 45 20 44 41 |. . . THE DA| 00000290 54 41 20 46 4f 52 20 54 48 45 20 4d 49 4c 45 41 |TA FOR THE MILEA| 000002a0 47 45 20 41 4e 44 20 00 02 8b 00 03 96 58 00 07 |GE AND ......X..| 000002b0 8d 4d 31 be 4d 32 00 11 8d 4d 47 be cf b7 b7 4d |.M1.M2...MG....M| 000002c0 32 c2 4d 31 b6 c4 47 b6 00 03 9a 20 00 0a 9a 20 |2.M1..G.... ... | 000002d0 41 47 41 49 4e 2e 20 00 1d 9a 20 43 41 4c 43 55 |AGAIN. ... CALCU| 000002e0 4c 41 54 49 4f 4e 2e 20 20 44 4f 20 54 48 45 20 |LATION. DO THE | 000002f0 4c 4f 4f 50 20 00 21 9a 20 53 54 41 52 54 49 4e |LOOP .!. STARTIN| 00000300 47 20 4d 49 4c 45 41 47 45 20 46 4f 52 20 54 48 |G MILEAGE FOR TH| 00000310 45 20 4e 45 58 54 20 00 1f 9a 20 54 48 45 20 4e |E NEXT ... THE N| 00000320 45 57 20 4d 49 4c 45 41 47 45 20 52 45 41 44 49 |EW MILEAGE READI| 00000330 4e 47 20 54 48 45 20 00 20 9a 20 50 52 49 4e 54 |NG THE . . PRINT| 00000340 20 54 48 45 20 41 4e 53 57 45 52 2e 20 20 54 48 | THE ANSWER. TH| 00000350 45 4e 20 4d 41 4b 45 20 00 22 9a 20 43 41 4c 43 |EN MAKE .". CALC| 00000360 55 4c 41 54 45 20 4d 49 4c 45 53 20 50 45 52 20 |ULATE MILES PER | 00000370 47 41 4c 4c 4f 4e 20 41 4e 44 20 00 03 9a 20 00 |GALLON AND ... .| 00000380 06 97 4d 32 b3 47 00 0b 8c 58 be c8 01 31 b1 c8 |..M2.G...X...1..| 00000390 01 36 00 03 9a 20 00 16 9a 20 47 41 4c 4c 4f 4e |.6... ... GALLON| 000003a0 53 20 55 53 45 44 20 44 41 54 41 2e 20 00 1d 9a |S USED DATA. ...| 000003b0 20 52 45 41 44 20 54 48 45 20 4e 45 58 54 20 4d | READ THE NEXT M| 000003c0 49 4c 45 41 47 45 20 41 4e 44 20 00 1f 9a 20 54 |ILEAGE AND ... T| 000003d0 48 45 52 45 20 41 52 45 20 36 20 52 45 41 44 49 |HERE ARE 6 READI| 000003e0 4e 47 53 20 54 4f 20 44 4f 2e 20 00 03 9a 20 00 |NGS TO DO. ... .| 000003f0 1a 9c c7 16 20 4d 49 4c 45 41 47 45 20 20 47 41 |.... MILEAGE GA| 00000400 4c 4c 4f 4e 53 20 20 4d 50 47 00 13 9c c7 0f 20 |LLONS MPG..... | 00000410 20 20 20 20 20 20 20 20 20 20 23 20 4f 46 00 02 | # OF..| 00000420 9c 00 16 9c c7 12 20 20 20 54 52 49 50 20 54 4f |...... TRIP TO| 00000430 20 50 48 4f 45 4e 49 58 00 0e 8d 4d 31 be c8 07 | PHOENIX...M1...| 00000440 31 31 34 30 36 2e 34 00 03 9a 20 00 18 9a 20 54 |11406.4... ... T| 00000450 48 45 20 43 4f 4c 55 4d 4e 20 48 45 41 44 49 4e |HE COLUMN HEADIN| 00000460 47 53 2e 20 00 22 9a 20 53 45 54 20 53 54 41 52 |GS. .". SET STAR| 00000470 54 49 4e 47 20 4d 49 4c 45 41 47 45 20 41 4e 44 |TING MILEAGE AND| 00000480 20 50 52 49 4e 54 20 00 03 9a 20 00 1d 9a 20 54 | PRINT ... ... T| 00000490 48 45 20 4d 49 4c 45 41 47 45 20 52 45 41 44 49 |HE MILEAGE READI| 000004a0 4e 47 53 2e 20 20 20 20 20 00 22 9a 20 50 52 4f |NGS. .". PRO| 000004b0 43 45 53 53 20 49 53 20 52 45 50 45 41 54 45 44 |CESS IS REPEATED| 000004c0 20 46 4f 52 20 41 4c 4c 20 4f 46 20 00 1f 9a 20 | FOR ALL OF ... | 000004d0 54 48 45 20 4d 49 4c 45 53 20 50 45 52 20 47 41 |THE MILES PER GA| 000004e0 4c 4c 4f 4e 2e 20 20 54 48 49 53 20 00 21 9a 20 |LLON. THIS .!. | 000004f0 4e 55 4d 42 45 52 20 4f 46 20 47 41 4c 4c 4f 4e |NUMBER OF GALLON| 00000500 53 20 55 53 45 44 20 54 4f 20 47 45 54 20 00 1e |S USED TO GET ..| 00000510 9a 20 4f 46 20 4d 49 4c 45 53 20 49 53 20 44 49 |. OF MILES IS DI| 00000520 56 49 44 45 44 20 42 59 20 54 48 45 20 00 21 9a |VIDED BY THE .!.| 00000530 20 42 45 54 57 45 45 4e 20 46 49 4c 4c 2d 55 50 | BETWEEN FILL-UP| 00000540 53 2e 20 20 54 48 45 20 4e 55 4d 42 45 52 20 00 |S. THE NUMBER .| 00000550 20 9a 20 54 48 45 20 4e 55 4d 42 45 52 20 4f 46 | . THE NUMBER OF| 00000560 20 4d 49 4c 45 53 20 54 52 41 56 45 4c 45 44 20 | MILES TRAVELED | 00000570 00 20 9a 20 4d 49 4c 45 41 47 45 20 52 45 41 44 |. . MILEAGE READ| 00000580 49 4e 47 2e 20 20 54 48 49 53 20 47 49 56 45 53 |ING. THIS GIVES| 00000590 20 00 23 9a 20 53 55 42 54 52 41 43 54 49 4e 47 | .#. SUBTRACTING| 000005a0 20 49 54 20 46 52 4f 4d 20 54 48 45 20 43 55 52 | IT FROM THE CUR| 000005b0 52 45 4e 54 20 00 1f 9a 20 52 45 41 44 49 4e 47 |RENT ... READING| 000005c0 20 46 52 4f 4d 20 54 48 45 20 2d 44 41 54 41 2d | FROM THE -DATA-| 000005d0 20 41 4e 44 20 00 22 9a 20 57 4f 52 4b 53 20 42 | AND .". WORKS B| 000005e0 59 20 47 45 54 54 49 4e 47 20 41 20 4e 45 57 20 |Y GETTING A NEW | 000005f0 4d 49 4c 45 41 47 45 20 00 21 9a 20 44 55 52 49 |MILEAGE .!. DURI| 00000600 4e 47 20 54 48 45 20 54 52 49 50 2e 20 20 54 48 |NG THE TRIP. TH| 00000610 45 20 50 52 4f 47 52 41 4d 20 00 22 9a 20 4d 49 |E PROGRAM .". MI| 00000620 4c 45 41 47 45 20 41 4e 44 20 47 41 4c 4c 4f 4e |LEAGE AND GALLON| 00000630 53 20 44 41 54 41 20 54 41 4b 45 4e 20 00 1f 9a |S DATA TAKEN ...| 00000640 20 45 4e 44 20 4f 46 20 54 48 45 20 50 52 4f 47 | END OF THE PROG| 00000650 52 41 4d 20 48 41 56 45 20 54 48 45 20 00 20 9a |RAM HAVE THE . .| 00000660 20 54 48 45 20 2d 44 41 54 41 2d 20 53 54 41 54 | THE -DATA- STAT| 00000670 45 4d 45 4e 54 53 20 41 54 20 54 48 45 20 00 03 |EMENTS AT THE ..| 00000680 9a 20 00 23 9a 20 4d 47 3d 49 4e 54 45 47 45 52 |. .#. MG=INTEGER| 00000690 20 56 41 4c 55 45 20 4f 46 20 28 28 4d 31 2d 4d | VALUE OF ((M1-M| 000006a0 32 29 2f 47 29 20 00 17 9a 20 2a 2a 2a 2a 2a 20 |2)/G) ... ***** | 000006b0 46 4f 52 4d 55 4c 41 20 2a 2a 2a 2a 2a 20 00 03 |FORMULA ***** ..| 000006c0 9a 20 00 1d 9a 20 2a 2a 2a 20 4d 47 20 3d 20 4d |. ... *** MG = M| 000006d0 49 4c 45 53 20 50 45 52 20 47 41 4c 4c 4f 4e 20 |ILES PER GALLON | 000006e0 00 20 9a 20 2a 2a 2a 20 47 20 20 3d 20 47 41 4c |. . *** G = GAL| 000006f0 4c 4f 4e 53 20 4f 46 20 47 41 53 20 55 53 45 44 |LONS OF GAS USED| 00000700 20 00 1d 9a 20 2a 2a 2a 20 4d 32 20 3d 20 45 4e | ... *** M2 = EN| 00000710 44 49 4e 47 20 4d 49 4c 45 41 47 45 20 20 20 00 |DING MILEAGE .| 00000720 1d 9a 20 2a 2a 2a 20 4d 31 20 3d 20 53 54 41 52 |.. *** M1 = STAR| 00000730 54 49 4e 47 20 4d 49 4c 45 41 47 45 20 00 1d 9a |TING MILEAGE ...| 00000740 20 2a 2a 2a 20 58 20 20 3d 20 4c 4f 4f 50 20 43 | *** X = LOOP C| 00000750 4f 55 4e 54 45 52 20 20 20 20 20 00 1d 9a 20 2a |OUNTER ... *| 00000760 2a 2a 2a 2a 20 20 44 49 43 54 49 4f 4e 41 52 59 |**** DICTIONARY| 00000770 20 20 2a 2a 2a 2a 2a 20 20 00 03 9a 20 00 03 9a | ***** ... ...| 00000780 20 00 1d 9a 20 45 44 45 4e 20 50 52 41 49 52 49 | ... EDEN PRAIRI| 00000790 45 2c 20 4d 4e 20 20 35 35 33 34 34 20 20 20 00 |E, MN 55344 .| 000007a0 11 9a 20 44 41 54 41 42 41 52 20 43 4f 52 50 2e |.. DATABAR CORP.| 000007b0 20 00 12 9a 20 43 4f 50 59 52 49 47 48 54 20 31 | ... COPYRIGHT 1| 000007c0 39 38 33 20 00 03 9a 20 00 03 9a 20 00 14 9a 20 |983 ... ... ... | 000007d0 4d 49 4c 45 53 20 50 45 52 20 47 41 4c 4c 4f 4e |MILES PER GALLON| 000007e0 20 00 27 58 31 24 be c7 20 28 43 29 20 31 39 38 | .'X1$.. (C) 198| 000007f0 33 20 44 41 54 41 42 41 52 20 43 4f 52 50 2e 53 |3 DATABAR CORP.S| 00000800 4e 30 39 39 37 30 30 30 37 00 80 80 80 80 b0 b2 |N09970007.......| 00000810 a5 b3 b3 80 a5 80 b4 af 80 a5 b8 a9 b4 80 80 80 |................| 00000820 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 |................| 00000830 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 |................| 00000840 1d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000850 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000860 00 |.|
Strangely the lines seem to be in reverse order, but at other boundaries than those observed in oscar_output2.hex.
strings oscar_output3.hex.bin |tac |tee oscar_output3.txt
(C) 1983 DATABAR CORP.SN09970007 'X1$ MILES PER GALLON COPYRIGHT 1983 DATABAR CORP. EDEN PRAIRIE, MN 55344 ***** DICTIONARY ***** *** X = LOOP COUNTER *** M1 = STARTING MILEAGE *** M2 = ENDING MILEAGE *** G = GALLONS OF GAS USED *** MG = MILES PER GALLON ***** FORMULA ***** MG=INTEGER VALUE OF ((M1-M2)/G) THE -DATA- STATEMENTS AT THE END OF THE PROGRAM HAVE THE MILEAGE AND GALLONS DATA TAKEN DURING THE TRIP. THE PROGRAM WORKS BY GETTING A NEW MILEAGE READING FROM THE -DATA- AND SUBTRACTING IT FROM THE CURRENT MILEAGE READING. THIS GIVES THE NUMBER OF MILES TRAVELED BETWEEN FILL-UPS. THE NUMBER OF MILES IS DIVIDED BY THE NUMBER OF GALLONS USED TO GET THE MILES PER GALLON. THIS PROCESS IS REPEATED FOR ALL OF THE MILEAGE READINGS. SET STARTING MILEAGE AND PRINT THE COLUMN HEADINGS. 11406.4 TRIP TO PHOENIX # OF MILEAGE GALLONS MPG THERE ARE 6 READINGS TO DO. READ THE NEXT MILEAGE AND GALLONS USED DATA. CALCULATE MILES PER GALLON AND PRINT THE ANSWER. THEN MAKE THE NEW MILEAGE READING THE STARTING MILEAGE FOR THE NEXT CALCULATION. DO THE LOOP AGAIN. THE DATA FOR THE MILEAGE AND GALLONS USED IS NEXT. MILES-- GALS--MILES--GALS 9.80 11678.6 7.30 11520.7 20.7 12118.4 8.65 11823.2 16.2 12657.7 18.4 12394.0 WHAT LINE WOULD YOU ADD TO MAKE THIS PROGRAM START OVER? WE'LL TELL YOU HOW TO MAKE PROGRAMS DO WHAT YOU WANT IN THE NEXT FEW ISSUES.
TI99/4A emulation
There are apparently a number of emulators around, but I didn't see how to match the payload we have with the various expected data formats.
The Oscar manual says in the section specific to TI99/4A that it's to be used as a cassette reader "TI Basic" "OLD CS1".
So, theoretically, one approach is that one could interpret the Control Array with both patents and produce a wav file to feed e.g. in the MESS emulator.
Any volunteer?
An other approach is to take the payload we got and understand how to convert it into a "disk" format.
One could try to disassemble the payload, but it's Basic so full of refs to routines in TI99/4A ROM.
If anyone could go further than I, contact me on twitter (@doegox).