Inside the Einstein: The Disc, a TatungBytes course: XtalDOS listing the first practice disc image, on a rainbow sunburst
← Back to Modules
module
5

Making A Byte

Introduction

A disc image is a file, and a file is a row of bytes. If you can make a byte in Python, you can make a file of them; and if you can change one byte of a file, you can change a disc. This section does exactly that: it starts with one byte and ends with you changing the text inside a file on FIRST.DSK and asking the Einstein to show it to you.

One Byte

Python keeps bytes in a value called, sensibly, bytes. You make one from a list of numbers in square brackets:

one = bytes([229])

That is one byte, holding 229. If you print it, Python shows it as b'\xe5': the b'...' says "these are bytes", and \xe5 means "the byte whose hex value is E5" - the same 229 from S4, written in the hex you now know. len(one) is 1: one byte long.

Python shows a byte as a character when it is the code for a letter, digit or punctuation mark, and as \x.. hex when it is not. 72 is the code for H, 105 for i, 33 for !, and 10 is "new line", shown as \n. That is all text is, on a disc or anywhere else: bytes, each holding the code of one character.

Bytes You Can Change

A bytes value cannot be changed once it is made. For bytes you mean to change, Python has bytearray, which is the same thing but editable.

Each byte has a position, counted from 0: the first byte is at position 0, the second at 1, and so on. You read one with square brackets after the name - four[1] is the byte at position 1 - and you change one the same way:

four[0] = 0x68

puts 0x68 (the code for a small h) in position 0.

Counting from 0 feels odd for a day and then becomes natural, and it is exactly how a disc counts: the first sector of a track is sector 0, and the first track is track 0.

Into A File, And Back

To put bytes in a file:

with open("four.bin", "wb") as f:
    f.write(four)

open opens a file by name; "wb" means write, in bytes. The with ... as f: line gives the open file the name f for the indented line underneath it, and closes the file properly when that is done. The indent - four spaces - matters in Python: it is how Python knows which lines belong to the with.

To read a file back, open it with "rb" - read, bytes - and call read(), which gives you everything in it as one bytes value.

One more thing you can do with bytes: multiply them. bytes([0xE5]) * 512 is 512 bytes, every one E5 - a blank sector, made in one line.

The code

Save this as bytes.py in edisc.

one = bytes([229])
print(one)
print(len(one))

four = bytearray([72, 105, 33, 10])
print(four)
four[0] = 0x68
print(four)
print(four[1], hex(four[1]))

with open("four.bin", "wb") as f:
    f.write(four)

with open("four.bin", "rb") as f:
    back = f.read()
print(back, len(back))

sector = bytes([0xE5]) * 512
print(len(sector), sector[0], sector[511])

Run it

python3 bytes.py

What you should see

b'\xe5'
1
bytearray(b'Hi!\n')
bytearray(b'hi!\n')
105 0x69
b'hi!\n' 4
512 229 229

Four bytes - 72, 105, 33, 10 - print as Hi! and a new line, because those are the codes for those characters. Changing position 0 to 0x68 made it hi!. Your edisc folder now holds a file called four.bin that is exactly four bytes long, and reading it back gave the same four bytes. The last line is the blank sector: 512 bytes, and the first (position 0) and last (position 511) are both 229, E5.

Changing A Disc

Now for a real disc image. FIRST.DSK holds a file called HELLO.TXT, and in S3 the Einstein displayed it: Hello from your first disc image. That text is somewhere among the image's 215,296 bytes. You do not know where yet - Part three of the course will teach you to work it out exactly - but Python can search for it. find looks for a run of bytes and gives you the position where it starts.

Save this as change.py in edisc, beside FIRST.DSK.

with open("FIRST.DSK", "rb") as f:
    disc = bytearray(f.read())

print("The image is", len(disc), "bytes")

where = disc.find(b"Hello")
print("Found 'Hello' at byte", where, "which is", hex(where))
print(disc[where:where + 33])

disc[where:where + 5] = b"HELLO"
print(disc[where:where + 33])

with open("CHANGED.DSK", "wb") as f:
    f.write(disc)
print("Wrote CHANGED.DSK")

Two new things in it. bytearray(f.read()) reads the whole image and makes it editable. And disc[where:where + 33] is a slice: the bytes from position where up to - but not including - position where + 33. That is 33 bytes, the length of the sentence. A slice can be changed too: disc[where:where + 5] = b"HELLO" replaces the five bytes of Hello with the five bytes of HELLO.

The program writes the result to a new file, CHANGED.DSK, and leaves FIRST.DSK as it was.

Run it

python3 change.py

What you should see

The image is 215296 bytes
Found 'Hello' at byte 13312 which is 0x3400
bytearray(b'Hello from your first disc image.')
bytearray(b'HELLO from your first disc image.')
Wrote CHANGED.DSK

Now ask the Einstein. Start MAME with CHANGED.DSK in drive 1:

mame -rompath ~/roms einstein -flop1 XTALDOS.DSK -flop2 CHANGED.DSK -window

and type DIR 1:, then DISP 1:HELLO.TXT:

0:DIR 1:
1: HELLO   .TXT
2k Size, 186k Free, 190k Total
0:DISP 1:HELLO.TXT
HELLO from your first disc image.

0:

XtalDOS displaying the changed file

You changed five bytes of a disc image with a program you wrote, and a disc operating system from 1984 read them back. Every section from here on is that, done with more understanding each time.

Change one thing

Make each change in change.py, run it, and look at CHANGED.DSK in MAME.

  • Change b"HELLO" to b"Jello". What will DISP show?
  • Search for b"first" instead of b"Hello", and replace those five bytes with b"FIRST". Which number in the second line of output changes, and by how much? Why that much?
  • Replace the five bytes with b"Hi" - only two bytes. Look at what print shows afterwards, and at the image's length. What has happened to the image, and what do you expect MAME or DISP to make of it?

When it goes wrong

SymptomCause
FileNotFoundError: [Errno 2] No such file or directory: 'FIRST.DSK'FIRST.DSK is not in the folder the terminal is in. cd into edisc, and check the file is there
find prints -1The bytes searched for are not in the image - a typing difference, or capitals where the image has small letters. -1 is how find says "not found"
TypeError: 'bytes' object does not support item assignmentYou tried to change a bytes. Make it a bytearray first
MAME closes at once, and the terminal shows Caught unhandled St16invalid_argument exception: Incorrect layout on track 7 head 0, ...The image is no longer the size its own headers say. Replacing 5 bytes with 2 made it 3 bytes shorter (215,293), and everything after the change moved 3 places. Replace bytes with the same number of bytes

Summary

A byte in Python is a bytes or, if you want to change it, a bytearray. You can make them from numbers, read and change them by position (from 0), take slices, search them with find, and write them to a file and read them back with open in "wb" and "rb" modes. A disc image is just such a file - and you have already changed one and had the Einstein agree.

Next

S6, Doing It Again - loops and functions, and the dump helper that shows any part of a disc image as rows of hex.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.