Foreword
In the previous post we managed to read a sector of data from an SD Card.
In this post w will continue our journey and see if we can read a file from a FAT32 formatted partition. To be able to read a file from a partition will form an important part of being able to boot an Amiga core on a Arty A7, and thus to load a Boot ROM and disk image from SD Card.
There is quite number of technical details involved to read a file from a FAT32 partition. Writing the functionality for this right from the start in 6502 Assembly language is quite a daunting task.
To make our lives easier we will start to write the functionality in a High Level language. As my current knowledge of FAT32 is rather limited, experimenting with such a partition in a High Level Language will get one quickly up to speed.
Once we know how to read a file from a FAT32 partition, we can write 6502 assembly for this in a future post.
About MBR and FAT32
Before we look in detail how to read a file from an SD Card, let us start with some basic terminology.
Firstly, the storage of any SD Card is divided into many blocks, where each block is 512 bytes in size. The choice of 512 bytes per block is actually rooted in the history of Personal computers where almost any Floppy Disk Drive or Hard Drive had a basic block size of 512 bytes.
To address blocks, all the blocks are numbered consecutively starting at block 0, and going up to the maximum number of blocks the device supports.
Block zero is called the MBR or master boot record. This is also where the history of the IBM PC kicks in again and I think is probably still relevant today. When an IBM PC boots up, it looks for machine language program at block zero, to start the booting of the system. Hence the name Master Boot Record.
The MBR has some other purpose as well, which is to store one or more partition entries, so you can be able to create more than one volume on the same device. We briefly encountered this in the previous post where we saw data at location 0x1be of block 0. This data was in actual fact a partition entry.
Also, from the previous post, you will remember that apart from the partition entry data, all the other bytes of that block were zero. So, although we call block zero the MBR, the SD Cards you use today most probably will not contain machine code in that block.
Now back to the partition entry. A partition entry gives us the block number of the sector of the partition in question. This first sector of the partition is called again, surprise, a boot record! The name is again because of the legacy of the IBM PC.
The boot record also contain some data to hold of the File allocation table and to be able to read the contents of a file.
We will have a look at a typical boot record in the next section.
Looking at the boot record
To look at the boot record, we first need find the block number of the it, via the MBR. In the previous post I made a screenshot of the MBR from the SD Card I was playing with, which contained the partition entry.
Here is the screenshot again, but with the bytes of the partition entry highlighted:
As you can see the partition entry starts at 0x1be and is 16 bytes in size. To get the meaning of the bytes, we look at the following link:
https://en.wikipedia.org/wiki/Master_boot_record#PTE
Two pieces of useful information for us, in the last 8 bytes of the entry:
- offset 8: LBA of first absolute sector in the partition (4 bytes)
- offset C: Number of sectors in partition.
RandomAccessFile fis = RandomAccessFile("dump.sdcard","r");
With instances of RandomAccessFile, we can easily jump around within different positions in the file, which is what will need for this exercise of attempting to read a file from a FAT32 partition in a dump file. fis.seek(8213 * 512);
byte[] buf = new byte[512];
fis.read(buf);
var dataString = new String(buf, StandardCharsets.US_ASCII);
System.out.println(dataString);
Obviously we need to multiply 8213 by 512, because the seek method wants the position in bytes. I then reaad the sector into a byte buffer and then convert it to a String to see if there is any interesting human readable properties. When printing the String, we see the following:A deeper look into the Boot sector
sectorsPerFat = FatBrowser.readFourBytes(buf, 36);
numFat = buf[16];
numReserved = buf[14];
sectorsPerCluster = buf[13];
rootCluster = buf[44];
dataStart = numReserved + numFat * sectorsPerFat;
Let us dissect this snippet of code a bit. buf is the byte buffer we read in the previous section, containing the boot sector. FatBrowser.readFourBytes() is a pseudo function for taking four bytes starting at position 36 of buf, and forming a number.Looking into the root directory
fis.seek((8213 + 9 + 945+ 945) * 512);
byte[] buf = new byte[512];
fis.read(buf);
var dataString = new String(buf, StandardCharsets.US_ASCII);
for (int i = 0; i < 16; i++) {
System.out.println(dataString.substring(0, 32));
dataString = dataString.substring(32);
}
The number we use in the seek I used as derived from the previous section, with 8213 the start of my FAT32 partition.Reading a file
- READ_ONLY=0x01
- HIDDEN=0x02
- SYSTEM=0x04
- VOLUME_ID=0x08
- DIRECTORY=0x10
- ARCHIVE=0x20
- LFN=READ_ONLY|HIDDEN|SYSTEM|VOLUME_ID
for (int i = 0; i < 16; i++) {
int beginFileEntry = i * 32;
if (buf[beginFileEntry + 11] == 15) {
continue;
}
System.out.println(new String(buf, beginFileEntry, 11));
}
This result in the following output: for (int j = 0; j < 63; j++) {
fis.read(buf);
for (int i = 0; i < 16; i++) {
int beginFileEntry = i * 32;
if (buf[beginFileEntry + 11] == 15) {
continue;
}
if ((buf[beginFileEntry] & 0xff) == 0xE5) {
continue;
}
System.out.println(new String(buf, beginFileEntry, 11));
}
}
This time we are seeing some more interesting stuff: int foundSlot = -1;
outerLoop:
for (int j = 0; j < 63; j++) {
fis.read(buf);
for (int i = 0; i < 16; i++) {
int beginFileEntry = i * 32;
if (buf[beginFileEntry + 11] == 15) {
continue;
}
if ((buf[beginFileEntry] & 0xff) == 0xE5) {
continue;
}
if (new String(buf, beginFileEntry, 11).equalsIgnoreCase("TEST TXT")) {
foundSlot = i * 32;
break outerLoop;
}
}
}
Now, from the resulting file entry, we know the following:- bytes 20 + 21: High 16 bits of first cluster number for file
- bytes 26 + 27: low 16 bits of first cluster number for file
int cluster = (buf[foundSlot + 21] & 0xff) >> 24 | (buf[foundSlot + 20] & 0xff) >> 16
| (buf[foundSlot + 27] & 0xff) >> 8 | (buf[foundSlot + 26] & 0xff) >> 0;
Finally, we read the first sector of the file in question as follows: fis.seek((8213 + 9 + 945+ 945 + (cluster - 2) * 64) * 512);
fis.read(buf);
System.out.println(new String(buf));
In the seek command we basically start off again with calculation to find beginning of Data area, and adding to it the cluster number converted to a sector count. This is the output I get:.png)





No comments:
Post a Comment